gitcommit90 commited on
Commit
a57aa9f
·
verified ·
1 Parent(s): 23db6ef

NVFP4 (ModelOpt) quantization of sensenova/SenseNova-Loop-9B-Preview @27abea77 with BF16 MTP head

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +11 -0
  2. LICENSE +201 -0
  3. README.md +186 -0
  4. SHA256SUMS +65 -0
  5. chat_template.jinja +154 -0
  6. config.json +261 -0
  7. evaluation/QUALITY_GATES.md +14 -0
  8. evaluation/compare_vs_ref.json +138 -0
  9. evaluation/eval_data_MANIFEST.json +67 -0
  10. evaluation/harness/build_eval_data.py +139 -0
  11. evaluation/harness/common.py +177 -0
  12. evaluation/harness/compare.py +133 -0
  13. evaluation/harness/ifeval_lite.py +236 -0
  14. evaluation/harness/mock_server.py +171 -0
  15. evaluation/harness/perf_bench.py +188 -0
  16. evaluation/harness/requirements.lock +48 -0
  17. evaluation/harness/run_eval.py +139 -0
  18. evaluation/harness/tasks.py +284 -0
  19. evaluation/harness/test_harness.py +147 -0
  20. evaluation/harness/tool_cases.py +116 -0
  21. evaluation/hf_bf16_checks.json +85 -0
  22. evaluation/kernel_summary.txt +28 -0
  23. evaluation/nvfp4_v1_mtp_release_probe.json +82 -0
  24. evaluation/nvfp4_v1_probe.json +82 -0
  25. evaluation/perf_sglang_bf16.json +170 -0
  26. evaluation/perf_sglang_bf16_mtp.json +170 -0
  27. evaluation/perf_sglang_nvfp4_v1.json +170 -0
  28. evaluation/perf_sglang_nvfp4_v1_marlin_w4a16.json +98 -0
  29. evaluation/perf_sglang_nvfp4_v1_mtp.json +170 -0
  30. evaluation/sglang_bf16_mtp_probe.json +82 -0
  31. evaluation/sglang_bf16_probe.json +82 -0
  32. evaluation/summary_sglang_bf16.json +148 -0
  33. evaluation/summary_sglang_bf16_mtp.json +103 -0
  34. evaluation/summary_sglang_nvfp4_v1.json +148 -0
  35. evaluation/summary_sglang_nvfp4_v1_marlin_w4a16_gsm8k.json +62 -0
  36. evaluation/summary_sglang_nvfp4_v1_mtp.json +103 -0
  37. generation_config.json +12 -0
  38. hf_quant_config.json +103 -0
  39. model.safetensors +3 -0
  40. modeling_sensenova_loop.py +395 -0
  41. preprocessor_config.json +26 -0
  42. processor_config.json +60 -0
  43. quantization/10_reference_checks.py +92 -0
  44. quantization/20_quantize.py +134 -0
  45. quantization/30_verify_export.py +126 -0
  46. quantization/build_calib.py +329 -0
  47. quantization/calib_v1.jsonl.sha256 +1 -0
  48. quantization/calib_v1.meta.json +72 -0
  49. quantization/quantization_stats.json +389 -0
  50. quantization/requirements-quant.lock +107 -0
.gitattributes CHANGED
@@ -33,3 +33,14 @@ 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
+ assets/loop-mhc-overview.jpg filter=lfs diff=lfs merge=lfs -text
37
+ assets/loop9b-benchmark-results.png filter=lfs diff=lfs merge=lfs -text
38
+ examples/chocolate/chocolate-horizontal.jpg filter=lfs diff=lfs merge=lfs -text
39
+ examples/energy-analysis/energy-analysis.png filter=lfs diff=lfs merge=lfs -text
40
+ examples/iceland/iceland-horizontal-v2.jpg filter=lfs diff=lfs merge=lfs -text
41
+ examples/ink-journey/ink-journey-demo.mp4 filter=lfs diff=lfs merge=lfs -text
42
+ examples/ink-journey/ink-journey-preview.gif filter=lfs diff=lfs merge=lfs -text
43
+ examples/moon-simulation/moon-simulation.gif filter=lfs diff=lfs merge=lfs -text
44
+ examples/teacher-day/teacher-day-horizontal.jpg filter=lfs diff=lfs merge=lfs -text
45
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
46
+ examples/odyssey/odyssey-preview.gif filter=lfs diff=lfs merge=lfs -text
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.
README.md ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: sensenova/SenseNova-Loop-9B-Preview
4
+ base_model_relation: quantized
5
+ library_name: transformers
6
+ pipeline_tag: image-text-to-text
7
+ language:
8
+ - en
9
+ - zh
10
+ tags:
11
+ - nvfp4
12
+ - modelopt
13
+ - quantized
14
+ - multimodal
15
+ - looped-transformer
16
+ - mhc
17
+ - mtp
18
+ - custom_code
19
+ ---
20
+
21
+ # SenseNova-Loop-9B-Preview-NVFP4
22
+
23
+ > **Unofficial community quantization.** The original model, architecture and weights are by **SenseNova**:
24
+ > [sensenova/SenseNova-Loop-9B-Preview](https://huggingface.co/sensenova/SenseNova-Loop-9B-Preview)
25
+ > (revision `27abea779b1e6634b136805b6519371739d551f7`), licensed Apache-2.0. We did not train this model.
26
+ > This repository contains modified weights (post-training quantization only — no fine-tuning, QAT or distillation).
27
+
28
+ A calibrated, packed **NVFP4** (ModelOpt) checkpoint of SenseNova-Loop-9B-Preview with the **MTP head kept BF16**
29
+ so MTP speculative decoding works, validated on an NVIDIA DGX Spark (GB10, SM121) with **native W4A4 FP4 GEMMs**.
30
+
31
+ ## Provenance
32
+ | Item | Value |
33
+ | --- | --- |
34
+ | Base model | `sensenova/SenseNova-Loop-9B-Preview` @ `27abea779b1e6634b136805b6519371739d551f7` (weights stored FP32 upstream; loaded as BF16 backbone + FP32 mHC, as the upstream card prescribes) |
35
+ | Quantization tool | NVIDIA Model Optimizer `nvidia-modelopt==0.48.0rc1` (`NVFP4_DEFAULT_CFG`, `max` calibration), PyTorch 2.11.0+cu130, Transformers 5.18.0 |
36
+ | Format | NVFP4: E2M1 weights, group size 16, FP8-E4M3 block scales, FP32 per-tensor global scale (`weight_scale_2`) and static activation global scale (`input_scale`); activations quantized to NVFP4 at runtime (W4A4); KV cache **not** quantized (BF16) |
37
+ | Export | 2026-10-30, `export_hf_checkpoint`, single `model.safetensors` (9.38 GB) |
38
+ | Hardware | NVIDIA DGX Spark: GB10 (SM121), 128 GB unified memory, driver 580.159.03, CUDA 13.0 |
39
+
40
+ ## Changes from the original (Apache-2.0 §4(b) notice)
41
+ - Decoder linear-layer weights quantized to NVFP4 (see exclusions); `config.json` gains `quantization_config`;
42
+ `hf_quant_config.json` added. Upstream FP32-stored tensors that were not quantized are stored as BF16, except the
43
+ mHC routing parameters, which remain FP32.
44
+ - `mtp.*` (MTP head) is included in `model.safetensors` as BF16 (upstream shipped it as a separate FP32 `mtp.safetensors`).
45
+ - Tokenizer, processor configs, chat template, generation config and `modeling_sensenova_loop.py` are byte-identical
46
+ to the pinned upstream revision (see `SHA256SUMS`).
47
+ - `runtime/` contains SGLang source files from the open SGLang PR
48
+ [#47281](https://github.com/sgl-project/sglang/pull/47281) (Apache-2.0, sgl-project) plus two small patches of ours
49
+ (`runtime/patches/`), needed to serve this checkpoint.
50
+
51
+ ## Architecture-specific handling
52
+ - **Looped block (layers 4–27 run twice, shared weights):** calibration ran through the real two-pass forward; every
53
+ looped layer was observed on both visits for every sample, so each shared quantizer's amax is the max over both
54
+ visits. One physical copy of each layer is stored (32 layers, not 56); per-visit KV/recurrent state remains
55
+ independent in the runtime.
56
+ - **mHC routers** (`mhc_attn`, `mhc_ffn`): FP32 parameters and FP32 routing math (Sinkhorn, 20 iterations), untouched.
57
+ - No scale folding / smoothing / graph transforms were applied (plain max calibration), so nothing assumes a single visit.
58
+
59
+ ## Precision exclusions
60
+ | Module group | Kept in | Reason |
61
+ | --- | --- | --- |
62
+ | Vision encoder + merger (`model.visual*`) | BF16 | fragile, small share of compute |
63
+ | `lm_head`, `embed_tokens` | BF16 | output/embedding sensitivity |
64
+ | mHC routing (`mhc_attn`, `mhc_ffn`, 96 tensors) | FP32 | required by the architecture |
65
+ | Gated-DeltaNet `in_proj_a`, `in_proj_b`, `conv1d`, `A_log`, `dt_bias`, norms | BF16 / FP32 | recurrent-state gates, tiny |
66
+ | All RMSNorms | BF16 | not linear |
67
+ | MTP head (`mtp.*`, 15 tensors) | BF16 | draft head; keeps MTP acceptance |
68
+ | **Quantized (NVFP4):** full-attention `q/k/v/o_proj`, GDN `in_proj_qkv`, `in_proj_z`, `out_proj`, MLP `gate/up/down_proj` in all 32 layers (200 linears) | NVFP4 | |
69
+
70
+ Parameters quantized: **73.4 %** of the backbone loaded by Transformers (6.91 B of 9.41 B), 71.6 % including the MTP
71
+ head. Checkpoint size: **9.38 GB** (upstream: 38.1 GB FP32 on disk ≈ 19.3 GB as BF16). Weight memory in SGLang:
72
+ 8.8 GB (vs 17.5 GiB BF16 weights in Transformers).
73
+
74
+ ## Calibration data
75
+ 256 chat-formatted examples (`calib_v1`, sha256 `9a4d2112ef71820a…`, see `quantization/calib_v1.jsonl.sha256`), rendered
76
+ with the model's own chat template/processor: 98,458 tokens incl. 39 images. Calibration data is not redistributed.
77
+
78
+ | Share | Source | Split / revision | Licence |
79
+ | --- | --- | --- | --- |
80
+ | 30 % chat (en/zh/es/ca) | OpenAssistant/oasst2 | train @ 179dd21f | Apache-2.0 |
81
+ | 20 % code | google-research-datasets/mbpp | full/train @ 4bb6404f | CC-BY-4.0 |
82
+ | 20 % math (half with `<think>` traces) | openai/gsm8k | main/train @ 740312ad | MIT |
83
+ | 15 % tool-calling trajectories | glaiveai/glaive-function-calling-v2 | @ e7f4b645 | Apache-2.0 |
84
+ | 15 % image QA | lmms-lab/textvqa | train shard 0 @ 9c0699cd | CC-BY-4.0 (images: OpenImages, CC-BY-2.0) |
85
+
86
+ Disjoint from all evaluation items (different splits/datasets; 13-gram overlap check found only shared tool-schema boilerplate).
87
+
88
+ ## Quality vs BF16 reference (same SGLang build and flags, greedy, thinking off)
89
+ Small fixed suite (300 items; data hashes in `evaluation/eval_data_MANIFEST.json`). Gates were declared before
90
+ any NVFP4 result was seen (`evaluation/QUALITY_GATES.md`). Paired-bootstrap 95 % CI for the delta. These are
91
+ **subsets**, not full-benchmark scores; differences inside the CI are "not detectable at this n", not "lossless".
92
+
93
+ | Task | n | BF16 | NVFP4 | Δ (pp) [95 % CI] |
94
+ | --- | --- | --- | --- | --- |
95
+ | IFEval subset (prompt-level strict, re-implemented checker) | 60 | 78.3 | 75.0 | −3.3 [−13.3, +6.7] |
96
+ | GSM8K test subset | 80 | 92.5 | 95.0 | +2.5 [−2.5, +7.5] |
97
+ | MMLU-Pro test subset | 60 | 65.0 | 68.3 | +3.3 [−6.7, +13.3] |
98
+ | HumanEval subset (pass@1) | 40 | 82.5 | 80.0 | −2.5 [−7.5, 0.0] |
99
+ | Function calling (20 hand-written BFCL-style cases) | 20 | 95.0 | 95.0 | 0.0 [−15, +15] |
100
+ | TextVQA val subset | 40 | 90.0 | 90.0 | 0.0 [−7.5, +7.5] |
101
+ | **All (micro)** | **300** | **82.7** | **83.0** | **+0.3 [−3.0, +4.0]** |
102
+
103
+ Tool calls: 100 % well-formed for both. 0 request errors. Truncations at the max-token limit: 14 (NVFP4) vs 19 (BF16).
104
+ With MTP enabled the NVFP4 model scored GSM8K 96.3 % (n=80), HumanEval 80.0 % (n=40), tools 90 % (n=20); MTP is
105
+ lossless in principle at temperature 0, small differences come from batch-dependent numerics.
106
+
107
+ ## Performance (DGX Spark GB10, SGLang, same flags for all rows)
108
+ Prompt 1,051 tokens, exactly 256 output tokens (`ignore_eos`), greedy, unique prompts (no prefix-cache hits),
109
+ `--mem-fraction-static 0.45`, CUDA graphs on. No MTP rows: 16 requests per level; MTP rows: 8 requests per level.
110
+ Aggregate output tokens/s (per-request decode tok/s p50 in parentheses):
111
+
112
+ | Concurrency | BF16 | NVFP4 W4A4 | BF16 + MTP | **NVFP4 W4A4 + MTP** | NVFP4 W4A16 (Marlin) |
113
+ | --- | --- | --- | --- | --- | --- |
114
+ | 1 | 5.75 (5.8) | 9.39 (9.6) | 7.51 (7.6) | **12.21 (12.6)** | 10.98 (11.4) |
115
+ | 2 | 11.44 | 17.61 | 14.77 | **24.79** | – |
116
+ | 4 | 21.86 | 33.48 | 26.81 | **42.51** | – |
117
+ | 8 | 39.93 (5.5) | 64.79 (9.3) | 45.70 (7.0) | **64.43 (12.2)** | 58.17 (8.8) |
118
+
119
+ TTFT p50 at c=1: 0.75 s BF16 → 0.58 s NVFP4. 0 errors / OOMs at any level. MTP mean accepted length (3 draft
120
+ steps): **2.93 NVFP4 vs 2.95 BF16** on the eval tasks; on the synthetic perf prompts ≈2.0, which is why MTP gives
121
+ no extra aggregate throughput at c=8 there. KV-cache capacity at the same memory budget: 407 k tokens (NVFP4) vs
122
+ 308 k (BF16).
123
+
124
+ **Kernel evidence (native FP4):** a torch-profiler trace of NVFP4 serving shows the quantized linears executing
125
+ `cutlass3x_sm120_bstensorop_…gemm_block_scaled_ue4m3xe2m1_ue4m3xe2m1…` (block-scaled FP4×FP4 tensor-core GEMM,
126
+ dispatched via the FlashInfer cuDNN backend) with activations quantized by `quantize_with_block_size` — i.e. true
127
+ W4A4, not dequantize-to-BF16 (`evaluation/kernel_summary.txt`). The W4A16 column uses the same checkpoint with
128
+ Marlin weight-only kernels; it is faster for single-stream decode on GB10 but slower under concurrency.
129
+
130
+ ## Hardware & software requirements
131
+ - NVIDIA Blackwell GPU with FP4 tensor cores. **Tested only on DGX Spark (GB10, SM121)**. Weights need ≈9 GB plus
132
+ KV/recurrent-state memory.
133
+ - Serving: SGLang image `lmsysorg/sglang:nightly-dev-cu13-20261029-c10e0e17`
134
+ (`sha256:78142f9f4b7fb3ee27f59065615dc7bb263f643ed256c9299c3861086ade4d47`) with the files in `runtime/overlay`
135
+ (SGLang PR #47281 @ `336c54ac1e721197806d94b6929f567446a59693` + our patches) mounted over the image.
136
+ As of 2026-10-30 we found no engine supporting `sensenova_loop` + NVFP4 out of the box (checked: SGLang main,
137
+ LightLLM main — model supported but no NVFP4, vLLM 0.27.1 — no `sensenova_loop`).
138
+ - Plain Transformers (`trust_remote_code=True`) loads the original BF16 model but **cannot** execute this packed NVFP4
139
+ checkpoint or MTP; use the SGLang runtime above.
140
+
141
+ ## Usage
142
+ ```bash
143
+ # from the repository root (Docker + NVIDIA container runtime required)
144
+ runtime/serve_sglang.sh . # NVFP4 W4A4 + MTP (NEXTN, 3 steps), 127.0.0.1:30080
145
+ curl -s http://127.0.0.1:30080/v1/models
146
+ python3 runtime/probe.py http://127.0.0.1:30080/v1 sensenova-loop-9b-nvfp4 [image.jpg] # text / thinking / tool / image smoke test
147
+ # options: --no-mtp ; W4A16: SGLANG_DISABLE_SILU_FP4_QUANT_FUSION=1 runtime/serve_sglang.sh . --fp4-gemm-backend marlin
148
+ ```
149
+ OpenAI-compatible API; thinking is toggled with `chat_template_kwargs: {"enable_thinking": false|true}`; tools use the
150
+ `qwen3_coder` parser (the model's XML `<tool_call><function=…>` format).
151
+
152
+ ## Limitations
153
+ - Upstream is a *Preview* model; all upstream limitations and intended-use terms apply.
154
+ - The serving path depends on an **unmerged** SGLang PR plus our patches, pinned to one nightly image; it was tested
155
+ only on GB10. Pipeline/tensor parallelism and other GPUs were not tested.
156
+ - Evaluation is small (300 items, thinking off, contexts ≤ ~3 K tokens). Long context (> 32 K), video input, thinking-mode
157
+ quality at scale and non-English quality were not evaluated. Only greedy decoding was benchmarked.
158
+ - The mHC Sinkhorn routing runs as many small FP32 PyTorch kernels and dominates a large share of GPU time in both BF16 and
159
+ NVFP4; a fused kernel would likely improve speed further (not attempted).
160
+ - In this SGLang build, `--fp4-gemm-backend marlin` crashes unless `SGLANG_DISABLE_SILU_FP4_QUANT_FUSION=1` is set
161
+ (upstream bug: the fused SiLU+FP4-quant path is enabled regardless of backend).
162
+ - Quantization may change behaviour on inputs unlike the calibration mixture.
163
+
164
+ ## Files
165
+ - `model.safetensors`, `config.json`, `hf_quant_config.json` — NVFP4 checkpoint (+ BF16 MTP head, FP32 mHC).
166
+ - Upstream (unchanged): `tokenizer*.json`, `chat_template.jinja`, `*processor_config.json`, `generation_config.json`, `modeling_sensenova_loop.py`.
167
+ - `runtime/` — tested launcher, SGLang overlay and patches, probe script, compatibility notes.
168
+ - `quantization/` — quantization/verification scripts, stats, structural verification report, environment lock, calibration manifest.
169
+ - `evaluation/` — quality gates, eval summaries, paired comparison, perf JSONs, kernel trace summary, reference checks.
170
+ - `SHA256SUMS` — checksums of every file.
171
+
172
+ ## Licence
173
+ Apache-2.0, inherited from the base model (`LICENSE`, verbatim from `OpenSenseNova/SenseNova-Loop` @ `a521b0af`; the
174
+ upstream repository has no NOTICE file). SGLang files in `runtime/overlay` are Apache-2.0 (sgl-project).
175
+
176
+ ## Citation
177
+ Please cite the original model:
178
+ ```bibtex
179
+ @misc{sensenova2026loop,
180
+ title = {{SenseNova-Loop-9B-Preview}},
181
+ author = {{SenseNova}},
182
+ year = {2026},
183
+ howpublished = {Hugging Face model repository},
184
+ url = {https://huggingface.co/sensenova/SenseNova-Loop-9B-Preview}
185
+ }
186
+ ```
SHA256SUMS ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ a4aee8afcf2e0711942cf848899be66016f8d14a889ff9ede07bca099c28f715 ./chat_template.jinja
2
+ 5bec5643138a09f2917a0b7471a673baecfbf85f59af49cc048f4318f32e7a3d ./config.json
3
+ 021b420e4757b37909200d1cc52bf308116461735a6b50c67df652267f54c587 ./evaluation/compare_vs_ref.json
4
+ 5d01579bee6c0863addc3236392b4d88c8d7a7d3cf66b62ae65db1430c0110a3 ./evaluation/eval_data_MANIFEST.json
5
+ 6df2bf0438b64d1353c1737e3659c86bce501dc188de9ce448492895e8b5ae63 ./evaluation/harness/build_eval_data.py
6
+ e2818e8c8ff581d65ac8a4dbfc07b58eaea97546dfd63cc3f8f31faaf8ed0f22 ./evaluation/harness/common.py
7
+ 0423139c0573e09181f065dbdb273b123aa4a966a5ec57a3643b2fe4f8f43b92 ./evaluation/harness/compare.py
8
+ 535255ccff9b6032494d918331dce7d7bbd5a455771143afeaf7977458b52bf4 ./evaluation/harness/ifeval_lite.py
9
+ c0e6d611823b672513730fae4f83d6e72cb0927202a0df1112176465d316731f ./evaluation/harness/mock_server.py
10
+ 8909f46e7c6383cce45dbad9c64d78035316031b6f7d99be664ae8b7c3d3577a ./evaluation/harness/perf_bench.py
11
+ d2b24fe51830bf6ed99af01c66ed1b861a50eff88d130fac3f9369e6865837cd ./evaluation/harness/requirements.lock
12
+ 49f85f32fb01f029182491d5dd803c26c37814e9ecfa283f37d705543242bc3d ./evaluation/harness/run_eval.py
13
+ fe25e6a9d5432922ff4509a9a80e7b996072ee2091353840a6d0fdf764d8daef ./evaluation/harness/tasks.py
14
+ 8c28fad23d10bfa56897a6c0d306b0a474cb6bc2ac7a08355682bd697a2dcd3f ./evaluation/harness/test_harness.py
15
+ ec6899745f66eaf5eb92975341bd44b8c6d5ede2116ea6d0d56d8ba008ae7702 ./evaluation/harness/tool_cases.py
16
+ 90dbf7789104b8763f8980f564d8da2c6d033a2207707a386dba8756cd8d2063 ./evaluation/hf_bf16_checks.json
17
+ a1a5de6483f0d8c2d759b9c60cb86da4794a4036c6101bba104cc1d5dba63dfb ./evaluation/kernel_summary.txt
18
+ ce49434ad0c1806bcf01a57957add6fb2ca67f1aa6a61394ae248a75d41b7c80 ./evaluation/nvfp4_v1_mtp_release_probe.json
19
+ 4cfff7659e057c02133576124dd0dd65b28a8d6869d1b460d68055dc01a2454a ./evaluation/nvfp4_v1_probe.json
20
+ a588317f6ba1071ab5f67a850e0ced977dfbf3f3dd0b34ea2d5ac54767947c17 ./evaluation/perf_sglang_bf16.json
21
+ 7f9aa0a53f019234a256057b0f759695cf0ac266ac033e3e02b84bf71ffcec41 ./evaluation/perf_sglang_bf16_mtp.json
22
+ 8a115863344c244470a3a18959e89c914aa852602e75cb365554d62adfd66c9f ./evaluation/perf_sglang_nvfp4_v1.json
23
+ d68a9f7a151718b65b6782ea48f4db0aa60f068ab8636eb51f2f2935a61b0058 ./evaluation/perf_sglang_nvfp4_v1_marlin_w4a16.json
24
+ 546730231cdf739cf84f0d92768c2a31378c165b3e48a1840117b9a2faa5035a ./evaluation/perf_sglang_nvfp4_v1_mtp.json
25
+ ccdb9203010fc0534d37b0b475d85f088f5e62568d3cbd76229af4d8e50ffb28 ./evaluation/QUALITY_GATES.md
26
+ 8f50277ef8a232dc4abab998fa80ffdcc46c13cbebea1d9328e93878af035157 ./evaluation/sglang_bf16_mtp_probe.json
27
+ 92c0708bb0d27772bedefc3d5f458b742c48325025af5a1269535de6dca2d914 ./evaluation/sglang_bf16_probe.json
28
+ ec88f909edcbf225936bbeac88ede9312520593eb53db976d5079d9dd6c45ed1 ./evaluation/summary_sglang_bf16.json
29
+ 58cd6bbadfc9fc070a784e65810414a24919084e4570e1ff7680e8a00f14504b ./evaluation/summary_sglang_bf16_mtp.json
30
+ c6d75330180b2d1bd5f2dda574afeee8844e18e44c46ba0f3a1128eb052d2326 ./evaluation/summary_sglang_nvfp4_v1.json
31
+ aff7bcc52ad3e83e6394a8d31e733d8b5725d213836beb98824c397e726c66c6 ./evaluation/summary_sglang_nvfp4_v1_marlin_w4a16_gsm8k.json
32
+ 944d3ce06cffbfb7a8e10c1af186aaa62a196e6b96cd8c3a1e6800e5d998fa70 ./evaluation/summary_sglang_nvfp4_v1_mtp.json
33
+ f288bb4281580138395c6acc7b3adb62f45c21530bc0708cb2cf9edf093f6a4f ./generation_config.json
34
+ 8951a99119cdcd06c699d91ccc5db0a40b595961041a5ec2e6103b88112757e8 ./.gitattributes
35
+ b197af747a1f072419a8e7e2db3252d9aeea84d21f65db37f748430ab7f83bfd ./hf_quant_config.json
36
+ c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4 ./LICENSE
37
+ 26e11d3522aa730c0ba031340ce42632bc2834da8cc0b6804006bb207d979a5b ./modeling_sensenova_loop.py
38
+ 1e420ea69111b7bb3c7c529cdbbb4ff4e6ebb531c0cd2be5c634cd88cd40eaca ./model.safetensors
39
+ 3a159dfec9978a186a72ba085e0ad6a050f3968d8b364218d7bd13f5c89381f2 ./preprocessor_config.json
40
+ d89ef49ce9cd37fbf510158e13c1ef063d9286411c1ec9049932dbe0487143b1 ./processor_config.json
41
+ df49d802cffe959b3176eed9abee2277d3e4291cdaf2d9abf8430ee17aeba700 ./quantization/10_reference_checks.py
42
+ 67d6edacb11e519afb6d81adb4c559930b17ce9a8adeed0e7c8c2043d8383337 ./quantization/20_quantize.py
43
+ 27dcbf33dfb7e83cace92cc8221e20d2ead29d4090bf15c9348e181b6bb5ba47 ./quantization/30_verify_export.py
44
+ dc54da6f2e37136c3172779d86ae0134a353942f341eaa8934d3000a01f34682 ./quantization/build_calib.py
45
+ 04c164fb57e8125eed5d624cefa806a61bc28cb929aca6406d2923ea43f11825 ./quantization/calib_v1.jsonl.sha256
46
+ 3a2015bbf02262d97e0de28f4fa2b3e73d43f58635a507a96788bf7b587236d8 ./quantization/calib_v1.meta.json
47
+ 4d71bc0045ee80c45f5eef89e5e721e064e2505020b327942501b017daf0fdf8 ./quantization/quantization_stats.json
48
+ 723419a376e5f2339f4f0d4f9376b16ea2d5359e54659ccb85c2bb2d5c3028fd ./quantization/requirements-quant.lock
49
+ be632c83213d44b4a18f367c59fcb55f5d0d738b8c67f64ccc61df3c6a9fb598 ./quantization/verify.json
50
+ 9d1df67a6eb124431baf4bfbd9eb9b30b00d8f9a8116dec05c847a39036ba8a3 ./README.md
51
+ 1b95e589cd65a97a97fae93cdeec07fcadbf0ff204be746fe20db32550a2c248 ./runtime/COMPAT_NOTES.md
52
+ e00d2fafe780fcf4c04ab52b040e43ec7143c7bd6e070637563f2724b59888d1 ./runtime/overlay/python/sglang/srt/configs/__init__.py
53
+ bdc7fa2055b799ce07441a5d499fafa554b7b797d4c4a24fd251b846b51260fd ./runtime/overlay/python/sglang/srt/configs/model_config.py
54
+ eb6df8f37ac06e6f439dd24f687e5bf1078599ade28acaa20d8799bf77c7e057 ./runtime/overlay/python/sglang/srt/configs/sensenova_loop.py
55
+ b3b4959f2336d1afdf4f0353a0a0eecfc38495226912022dd862489cbb532c98 ./runtime/overlay/python/sglang/srt/layers/rotary_embedding/mrope_rope_index.py
56
+ 0883bffeff0519db0fb8035b3260eca57c48ebe00e42323fb75cb7f455c1ddad ./runtime/overlay/python/sglang/srt/models/sensenova_loop.py
57
+ 7fc07bd18b974aa8f54013f2bb39536a65d32ad0cc1a1df2d239ead0dc136754 ./runtime/overlay/python/sglang/srt/multimodal/processors/qwen_vl.py
58
+ 68e1076237ed67adfa06530139d79df0783a2519620443a58cce94c5143d56c4 ./runtime/overlay/python/sglang/srt/utils/hf_transformers/common.py
59
+ d93e6342f9487567a7847443eb47b41701b4510474cd5664c8dc51b37eb6f6fd ./runtime/patches/model_config_mtp_draft.patch
60
+ 029fa25bb4ea5a16c9fce8de412e7ea6481fc540508c63bdd629026b9a56f926 ./runtime/patches/sensenova_loop_quant.patch
61
+ 7b9fd04a5e756a982cac292d314ef2ca84dca0517a4abb4f8a4c009745fd0267 ./runtime/probe.py
62
+ be2e56aca0bdbc48bab616db27b37ab36a170b2a3a35534e8be25b291508e54a ./runtime/serve_sglang.sh
63
+ 792fa3f0cb88b111e54ef3134c873531008c4df471d108da17903426e308aa7b ./tokenizer_config.json
64
+ 06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523 ./tokenizer.json
65
+ bd96830cce1312679aa1f2ea43ed17da20e8ebbcea2398cfa64e2d275a96fdd7 ./video_preprocessor_config.json
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 false %}
150
+ {{- '<think>\n\n</think>\n\n' }}
151
+ {%- else %}
152
+ {{- '<think>\n' }}
153
+ {%- endif %}
154
+ {%- endif %}
config.json ADDED
@@ -0,0 +1,261 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "SenseNovaLoopForConditionalGeneration"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "modeling_sensenova_loop.SenseNovaLoopConfig",
7
+ "AutoModel": "modeling_sensenova_loop.SenseNovaLoopModel",
8
+ "AutoModelForCausalLM": "modeling_sensenova_loop.SenseNovaLoopForConditionalGeneration",
9
+ "AutoModelForImageTextToText": "modeling_sensenova_loop.SenseNovaLoopForConditionalGeneration"
10
+ },
11
+ "bos_token_id": null,
12
+ "dtype": "bfloat16",
13
+ "eos_token_id": 248046,
14
+ "hidden_size": 4096,
15
+ "image_token_id": 248056,
16
+ "loop_settings": {
17
+ "counts": [
18
+ 2
19
+ ],
20
+ "end": 28,
21
+ "eval_count": 2,
22
+ "start": 4
23
+ },
24
+ "mhc": {
25
+ "end": 28,
26
+ "fixed_identity": false,
27
+ "iterations": 20,
28
+ "pre_scale": 1.0,
29
+ "start": 4,
30
+ "streams": 2
31
+ },
32
+ "model_name": "SenseNova-Loop-9B-Preview",
33
+ "model_type": "sensenova_loop",
34
+ "pad_token_id": 248044,
35
+ "patch_conv3d": true,
36
+ "text_config": {
37
+ "attention_bias": false,
38
+ "attention_dropout": 0.0,
39
+ "attn_output_gate": true,
40
+ "bos_token_id": null,
41
+ "dtype": "bfloat16",
42
+ "eos_token_id": 248044,
43
+ "full_attention_interval": 4,
44
+ "head_dim": 256,
45
+ "hidden_act": "silu",
46
+ "hidden_size": 4096,
47
+ "initializer_range": 0.02,
48
+ "intermediate_size": 12288,
49
+ "layer_types": [
50
+ "linear_attention",
51
+ "linear_attention",
52
+ "linear_attention",
53
+ "full_attention",
54
+ "linear_attention",
55
+ "linear_attention",
56
+ "linear_attention",
57
+ "full_attention",
58
+ "linear_attention",
59
+ "linear_attention",
60
+ "linear_attention",
61
+ "full_attention",
62
+ "linear_attention",
63
+ "linear_attention",
64
+ "linear_attention",
65
+ "full_attention",
66
+ "linear_attention",
67
+ "linear_attention",
68
+ "linear_attention",
69
+ "full_attention",
70
+ "linear_attention",
71
+ "linear_attention",
72
+ "linear_attention",
73
+ "full_attention",
74
+ "linear_attention",
75
+ "linear_attention",
76
+ "linear_attention",
77
+ "full_attention",
78
+ "linear_attention",
79
+ "linear_attention",
80
+ "linear_attention",
81
+ "full_attention"
82
+ ],
83
+ "linear_conv_kernel_dim": 4,
84
+ "linear_key_head_dim": 128,
85
+ "linear_num_key_heads": 16,
86
+ "linear_num_value_heads": 32,
87
+ "linear_value_head_dim": 128,
88
+ "mamba_ssm_dtype": "float32",
89
+ "max_position_embeddings": 262144,
90
+ "mlp_only_layers": [],
91
+ "model_type": "qwen3_5_text",
92
+ "mtp_num_hidden_layers": 1,
93
+ "mtp_use_dedicated_embeddings": false,
94
+ "num_attention_heads": 16,
95
+ "num_hidden_layers": 32,
96
+ "num_key_value_heads": 4,
97
+ "pad_token_id": 248044,
98
+ "partial_rotary_factor": 0.25,
99
+ "rms_norm_eps": 1e-06,
100
+ "rope_parameters": {
101
+ "mrope_interleaved": true,
102
+ "mrope_section": [
103
+ 11,
104
+ 11,
105
+ 10
106
+ ],
107
+ "partial_rotary_factor": 0.25,
108
+ "rope_theta": 10000000,
109
+ "rope_type": "default"
110
+ },
111
+ "tie_word_embeddings": false,
112
+ "use_cache": true,
113
+ "vocab_size": 248320
114
+ },
115
+ "tie_word_embeddings": false,
116
+ "transformers_version": "5.18.0",
117
+ "video_token_id": 248057,
118
+ "vision_config": {
119
+ "deepstack_visual_indexes": [],
120
+ "depth": 27,
121
+ "dtype": "bfloat16",
122
+ "hidden_act": "gelu_pytorch_tanh",
123
+ "hidden_size": 1152,
124
+ "in_channels": 3,
125
+ "initializer_range": 0.02,
126
+ "intermediate_size": 4304,
127
+ "model_type": "qwen3_5_vision",
128
+ "num_heads": 16,
129
+ "num_position_embeddings": 2304,
130
+ "out_hidden_size": 4096,
131
+ "patch_size": 16,
132
+ "rope_parameters": {
133
+ "rope_theta": 10000.0,
134
+ "rope_type": "axial"
135
+ },
136
+ "spatial_merge_size": 2,
137
+ "temporal_patch_size": 2
138
+ },
139
+ "vision_end_token_id": 248054,
140
+ "vision_start_token_id": 248053,
141
+ "quantization_config": {
142
+ "config_groups": {
143
+ "group_0": {
144
+ "input_activations": {
145
+ "dynamic": false,
146
+ "num_bits": 4,
147
+ "type": "float",
148
+ "group_size": 16
149
+ },
150
+ "weights": {
151
+ "dynamic": false,
152
+ "num_bits": 4,
153
+ "type": "float",
154
+ "group_size": 16
155
+ },
156
+ "targets": [
157
+ "Linear"
158
+ ]
159
+ }
160
+ },
161
+ "ignore": [
162
+ "lm_head",
163
+ "model.language_model.embed_tokens",
164
+ "model.language_model.layers.0.linear_attn.conv1d",
165
+ "model.language_model.layers.0.linear_attn.in_proj_a",
166
+ "model.language_model.layers.0.linear_attn.in_proj_b",
167
+ "model.language_model.layers.1.linear_attn.conv1d",
168
+ "model.language_model.layers.1.linear_attn.in_proj_a",
169
+ "model.language_model.layers.1.linear_attn.in_proj_b",
170
+ "model.language_model.layers.10.linear_attn.conv1d",
171
+ "model.language_model.layers.10.linear_attn.in_proj_a",
172
+ "model.language_model.layers.10.linear_attn.in_proj_b",
173
+ "model.language_model.layers.12.linear_attn.conv1d",
174
+ "model.language_model.layers.12.linear_attn.in_proj_a",
175
+ "model.language_model.layers.12.linear_attn.in_proj_b",
176
+ "model.language_model.layers.13.linear_attn.conv1d",
177
+ "model.language_model.layers.13.linear_attn.in_proj_a",
178
+ "model.language_model.layers.13.linear_attn.in_proj_b",
179
+ "model.language_model.layers.14.linear_attn.conv1d",
180
+ "model.language_model.layers.14.linear_attn.in_proj_a",
181
+ "model.language_model.layers.14.linear_attn.in_proj_b",
182
+ "model.language_model.layers.16.linear_attn.conv1d",
183
+ "model.language_model.layers.16.linear_attn.in_proj_a",
184
+ "model.language_model.layers.16.linear_attn.in_proj_b",
185
+ "model.language_model.layers.17.linear_attn.conv1d",
186
+ "model.language_model.layers.17.linear_attn.in_proj_a",
187
+ "model.language_model.layers.17.linear_attn.in_proj_b",
188
+ "model.language_model.layers.18.linear_attn.conv1d",
189
+ "model.language_model.layers.18.linear_attn.in_proj_a",
190
+ "model.language_model.layers.18.linear_attn.in_proj_b",
191
+ "model.language_model.layers.2.linear_attn.conv1d",
192
+ "model.language_model.layers.2.linear_attn.in_proj_a",
193
+ "model.language_model.layers.2.linear_attn.in_proj_b",
194
+ "model.language_model.layers.20.linear_attn.conv1d",
195
+ "model.language_model.layers.20.linear_attn.in_proj_a",
196
+ "model.language_model.layers.20.linear_attn.in_proj_b",
197
+ "model.language_model.layers.21.linear_attn.conv1d",
198
+ "model.language_model.layers.21.linear_attn.in_proj_a",
199
+ "model.language_model.layers.21.linear_attn.in_proj_b",
200
+ "model.language_model.layers.22.linear_attn.conv1d",
201
+ "model.language_model.layers.22.linear_attn.in_proj_a",
202
+ "model.language_model.layers.22.linear_attn.in_proj_b",
203
+ "model.language_model.layers.24.linear_attn.conv1d",
204
+ "model.language_model.layers.24.linear_attn.in_proj_a",
205
+ "model.language_model.layers.24.linear_attn.in_proj_b",
206
+ "model.language_model.layers.25.linear_attn.conv1d",
207
+ "model.language_model.layers.25.linear_attn.in_proj_a",
208
+ "model.language_model.layers.25.linear_attn.in_proj_b",
209
+ "model.language_model.layers.26.linear_attn.conv1d",
210
+ "model.language_model.layers.26.linear_attn.in_proj_a",
211
+ "model.language_model.layers.26.linear_attn.in_proj_b",
212
+ "model.language_model.layers.28.linear_attn.conv1d",
213
+ "model.language_model.layers.28.linear_attn.in_proj_a",
214
+ "model.language_model.layers.28.linear_attn.in_proj_b",
215
+ "model.language_model.layers.29.linear_attn.conv1d",
216
+ "model.language_model.layers.29.linear_attn.in_proj_a",
217
+ "model.language_model.layers.29.linear_attn.in_proj_b",
218
+ "model.language_model.layers.30.linear_attn.conv1d",
219
+ "model.language_model.layers.30.linear_attn.in_proj_a",
220
+ "model.language_model.layers.30.linear_attn.in_proj_b",
221
+ "model.language_model.layers.4.linear_attn.conv1d",
222
+ "model.language_model.layers.4.linear_attn.in_proj_a",
223
+ "model.language_model.layers.4.linear_attn.in_proj_b",
224
+ "model.language_model.layers.5.linear_attn.conv1d",
225
+ "model.language_model.layers.5.linear_attn.in_proj_a",
226
+ "model.language_model.layers.5.linear_attn.in_proj_b",
227
+ "model.language_model.layers.6.linear_attn.conv1d",
228
+ "model.language_model.layers.6.linear_attn.in_proj_a",
229
+ "model.language_model.layers.6.linear_attn.in_proj_b",
230
+ "model.language_model.layers.8.linear_attn.conv1d",
231
+ "model.language_model.layers.8.linear_attn.in_proj_a",
232
+ "model.language_model.layers.8.linear_attn.in_proj_b",
233
+ "model.language_model.layers.9.linear_attn.conv1d",
234
+ "model.language_model.layers.9.linear_attn.in_proj_a",
235
+ "model.language_model.layers.9.linear_attn.in_proj_b",
236
+ "model.visual*",
237
+ "mtp.fc",
238
+ "mtp.layers.0.input_layernorm",
239
+ "mtp.layers.0.mlp.down_proj",
240
+ "mtp.layers.0.mlp.gate_proj",
241
+ "mtp.layers.0.mlp.up_proj",
242
+ "mtp.layers.0.post_attention_layernorm",
243
+ "mtp.layers.0.self_attn.k_norm",
244
+ "mtp.layers.0.self_attn.k_proj",
245
+ "mtp.layers.0.self_attn.o_proj",
246
+ "mtp.layers.0.self_attn.q_norm",
247
+ "mtp.layers.0.self_attn.q_proj",
248
+ "mtp.layers.0.self_attn.v_proj",
249
+ "mtp.norm",
250
+ "mtp.pre_fc_norm_embedding",
251
+ "mtp.pre_fc_norm_hidden"
252
+ ],
253
+ "quant_algo": "NVFP4",
254
+ "group_size": 16,
255
+ "producer": {
256
+ "name": "modelopt",
257
+ "version": "0.48.0rc1"
258
+ },
259
+ "quant_method": "modelopt"
260
+ }
261
+ }
evaluation/QUALITY_GATES.md ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quality / performance gates (declared 2026-10-30 ~15:25 PDT, BEFORE any NVFP4 eval result was seen)
2
+
3
+ Eval: eval/ (300 fixed items: IFEval 60, GSM8K 80, MMLU-Pro 60, HumanEval 40, tools 20, TextVQA 40),
4
+ temperature 0, thinking off, same SGLang build/flags for reference (BF16 backbone, FP32 mHC) and candidate.
5
+
6
+ Quality (candidate vs BF16 reference, paired):
7
+ - G1 pooled accuracy (all 300 items, item-weighted): delta point estimate >= -3.0 pp AND paired-bootstrap 95% CI lower bound >= -7.0 pp.
8
+ - G2 no single task point-estimate drop > 8 pp (n=20-80 per task, so this is a coarse tripwire, not a significance test).
9
+ - G3 tool calling: 0 malformed tool calls beyond the reference's count; image task (TextVQA) drop <= 8 pp.
10
+ - G4 stability: 0 request errors, all outputs finite/non-degenerate (no repetition collapse in truncation counts beyond ref + 3).
11
+ - G5 MTP: mean accepted length (spec) with NVFP4 target >= 85% of the BF16-target value under identical spec settings.
12
+ Performance (same engine, same flags, fixed 1024-in/256-out, concurrency 1/2/4/8):
13
+ - P1 0 errors/OOM at c=1..8. P2 report decode tok/s, TTFT, aggregate throughput honestly; speedup is reported, not gated.
14
+ If a recipe fails G1-G5, try the next recipe (max 3: decoder-all -> MLP-only -> decoder w/ first/last layers BF16).
evaluation/compare_vs_ref.json ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ref": "eval/runs/sglang_bf16",
3
+ "cand": "eval/runs/sglang_nvfp4_v1",
4
+ "tasks": {
5
+ "gsm8k": {
6
+ "n": 80,
7
+ "ref_acc": 0.925,
8
+ "cand_acc": 0.95,
9
+ "delta": 0.025,
10
+ "delta_boot95": [
11
+ -0.025,
12
+ 0.075
13
+ ],
14
+ "ref_only_correct": 1,
15
+ "cand_only_correct": 3,
16
+ "mcnemar_p": 0.625,
17
+ "ref_agreement": 0.9375,
18
+ "identical_output": 0.075,
19
+ "mean_tokens_ref": 191.5,
20
+ "mean_tokens_cand": 187.2,
21
+ "truncated_ref": 1,
22
+ "truncated_cand": 1,
23
+ "errors_cand": 0
24
+ },
25
+ "humaneval": {
26
+ "n": 40,
27
+ "ref_acc": 0.825,
28
+ "cand_acc": 0.8,
29
+ "delta": -0.025,
30
+ "delta_boot95": [
31
+ -0.075,
32
+ 0.0
33
+ ],
34
+ "ref_only_correct": 1,
35
+ "cand_only_correct": 0,
36
+ "mcnemar_p": 1.0,
37
+ "ref_agreement": 0.425,
38
+ "identical_output": 0.375,
39
+ "mean_tokens_ref": 226.6,
40
+ "mean_tokens_cand": 198.0,
41
+ "truncated_ref": 0,
42
+ "truncated_cand": 0,
43
+ "errors_cand": 0
44
+ },
45
+ "ifeval": {
46
+ "n": 60,
47
+ "ref_acc": 0.7833333333333333,
48
+ "cand_acc": 0.75,
49
+ "delta": -0.03333333333333333,
50
+ "delta_boot95": [
51
+ -0.13333333333333333,
52
+ 0.06666666666666667
53
+ ],
54
+ "ref_only_correct": 6,
55
+ "cand_only_correct": 4,
56
+ "mcnemar_p": 0.75390625,
57
+ "ref_agreement": 0.05,
58
+ "identical_output": 0.05,
59
+ "mean_tokens_ref": 318.9,
60
+ "mean_tokens_cand": 272.7,
61
+ "truncated_ref": 5,
62
+ "truncated_cand": 4,
63
+ "errors_cand": 0
64
+ },
65
+ "mmlu_pro": {
66
+ "n": 60,
67
+ "ref_acc": 0.65,
68
+ "cand_acc": 0.6833333333333333,
69
+ "delta": 0.03333333333333333,
70
+ "delta_boot95": [
71
+ -0.06666666666666667,
72
+ 0.13333333333333333
73
+ ],
74
+ "ref_only_correct": 4,
75
+ "cand_only_correct": 6,
76
+ "mcnemar_p": 0.75390625,
77
+ "ref_agreement": 0.7666666666666667,
78
+ "identical_output": 0.0,
79
+ "mean_tokens_ref": 622.1,
80
+ "mean_tokens_cand": 588.5,
81
+ "truncated_ref": 13,
82
+ "truncated_cand": 9,
83
+ "errors_cand": 0
84
+ },
85
+ "tools": {
86
+ "n": 20,
87
+ "ref_acc": 0.95,
88
+ "cand_acc": 0.95,
89
+ "delta": 0.0,
90
+ "delta_boot95": [
91
+ -0.15,
92
+ 0.15
93
+ ],
94
+ "ref_only_correct": 1,
95
+ "cand_only_correct": 1,
96
+ "mcnemar_p": 1.0,
97
+ "ref_agreement": 0.85,
98
+ "identical_output": 0.0,
99
+ "mean_tokens_ref": 64.2,
100
+ "mean_tokens_cand": 54.3,
101
+ "truncated_ref": 0,
102
+ "truncated_cand": 0,
103
+ "errors_cand": 0
104
+ },
105
+ "vqa": {
106
+ "n": 40,
107
+ "ref_acc": 0.9,
108
+ "cand_acc": 0.9,
109
+ "delta": 0.0,
110
+ "delta_boot95": [
111
+ -0.075,
112
+ 0.075
113
+ ],
114
+ "ref_only_correct": 1,
115
+ "cand_only_correct": 1,
116
+ "mcnemar_p": 1.0,
117
+ "ref_agreement": 0.925,
118
+ "identical_output": 0.925,
119
+ "mean_tokens_ref": 4.1,
120
+ "mean_tokens_cand": 4.1,
121
+ "truncated_ref": 0,
122
+ "truncated_cand": 0,
123
+ "errors_cand": 0
124
+ }
125
+ },
126
+ "overall_micro": {
127
+ "n": 300,
128
+ "ref_acc": 0.8266666666666667,
129
+ "cand_acc": 0.83,
130
+ "delta": 0.0033333333333333335,
131
+ "delta_boot95": [
132
+ -0.03,
133
+ 0.04
134
+ ],
135
+ "ref_agreement": 0.65,
136
+ "identical_output": 0.20333333333333334
137
+ }
138
+ }
evaluation/eval_data_MANIFEST.json ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "seed": 20261030,
4
+ "tasks": {
5
+ "ifeval": {
6
+ "n": 60,
7
+ "file": "ifeval_input_data.jsonl",
8
+ "sha256": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
9
+ "repo": "google/IFEval",
10
+ "rev": "966cd89545d6b6acfd7638bc708b98261ca58e84",
11
+ "split": "train (only split; = the IFEval eval set)",
12
+ "license": "Apache-2.0",
13
+ "eligible_pool": "495/541 prompts with only supported instruction ids",
14
+ "raw_file_sha256": "6a85310ca8ce15eff755aa08a3a4ff931c7e273e7515ebb3c492ea85fd8288f2"
15
+ },
16
+ "gsm8k": {
17
+ "n": 80,
18
+ "file": "main/test-00000-of-00001.parquet",
19
+ "sha256": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
20
+ "repo": "openai/gsm8k",
21
+ "rev": "740312add88f781978c0658806c59bc2815b9866",
22
+ "split": "main/test",
23
+ "license": "MIT",
24
+ "raw_file_sha256": "ee7b8da9e381df27b9e3f7758a159ab2bdaa4dbaa910546cbbc47e0cb44e4f59"
25
+ },
26
+ "mmlu_pro": {
27
+ "n": 60,
28
+ "file": "data/test-00000-of-00001.parquet",
29
+ "sha256": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
30
+ "repo": "TIGER-Lab/MMLU-Pro",
31
+ "rev": "b189ec765aa7ed75c8acfea42df31fdae71f97be",
32
+ "split": "test",
33
+ "license": "MIT",
34
+ "raw_file_sha256": "0e24a191921c2f453518a537a8b2117bd137e7714d4ef1565e9ba06c1ecb9ad8"
35
+ },
36
+ "humaneval": {
37
+ "n": 40,
38
+ "file": "openai_humaneval/test-00000-of-00001.parquet",
39
+ "sha256": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
40
+ "repo": "openai/openai_humaneval",
41
+ "rev": "7dce6050a7d6d172f3cc5c32aa97f52fa1a2e544",
42
+ "split": "test",
43
+ "license": "MIT",
44
+ "raw_file_sha256": "2f2871a15fbc95b6c683043359f4ed8e144c5a1c4f24f25f66bc51f598dfcfb6"
45
+ },
46
+ "tools": {
47
+ "n": 20,
48
+ "file": "eval/tool_cases.py",
49
+ "sha256": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
50
+ "repo": "(hand-written, eval/tool_cases.py)",
51
+ "rev": "in-repo",
52
+ "split": "n/a",
53
+ "license": "Apache-2.0 (this project); BFCL-style categories, no BFCL text"
54
+ },
55
+ "vqa": {
56
+ "n": 40,
57
+ "file": "data/validation-00000-of-00003.parquet",
58
+ "sha256": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d",
59
+ "repo": "lmms-lab/textvqa",
60
+ "rev": "9c0699cd19768ac5ab97568f6b3cbac4c0062884",
61
+ "split": "validation (shard 0 of 3, 1667 of 5000 rows)",
62
+ "license": "TextVQA annotations CC-BY-4.0; images from OpenImages (CC-BY-2.0, per-image Flickr attribution)",
63
+ "eligible_pool": "1630/1667 questions with a >=3-annotator consensus answer",
64
+ "raw_file_sha256": "491649b5b8add2c5bde13a1e36707002fcd11898699255d65e2e568ac4fbddff"
65
+ }
66
+ }
67
+ }
evaluation/harness/build_eval_data.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Select the FIXED eval items from pinned dataset revisions and write them to eval/data/.
3
+
4
+ Run once (needs HF access only if eval/data/raw is missing; see download list in DATASETS):
5
+ .venv-eval/bin/python eval/build_eval_data.py
6
+ Outputs: eval/data/<task>.jsonl, eval/data/images/*.jpg, eval/data/MANIFEST.json (with sha256s).
7
+ Re-running produces byte-identical files (fixed seeds, sorted inputs).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ import json
13
+ import os
14
+ import random
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ HERE = Path(__file__).resolve().parent
19
+ sys.path.insert(0, str(HERE))
20
+ import pyarrow.parquet as pq # noqa: E402
21
+
22
+ import ifeval_lite # noqa: E402
23
+ from common import EVAL_VERSION, write_jsonl # noqa: E402
24
+ from tool_cases import CASES # noqa: E402
25
+
26
+ DATA = HERE / "data"
27
+ RAW = DATA / "raw"
28
+ SEED = 20261030
29
+ N = {"ifeval": 60, "gsm8k": 80, "mmlu_pro": 60, "humaneval": 40, "tools": len(CASES), "vqa": 40}
30
+
31
+ DATASETS = {
32
+ "ifeval": dict(repo="google/IFEval", rev="966cd89545d6b6acfd7638bc708b98261ca58e84",
33
+ file="ifeval_input_data.jsonl", split="train (only split; = the IFEval eval set)",
34
+ license="Apache-2.0"),
35
+ "gsm8k": dict(repo="openai/gsm8k", rev="740312add88f781978c0658806c59bc2815b9866",
36
+ file="main/test-00000-of-00001.parquet", split="main/test", license="MIT"),
37
+ "mmlu_pro": dict(repo="TIGER-Lab/MMLU-Pro", rev="b189ec765aa7ed75c8acfea42df31fdae71f97be",
38
+ file="data/test-00000-of-00001.parquet", split="test", license="MIT"),
39
+ "humaneval": dict(repo="openai/openai_humaneval", rev="7dce6050a7d6d172f3cc5c32aa97f52fa1a2e544",
40
+ file="openai_humaneval/test-00000-of-00001.parquet", split="test", license="MIT"),
41
+ "tools": dict(repo="(hand-written, eval/tool_cases.py)", rev="in-repo", file="eval/tool_cases.py",
42
+ split="n/a", license="Apache-2.0 (this project); BFCL-style categories, no BFCL text"),
43
+ "vqa": dict(repo="lmms-lab/textvqa", rev="9c0699cd19768ac5ab97568f6b3cbac4c0062884",
44
+ file="data/validation-00000-of-00003.parquet",
45
+ split="validation (shard 0 of 3, 1667 of 5000 rows)",
46
+ license="TextVQA annotations CC-BY-4.0; images from OpenImages (CC-BY-2.0, per-image Flickr attribution)"),
47
+ }
48
+
49
+
50
+ def raw_path(task):
51
+ d = DATASETS[task]
52
+ return RAW / d["repo"].replace("/", "__") / d["file"]
53
+
54
+
55
+ def sha256(p):
56
+ h = hashlib.sha256()
57
+ with open(p, "rb") as f:
58
+ for chunk in iter(lambda: f.read(1 << 20), b""):
59
+ h.update(chunk)
60
+ return h.hexdigest()
61
+
62
+
63
+ def pick(rows, n, task):
64
+ rng = random.Random(f"{SEED}-{task}")
65
+ idx = sorted(rng.sample(range(len(rows)), n))
66
+ return idx
67
+
68
+
69
+ def build():
70
+ DATA.mkdir(exist_ok=True)
71
+ (DATA / "images").mkdir(exist_ok=True)
72
+ out = {}
73
+
74
+ # IFEval: restrict to prompts whose instructions are all supported by ifeval_lite
75
+ rows = [json.loads(l) for l in open(raw_path("ifeval"))]
76
+ rows.sort(key=lambda r: r["key"])
77
+ elig = [r for r in rows if all(i in ifeval_lite.SUPPORTED for i in r["instruction_id_list"])]
78
+ sel = [elig[i] for i in pick(elig, N["ifeval"], "ifeval")]
79
+ out["ifeval"] = [dict(id=f"ifeval_{r['key']}", key=r["key"], prompt=r["prompt"],
80
+ instruction_id_list=r["instruction_id_list"], kwargs=r["kwargs"]) for r in sel]
81
+ DATASETS["ifeval"]["eligible_pool"] = f"{len(elig)}/{len(rows)} prompts with only supported instruction ids"
82
+
83
+ rows = pq.read_table(raw_path("gsm8k")).to_pylist()
84
+ idx = pick(rows, N["gsm8k"], "gsm8k")
85
+ out["gsm8k"] = [dict(id=f"gsm8k_test_{i}", source_index=i, question=rows[i]["question"],
86
+ answer=rows[i]["answer"].split("####")[-1].strip().replace(",", ""),
87
+ solution=rows[i]["answer"]) for i in idx]
88
+
89
+ rows = pq.read_table(raw_path("mmlu_pro"), columns=["question_id", "question", "options", "answer",
90
+ "category", "src"]).to_pylist()
91
+ rows.sort(key=lambda r: r["question_id"])
92
+ idx = pick(rows, N["mmlu_pro"], "mmlu_pro")
93
+ out["mmlu_pro"] = [dict(id=f"mmlupro_{rows[i]['question_id']}", **rows[i]) for i in idx]
94
+
95
+ rows = pq.read_table(raw_path("humaneval")).to_pylist()
96
+ idx = pick(rows, N["humaneval"], "humaneval")
97
+ out["humaneval"] = [dict(id=rows[i]["task_id"].replace("/", "_"), **rows[i]) for i in idx]
98
+
99
+ out["tools"] = [dict(c) for c in CASES]
100
+
101
+ tbl = pq.read_table(raw_path("vqa"), columns=["question_id", "question", "answers", "image_id",
102
+ "image", "flickr_original_url"])
103
+ qids = tbl.column("question_id").to_pylist()
104
+ order = sorted(range(len(qids)), key=lambda i: qids[i])
105
+ # only questions with a consensus answer (some normalized answer given by >=3 of 10 annotators)
106
+ from collections import Counter
107
+ from tasks import vqa_norm
108
+ answers = tbl.column("answers").to_pylist()
109
+ order = [i for i in order if Counter(vqa_norm(a) for a in answers[i]).most_common(1)[0][1] >= 3]
110
+ DATASETS["vqa"]["eligible_pool"] = f"{len(order)}/{len(qids)} questions with a >=3-annotator consensus answer"
111
+ idx = [order[i] for i in pick(order, N["vqa"], "vqa")]
112
+ items = []
113
+ for i in idx:
114
+ r = tbl.slice(i, 1).to_pylist()[0]
115
+ img = r["image"]["bytes"]
116
+ ext = "png" if img[:4] == b"\x89PNG" else "jpg"
117
+ rel = f"images/textvqa_val_{r['question_id']}.{ext}"
118
+ (DATA / rel).write_bytes(img)
119
+ items.append(dict(id=f"textvqa_val_{r['question_id']}", question_id=r["question_id"],
120
+ image_id=r["image_id"], image=rel, image_sha256=hashlib.sha256(img).hexdigest(),
121
+ question=r["question"], answers=r["answers"],
122
+ image_source_url=r["flickr_original_url"]))
123
+ out["vqa"] = items
124
+
125
+ manifest = {"eval_version": EVAL_VERSION, "seed": SEED, "tasks": {}}
126
+ for task, rows in out.items():
127
+ assert len(rows) == N[task], (task, len(rows))
128
+ p = DATA / f"{task}.jsonl"
129
+ write_jsonl(p, rows)
130
+ manifest["tasks"][task] = {"n": len(rows), "file": p.name, "sha256": sha256(p), **DATASETS[task]}
131
+ if raw_path(task).exists() if task != "tools" else False:
132
+ manifest["tasks"][task]["raw_file_sha256"] = sha256(raw_path(task))
133
+ with open(DATA / "MANIFEST.json", "w") as f:
134
+ json.dump(manifest, f, indent=2)
135
+ print(json.dumps({t: v["n"] for t, v in manifest["tasks"].items()}), "total", sum(N.values()))
136
+
137
+
138
+ if __name__ == "__main__":
139
+ build()
evaluation/harness/common.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared helpers for the SenseNova-Loop NVFP4 eval harness (stdlib + requests only)."""
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import math
6
+ import re
7
+ import time
8
+ from typing import Any
9
+
10
+ EVAL_VERSION = "sn-loop-eval-v1"
11
+
12
+
13
+ # ----------------------------------------------------------------------------- stats
14
+ def wilson_ci(k: int, n: int, z: float = 1.959964) -> tuple[float, float]:
15
+ if n == 0:
16
+ return (float("nan"), float("nan"))
17
+ p = k / n
18
+ den = 1 + z * z / n
19
+ centre = (p + z * z / (2 * n)) / den
20
+ half = z * math.sqrt(p * (1 - p) / n + z * z / (4 * n * n)) / den
21
+ return (max(0.0, centre - half), min(1.0, centre + half))
22
+
23
+
24
+ # ----------------------------------------------------------------------------- text utils
25
+ THINK_RE = re.compile(r"<think>.*?</think>\s*", re.S)
26
+
27
+
28
+ def strip_think(text: str | None) -> str:
29
+ """Remove any <think>...</think> block (and a dangling '...</think>' prefix)."""
30
+ if not text:
31
+ return ""
32
+ text = THINK_RE.sub("", text)
33
+ if "</think>" in text:
34
+ text = text.split("</think>")[-1]
35
+ return text.strip()
36
+
37
+
38
+ # ----------------------------------------------------------------------------- tool-call parsing
39
+ # Chat template (Qwen3.5-style XML) emits:
40
+ # <tool_call>\n<function=NAME>\n<parameter=ARG>\nVALUE\n</parameter>\n...</function>\n</tool_call>
41
+ # String values are written raw; dict/list values as JSON (tojson); numbers/bools via |string
42
+ # (so Python-ish 'True'/'False' are possible).
43
+ TOOL_CALL_RE = re.compile(r"<tool_call>\s*<function=([^>\n]+)>(.*?)</function>\s*</tool_call>", re.S)
44
+ PARAM_RE = re.compile(r"<parameter=([^>\n]+)>\n?(.*?)\n?</parameter>", re.S)
45
+
46
+
47
+ def _coerce(value: str, schema: dict | None) -> Any:
48
+ typ = (schema or {}).get("type")
49
+ if typ == "string":
50
+ return value
51
+ v = value.strip()
52
+ if typ == "boolean" or v in ("True", "False"):
53
+ if v.lower() in ("true", "false"):
54
+ return v.lower() == "true"
55
+ try:
56
+ return json.loads(v)
57
+ except Exception:
58
+ pass
59
+ if typ in ("integer", "number"):
60
+ try:
61
+ return float(v) if "." in v else int(v)
62
+ except ValueError:
63
+ return v
64
+ return value
65
+
66
+
67
+ def parse_xml_tool_calls(text: str, tools: list[dict] | None = None) -> tuple[list[dict], dict]:
68
+ """Fallback parser for raw-text tool calls. Returns (calls, format_report)."""
69
+ schemas: dict[str, dict] = {}
70
+ for t in tools or []:
71
+ fn = t.get("function", t)
72
+ schemas[fn.get("name")] = (fn.get("parameters") or {}).get("properties") or {}
73
+ calls = []
74
+ for m in TOOL_CALL_RE.finditer(text or ""):
75
+ name = m.group(1).strip()
76
+ args = {}
77
+ for pm in PARAM_RE.finditer(m.group(2)):
78
+ pname = pm.group(1).strip()
79
+ args[pname] = _coerce(pm.group(2), schemas.get(name, {}).get(pname))
80
+ calls.append({"name": name, "arguments": args})
81
+ n_open = (text or "").count("<tool_call>")
82
+ n_close = (text or "").count("</tool_call>")
83
+ tail = (text or "").split("</tool_call>")[-1].strip() if n_close else ""
84
+ report = {
85
+ "n_tool_call_tags": n_open,
86
+ "balanced_tags": n_open == n_close,
87
+ "all_blocks_parsed": n_open == len(calls),
88
+ "suffix_after_last_call": bool(tail), # template says: NO suffix after the call
89
+ "well_formed": n_open > 0 and n_open == n_close == len(calls),
90
+ }
91
+ return calls, report
92
+
93
+
94
+ def calls_from_message(msg: dict, tools: list[dict] | None) -> tuple[list[dict], str, dict]:
95
+ """Return (calls, source, format_report). source in {'server','fallback_xml','none'}."""
96
+ tcs = msg.get("tool_calls") or []
97
+ if tcs:
98
+ calls, ok = [], True
99
+ for tc in tcs:
100
+ fn = tc.get("function", tc)
101
+ raw = fn.get("arguments")
102
+ if isinstance(raw, str):
103
+ try:
104
+ args = json.loads(raw) if raw.strip() else {}
105
+ except Exception:
106
+ args, ok = {"__unparseable__": raw}, False
107
+ else:
108
+ args = raw or {}
109
+ calls.append({"name": fn.get("name"), "arguments": args})
110
+ return calls, "server", {"well_formed": ok, "args_valid_json": ok}
111
+ content = strip_think(msg.get("content") or "")
112
+ calls, rep = parse_xml_tool_calls(content, tools)
113
+ return calls, ("fallback_xml" if calls else "none"), rep
114
+
115
+
116
+ # ----------------------------------------------------------------------------- HTTP client
117
+ def chat(session, base_url: str, model: str, messages: list, *, max_tokens: int,
118
+ tools: list | None = None, enable_thinking: bool = False, timeout: float = 600,
119
+ retries: int = 2, extra: dict | None = None) -> dict:
120
+ """One deterministic chat completion. Returns a dict with response + timing (never raises)."""
121
+ body: dict[str, Any] = {
122
+ "model": model,
123
+ "messages": messages,
124
+ "temperature": 0.0,
125
+ "top_p": 1.0,
126
+ "max_tokens": max_tokens,
127
+ "seed": 0,
128
+ "chat_template_kwargs": {"enable_thinking": enable_thinking},
129
+ }
130
+ if tools:
131
+ body["tools"] = tools
132
+ body["tool_choice"] = "auto"
133
+ if extra:
134
+ body.update(extra)
135
+ url = base_url.rstrip("/") + "/chat/completions"
136
+ last_err = None
137
+ for attempt in range(retries + 1):
138
+ t0 = time.time()
139
+ try:
140
+ r = session.post(url, json=body, timeout=timeout)
141
+ dt = time.time() - t0
142
+ if r.status_code != 200:
143
+ last_err = f"HTTP {r.status_code}: {r.text[:500]}"
144
+ if r.status_code < 500:
145
+ break
146
+ time.sleep(1 + attempt)
147
+ continue
148
+ j = r.json()
149
+ ch = j["choices"][0]
150
+ msg = ch.get("message") or {}
151
+ return {
152
+ "ok": True,
153
+ "content": msg.get("content") or "",
154
+ "reasoning_content": msg.get("reasoning_content") or msg.get("reasoning") or "",
155
+ "tool_calls": msg.get("tool_calls") or [],
156
+ "message": msg,
157
+ "finish_reason": ch.get("finish_reason"),
158
+ "usage": j.get("usage") or {},
159
+ "latency_s": round(dt, 3),
160
+ "error": None,
161
+ }
162
+ except Exception as e: # noqa: BLE001
163
+ last_err = f"{type(e).__name__}: {e}"
164
+ time.sleep(1 + attempt)
165
+ return {"ok": False, "content": "", "reasoning_content": "", "tool_calls": [], "message": {},
166
+ "finish_reason": None, "usage": {}, "latency_s": None, "error": last_err}
167
+
168
+
169
+ def read_jsonl(path) -> list[dict]:
170
+ with open(path) as f:
171
+ return [json.loads(l) for l in f if l.strip()]
172
+
173
+
174
+ def write_jsonl(path, rows) -> None:
175
+ with open(path, "w") as f:
176
+ for r in rows:
177
+ f.write(json.dumps(r, ensure_ascii=False) + "\n")
evaluation/harness/compare.py ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Paired comparison of two run_eval.py output dirs (reference vs candidate).
3
+
4
+ .venv-eval/bin/python eval/compare.py eval/runs/bf16 eval/runs/nvfp4 [--json out.json]
5
+
6
+ Per task (on the intersection of item ids):
7
+ * ref / cand accuracy, delta, paired bootstrap 95% CI of the delta (10k resamples, seed 0),
8
+ discordant counts (ref-only correct / cand-only correct) and exact McNemar p-value;
9
+ * reference agreement (label-free): fraction of items whose extracted answer == reference's;
10
+ * identical raw outputs %, mean completion tokens ref/cand, truncation counts.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import math
17
+ import random
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ HERE = Path(__file__).resolve().parent
22
+ sys.path.insert(0, str(HERE))
23
+ from common import read_jsonl # noqa: E402
24
+
25
+
26
+ def load(d):
27
+ d = Path(d)
28
+ out = {}
29
+ for p in sorted(d.glob("raw_*.jsonl")):
30
+ task = p.stem[4:]
31
+ out[task] = {r["id"]: r for r in read_jsonl(p)}
32
+ return out
33
+
34
+
35
+ def boot_ci(a, b, iters=10000, seed=0):
36
+ n = len(a)
37
+ if n == 0:
38
+ return (float("nan"), float("nan"))
39
+ rng = random.Random(seed)
40
+ diffs = [y - x for x, y in zip(a, b)]
41
+ stats = []
42
+ for _ in range(iters):
43
+ s = 0
44
+ for _ in range(n):
45
+ s += diffs[rng.randrange(n)]
46
+ stats.append(s / n)
47
+ stats.sort()
48
+ return (stats[int(0.025 * iters)], stats[int(0.975 * iters) - 1])
49
+
50
+
51
+ def mcnemar_exact(b, c):
52
+ n = b + c
53
+ if n == 0:
54
+ return 1.0
55
+ k = min(b, c)
56
+ p = sum(math.comb(n, i) for i in range(k + 1)) / 2 ** n
57
+ return min(1.0, 2 * p)
58
+
59
+
60
+ def norm_out(r):
61
+ return json.dumps([r.get("content") or "", r.get("tool_calls") or []], sort_keys=True)
62
+
63
+
64
+ def compare(ref_dir, cand_dir):
65
+ ref, cand = load(ref_dir), load(cand_dir)
66
+ res = {"ref": str(ref_dir), "cand": str(cand_dir), "tasks": {}}
67
+ tot = {"n": 0, "ref": 0, "cand": 0, "agree": 0, "ident": 0, "a": [], "b": []}
68
+ for task in ref:
69
+ if task not in cand:
70
+ continue
71
+ ids = sorted(set(ref[task]) & set(cand[task]))
72
+ a = [int(ref[task][i]["correct"]) for i in ids]
73
+ b = [int(cand[task][i]["correct"]) for i in ids]
74
+ n = len(ids)
75
+ agree = sum(1 for i in ids if json.dumps(ref[task][i].get("agree_key"), sort_keys=True)
76
+ == json.dumps(cand[task][i].get("agree_key"), sort_keys=True))
77
+ ident = sum(1 for i in ids if norm_out(ref[task][i]) == norm_out(cand[task][i]))
78
+ ref_only = sum(1 for x, y in zip(a, b) if x and not y)
79
+ cand_only = sum(1 for x, y in zip(a, b) if y and not x)
80
+
81
+ def mtok(rs):
82
+ t = [r["completion_tokens"] for r in rs if r.get("completion_tokens") is not None]
83
+ return round(sum(t) / len(t), 1) if t else None
84
+ lo, hi = boot_ci(a, b)
85
+ res["tasks"][task] = {
86
+ "n": n, "ref_acc": sum(a) / n, "cand_acc": sum(b) / n, "delta": (sum(b) - sum(a)) / n,
87
+ "delta_boot95": [lo, hi], "ref_only_correct": ref_only, "cand_only_correct": cand_only,
88
+ "mcnemar_p": mcnemar_exact(ref_only, cand_only),
89
+ "ref_agreement": agree / n, "identical_output": ident / n,
90
+ "mean_tokens_ref": mtok([ref[task][i] for i in ids]),
91
+ "mean_tokens_cand": mtok([cand[task][i] for i in ids]),
92
+ "truncated_ref": sum(1 for i in ids if ref[task][i].get("finish_reason") == "length"),
93
+ "truncated_cand": sum(1 for i in ids if cand[task][i].get("finish_reason") == "length"),
94
+ "errors_cand": sum(1 for i in ids if cand[task][i].get("error")),
95
+ }
96
+ tot["n"] += n
97
+ tot["ref"] += sum(a)
98
+ tot["cand"] += sum(b)
99
+ tot["agree"] += agree
100
+ tot["ident"] += ident
101
+ tot["a"] += a
102
+ tot["b"] += b
103
+ if tot["n"]:
104
+ lo, hi = boot_ci(tot["a"], tot["b"])
105
+ res["overall_micro"] = {"n": tot["n"], "ref_acc": tot["ref"] / tot["n"], "cand_acc": tot["cand"] / tot["n"],
106
+ "delta": (tot["cand"] - tot["ref"]) / tot["n"], "delta_boot95": [lo, hi],
107
+ "ref_agreement": tot["agree"] / tot["n"], "identical_output": tot["ident"] / tot["n"]}
108
+ return res
109
+
110
+
111
+ def main():
112
+ ap = argparse.ArgumentParser()
113
+ ap.add_argument("ref_dir")
114
+ ap.add_argument("cand_dir")
115
+ ap.add_argument("--json", default=None)
116
+ args = ap.parse_args()
117
+ res = compare(args.ref_dir, args.cand_dir)
118
+ hdr = f"{'task':10s} {'n':>4s} {'ref':>6s} {'cand':>6s} {'delta':>7s} {'boot95':>17s} {'r-/c+':>6s} {'mcnemar':>7s} {'agree':>6s} {'ident':>6s} {'tok r/c':>13s}"
119
+ print(hdr)
120
+ rows = list(res["tasks"].items()) + ([("ALL(micro)", res["overall_micro"])] if "overall_micro" in res else [])
121
+ for t, s in rows:
122
+ print(f"{t:10s} {s['n']:4d} {s['ref_acc']:6.3f} {s['cand_acc']:6.3f} {s['delta']:+7.3f} "
123
+ f"[{s['delta_boot95'][0]:+.3f},{s['delta_boot95'][1]:+.3f}] "
124
+ f"{str(s.get('ref_only_correct', '')) + '/' + str(s.get('cand_only_correct', '')):>6s} "
125
+ f"{s.get('mcnemar_p', float('nan')):7.3f} {s['ref_agreement']:6.3f} {s['identical_output']:6.3f} "
126
+ f"{str(s.get('mean_tokens_ref'))+'/'+str(s.get('mean_tokens_cand')):>13s}")
127
+ out = args.json or str(Path(args.cand_dir) / "compare_vs_ref.json")
128
+ Path(out).write_text(json.dumps(res, indent=2))
129
+ print("wrote", out)
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
evaluation/harness/ifeval_lite.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Light-weight re-implementation of a SUBSET of the IFEval instruction checkers.
2
+
3
+ Semantics follow google-research/google-research `instruction_following_eval/instructions.py`
4
+ (Apache-2.0, (c) Google LLC), re-written from scratch with these documented deviations:
5
+ * word tokenisation uses regex \\w+ (orig: nltk RegexpTokenizer(r"\\w+") -> identical) and
6
+ capital-word counting uses whitespace/punct split instead of nltk.word_tokenize;
7
+ * `length_constraints:number_sentences` and a few rarely used ids are NOT supported; the eval
8
+ builder only selects prompts whose every instruction id is in SUPPORTED.
9
+ * language detection uses `langdetect` (Apache-2.0) with a fixed seed, like the original.
10
+ Only "strict" scoring is implemented (prompt-level: all instructions pass).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ import re
16
+ import threading
17
+
18
+ _LANG_LOCK = threading.Lock() # langdetect seeds the *global* random module -> not thread-safe
19
+
20
+ try:
21
+ import langdetect
22
+ langdetect.DetectorFactory.seed = 0
23
+ except Exception: # pragma: no cover
24
+ langdetect = None
25
+
26
+
27
+ def _rel(count: int, relation: str, target: int) -> bool:
28
+ if relation == "less than":
29
+ return count < target
30
+ if relation == "at least":
31
+ return count >= target
32
+ raise ValueError(relation)
33
+
34
+
35
+ def _lang(text: str) -> str | None:
36
+ if langdetect is None:
37
+ return None
38
+ try:
39
+ with _LANG_LOCK:
40
+ langdetect.DetectorFactory.seed = 0
41
+ return langdetect.detect(text)
42
+ except Exception:
43
+ return None
44
+
45
+
46
+ def no_comma(r, **k):
47
+ return "," not in r
48
+
49
+
50
+ def number_highlighted_sections(r, num_highlights, **k):
51
+ n = 0
52
+ for h in re.findall(r"\*[^\n\*]*\*", r):
53
+ if h.strip("*").strip():
54
+ n += 1
55
+ for h in re.findall(r"\*\*[^\n\*]*\*\*", r):
56
+ if h.removeprefix("**").removesuffix("**").strip():
57
+ n += 1
58
+ return n >= num_highlights
59
+
60
+
61
+ def title(r, **k):
62
+ return any(t.lstrip("<").rstrip(">").strip() for t in re.findall(r"<<[^\n]+>>", r))
63
+
64
+
65
+ def number_bullet_lists(r, num_bullets, **k):
66
+ a = re.findall(r"^\s*\*[^\*].*$", r, flags=re.M)
67
+ b = re.findall(r"^\s*-.*$", r, flags=re.M)
68
+ return len(a) + len(b) == num_bullets
69
+
70
+
71
+ def json_format(r, **k):
72
+ v = r.strip()
73
+ v = re.sub(r"^```(?:json|Json|JSON)?\s*", "", v)
74
+ v = re.sub(r"\s*```$", "", v).strip()
75
+ try:
76
+ json.loads(v)
77
+ return True
78
+ except ValueError:
79
+ return False
80
+
81
+
82
+ def multiple_sections(r, section_spliter, num_sections, **k):
83
+ pat = r"\s?" + re.escape(section_spliter.strip()) + r"\s?\d+\s?"
84
+ return len(re.split(pat, r)) - 1 >= num_sections
85
+
86
+
87
+ def constrained_response(r, **k):
88
+ v = r.strip()
89
+ return any(o in v for o in ("My answer is yes.", "My answer is no.", "My answer is maybe."))
90
+
91
+
92
+ def english_lowercase(r, **k):
93
+ return r == r.lower() and _lang(r) == "en"
94
+
95
+
96
+ def english_capital(r, **k):
97
+ return r == r.upper() and _lang(r) == "en"
98
+
99
+
100
+ def capital_word_frequency(r, capital_frequency, capital_relation, **k):
101
+ words = re.findall(r"[A-Za-z][A-Za-z'\-]*", r)
102
+ n = sum(1 for w in words if w.isupper())
103
+ return _rel(n, capital_relation, capital_frequency)
104
+
105
+
106
+ def number_words(r, num_words, relation, **k):
107
+ return _rel(len(re.findall(r"\w+", r)), relation, num_words)
108
+
109
+
110
+ def number_paragraphs(r, num_paragraphs, **k):
111
+ paras = re.split(r"\s?\*\*\*\s?", r)
112
+ n = len(paras)
113
+ for i, p in enumerate(paras):
114
+ if not p.strip():
115
+ if i == 0 or i == len(paras) - 1:
116
+ n -= 1
117
+ else:
118
+ return False
119
+ return n == num_paragraphs
120
+
121
+
122
+ def nth_paragraph_first_word(r, num_paragraphs, nth_paragraph, first_word, **k):
123
+ paras = re.split(r"\n\n", r)
124
+ n = sum(1 for p in paras if p.strip())
125
+ paras_ne = [p for p in paras if p.strip()]
126
+ if nth_paragraph > len(paras_ne):
127
+ return False
128
+ p = paras_ne[nth_paragraph - 1].strip()
129
+ if not p:
130
+ return False
131
+ word = p.split()[0].strip()
132
+ word = word.lstrip("'\"").rstrip(".,?!'\"").lower()
133
+ # original strips leading/trailing punctuation char by char
134
+ word = re.sub(r"[^\w]+$", "", re.sub(r"^[^\w]+", "", word))
135
+ return n == num_paragraphs and word == first_word.lower()
136
+
137
+
138
+ def existence(r, keywords, **k):
139
+ return all(re.search(kw, r, flags=re.I) for kw in keywords)
140
+
141
+
142
+ def frequency(r, keyword, frequency, relation, **k):
143
+ return _rel(len(re.findall(keyword, r, flags=re.I)), relation, frequency)
144
+
145
+
146
+ def forbidden_words(r, forbidden_words, **k):
147
+ return not any(re.search(r"\b" + w + r"\b", r, flags=re.I) for w in forbidden_words)
148
+
149
+
150
+ def letter_frequency(r, letter, let_frequency, let_relation, **k):
151
+ return _rel(r.lower().count(letter.lower()), let_relation, let_frequency)
152
+
153
+
154
+ def end_checker(r, end_phrase, **k):
155
+ return r.strip().strip('"').lower().endswith(end_phrase.strip().lower())
156
+
157
+
158
+ def quotation(r, **k):
159
+ v = r.strip()
160
+ return len(v) > 1 and v[0] == '"' and v[-1] == '"'
161
+
162
+
163
+ def postscript(r, postscript_marker, **k):
164
+ v = r.lower()
165
+ m = postscript_marker.strip()
166
+ if m == "P.P.S":
167
+ pat = r"\s*p\.\s?p\.\s?s.*$"
168
+ elif m == "P.S.":
169
+ pat = r"\s*p\.\s?s\..*$"
170
+ else:
171
+ pat = r"\s*" + re.escape(m.lower()) + r".*$"
172
+ return bool(re.findall(pat, v, flags=re.M))
173
+
174
+
175
+ def number_placeholders(r, num_placeholders, **k):
176
+ return len(re.findall(r"\[.*?\]", r)) >= num_placeholders
177
+
178
+
179
+ def repeat_prompt(r, prompt_to_repeat, **k):
180
+ return r.strip().lower().startswith(prompt_to_repeat.strip().lower())
181
+
182
+
183
+ def two_responses(r, **k):
184
+ parts = r.split("******")
185
+ valid = []
186
+ for i, p in enumerate(parts):
187
+ if not p.strip():
188
+ if i != 0 and i != len(parts) - 1:
189
+ return False
190
+ else:
191
+ valid.append(p)
192
+ return len(valid) == 2 and valid[0].strip() != valid[1].strip()
193
+
194
+
195
+ def response_language(r, language, **k):
196
+ return _lang(r) == language
197
+
198
+
199
+ SUPPORTED = {
200
+ "punctuation:no_comma": no_comma,
201
+ "detectable_format:number_highlighted_sections": number_highlighted_sections,
202
+ "detectable_format:title": title,
203
+ "detectable_format:number_bullet_lists": number_bullet_lists,
204
+ "detectable_format:json_format": json_format,
205
+ "detectable_format:multiple_sections": multiple_sections,
206
+ "detectable_format:constrained_response": constrained_response,
207
+ "change_case:english_lowercase": english_lowercase,
208
+ "change_case:english_capital": english_capital,
209
+ "change_case:capital_word_frequency": capital_word_frequency,
210
+ "length_constraints:number_words": number_words,
211
+ "length_constraints:number_paragraphs": number_paragraphs,
212
+ "length_constraints:nth_paragraph_first_word": nth_paragraph_first_word,
213
+ "keywords:existence": existence,
214
+ "keywords:frequency": frequency,
215
+ "keywords:forbidden_words": forbidden_words,
216
+ "keywords:letter_frequency": letter_frequency,
217
+ "startend:end_checker": end_checker,
218
+ "startend:quotation": quotation,
219
+ "detectable_content:postscript": postscript,
220
+ "detectable_content:number_placeholders": number_placeholders,
221
+ "combination:repeat_prompt": repeat_prompt,
222
+ "combination:two_responses": two_responses,
223
+ "language:response_language": response_language,
224
+ }
225
+
226
+
227
+ def check(response: str, instruction_ids: list[str], kwargs_list: list[dict]) -> list[bool]:
228
+ out = []
229
+ for iid, kw in zip(instruction_ids, kwargs_list):
230
+ kw = {k: v for k, v in (kw or {}).items() if v is not None}
231
+ fn = SUPPORTED[iid]
232
+ try:
233
+ out.append(bool(response.strip()) and bool(fn(response, **kw)))
234
+ except Exception:
235
+ out.append(False)
236
+ return out
evaluation/harness/mock_server.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Tiny OpenAI-compatible mock server that answers eval items with their GOLD answers.
3
+ Used only to test the harness offline (scorers, raw-text tool-call fallback parser, compare, perf_bench).
4
+
5
+ .venv-eval/bin/python eval/mock_server.py --port 18999 [--tool-mode server|xml] [--corrupt-every K]
6
+
7
+ --tool-mode xml returns tool calls as raw Qwen3.5-XML text in `content` (as a server without a
8
+ tool parser would), exercising the fallback parser. --corrupt-every K returns a wrong answer for
9
+ items whose id hash % K == 0 (to test compare.py deltas). Streaming is supported for perf_bench.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import argparse
14
+ import hashlib
15
+ import json
16
+ import sys
17
+ import time
18
+ from collections import Counter
19
+ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
20
+ from pathlib import Path
21
+
22
+ HERE = Path(__file__).resolve().parent
23
+ sys.path.insert(0, str(HERE))
24
+ from common import read_jsonl # noqa: E402
25
+ from tasks import TASKS, vqa_norm # noqa: E402
26
+
27
+ ARGS = None
28
+ INDEX: dict[str, tuple[str, dict]] = {}
29
+
30
+
31
+ def last_user_text(messages):
32
+ for m in reversed(messages):
33
+ if m["role"] == "user":
34
+ c = m["content"]
35
+ if isinstance(c, list):
36
+ return "".join(p.get("text", "") for p in c if p.get("type") == "text")
37
+ return c
38
+ return ""
39
+
40
+
41
+ def build_index():
42
+ for task, (build, _) in TASKS.items():
43
+ for item in read_jsonl(HERE / "data" / f"{task}.jsonl"):
44
+ msgs, _ = build(item, HERE / "data")
45
+ INDEX[last_user_text(msgs)] = (task, item)
46
+
47
+
48
+ def xml_call(c):
49
+ s = f"<tool_call>\n<function={c['name']}>\n"
50
+ for k, v in c["arguments"].items():
51
+ v = json.dumps(v) if isinstance(v, (dict, list)) else (str(v) if not isinstance(v, bool) else str(v))
52
+ s += f"<parameter={k}>\n{v}\n</parameter>\n"
53
+ return s + "</function>\n</tool_call>"
54
+
55
+
56
+ def gold_response(task, item, corrupt):
57
+ """Return (content, tool_calls)."""
58
+ if task == "ifeval":
59
+ return "this is a mock response without commas.", None
60
+ if task == "gsm8k":
61
+ ans = str(int(item["answer"]) + 1) if corrupt else item["answer"]
62
+ body = item["solution"].split("####")[0].strip()
63
+ return f"{body}\nThe final answer is {ans}", None
64
+ if task == "mmlu_pro":
65
+ a = "ABCDEFGHIJ"[(("ABCDEFGHIJ".index(item["answer"]) + 1) % len(item["options"]))] if corrupt else item["answer"]
66
+ return f"Let's think. The answer is ({a}).", None
67
+ if task == "humaneval":
68
+ sol = " return None\n" if corrupt else item["canonical_solution"]
69
+ return f"Here is the code:\n```python\n{item['prompt']}{sol}```", None
70
+ if task == "vqa":
71
+ return ("zzz" if corrupt else Counter(vqa_norm(a) for a in item["answers"]).most_common(1)[0][0]), None
72
+ if task == "tools":
73
+ if not item["gold_calls"] or corrupt:
74
+ return "I can answer that directly: mock.", None
75
+ calls = []
76
+ for g in item["gold_calls"]:
77
+ args = {k: v[0] for k, v in g["arguments"].items() if v and v[0] != "__optional__"}
78
+ calls.append({"name": g["name"], "arguments": args})
79
+ if ARGS.tool_mode == "xml":
80
+ return "\n".join(xml_call(c) for c in calls), None
81
+ return None, [{"id": f"call_{i}", "type": "function",
82
+ "function": {"name": c["name"], "arguments": json.dumps(c["arguments"])}}
83
+ for i, c in enumerate(calls)]
84
+ return "unknown", None
85
+
86
+
87
+ class H(BaseHTTPRequestHandler):
88
+ protocol_version = "HTTP/1.1"
89
+
90
+ def log_message(self, *a):
91
+ pass
92
+
93
+ def _send(self, code, obj):
94
+ b = json.dumps(obj).encode()
95
+ self.send_response(code)
96
+ self.send_header("Content-Type", "application/json")
97
+ self.send_header("Content-Length", str(len(b)))
98
+ self.end_headers()
99
+ self.wfile.write(b)
100
+
101
+ def do_GET(self):
102
+ if self.path.endswith("/models"):
103
+ return self._send(200, {"object": "list", "data": [{"id": "mock", "object": "model"}]})
104
+ self._send(404, {"error": "not found"})
105
+
106
+ def do_POST(self):
107
+ body = json.loads(self.rfile.read(int(self.headers.get("Content-Length", 0))))
108
+ msgs = body["messages"]
109
+ text = last_user_text(msgs)
110
+ prompt_tokens = sum(len(json.dumps(m["content"])) for m in msgs) // 4
111
+ if body.get("stream"):
112
+ return self._stream(body, prompt_tokens)
113
+ task_item = INDEX.get(text)
114
+ if task_item is None:
115
+ content, tcs = "mock: unknown prompt", None
116
+ else:
117
+ task, item = task_item
118
+ corrupt = ARGS.corrupt_every and int(hashlib.md5(item["id"].encode()).hexdigest(), 16) % ARGS.corrupt_every == 0
119
+ content, tcs = gold_response(task, item, corrupt)
120
+ msg = {"role": "assistant", "content": content}
121
+ if tcs:
122
+ msg["tool_calls"] = tcs
123
+ ct = max(1, len((content or "") + json.dumps(tcs or "")) // 4)
124
+ self._send(200, {"id": "mock", "object": "chat.completion", "model": body["model"],
125
+ "choices": [{"index": 0, "message": msg, "finish_reason": "tool_calls" if tcs else "stop"}],
126
+ "usage": {"prompt_tokens": prompt_tokens, "completion_tokens": ct,
127
+ "total_tokens": prompt_tokens + ct}})
128
+
129
+ def _stream(self, body, prompt_tokens):
130
+ n = int(body.get("max_tokens") or 16)
131
+ self.send_response(200)
132
+ self.send_header("Content-Type", "text/event-stream")
133
+ self.send_header("Transfer-Encoding", "chunked")
134
+ self.end_headers()
135
+
136
+ def chunk(obj):
137
+ data = f"data: {obj if isinstance(obj, str) else json.dumps(obj)}\n\n".encode()
138
+ self.wfile.write(f"{len(data):x}\r\n".encode() + data + b"\r\n")
139
+ self.wfile.flush()
140
+ time.sleep(ARGS.ttft)
141
+ for i in range(n):
142
+ chunk({"choices": [{"index": 0, "delta": {"content": "tok "}, "finish_reason": None}]})
143
+ time.sleep(ARGS.itl)
144
+ chunk({"choices": [{"index": 0, "delta": {}, "finish_reason": "length"}]})
145
+ if (body.get("stream_options") or {}).get("include_usage"):
146
+ chunk({"choices": [], "usage": {"prompt_tokens": prompt_tokens, "completion_tokens": n,
147
+ "total_tokens": prompt_tokens + n}})
148
+ chunk("[DONE]")
149
+ self.wfile.write(b"0\r\n\r\n")
150
+ self.wfile.flush()
151
+
152
+
153
+ def main():
154
+ global ARGS
155
+ ap = argparse.ArgumentParser()
156
+ ap.add_argument("--port", type=int, default=18999)
157
+ ap.add_argument("--tool-mode", choices=["server", "xml"], default="server")
158
+ ap.add_argument("--corrupt-every", type=int, default=0)
159
+ ap.add_argument("--ttft", type=float, default=0.02)
160
+ ap.add_argument("--itl", type=float, default=0.002)
161
+ ARGS = ap.parse_args()
162
+ build_index()
163
+ print(f"mock server on 127.0.0.1:{ARGS.port} ({len(INDEX)} items indexed)", flush=True)
164
+ srv = ThreadingHTTPServer(("127.0.0.1", ARGS.port), H)
165
+ srv.handle_error = lambda *a: None # quiet on client disconnects
166
+ srv.daemon_threads = True
167
+ srv.serve_forever()
168
+
169
+
170
+ if __name__ == "__main__":
171
+ main()
evaluation/harness/perf_bench.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Streaming throughput/latency benchmark for an OpenAI-compatible Chat Completions server.
3
+
4
+ .venv-eval/bin/python eval/perf_bench.py --base-url http://localhost:30000/v1 \
5
+ --model SenseNova-Loop-9B-Preview --concurrency 1,2,4,8 --num-requests 16 \
6
+ --input-tokens 1024 --output-tokens 256 --out eval/runs/perf_bf16.json
7
+
8
+ Prompt: deterministic pseudo-text (fixed seed) cut to exactly --input-tokens tokens with the model's
9
+ own tokenizer.json (the chat template adds a few more; actual prompt_tokens come from `usage`).
10
+ Each request gets a unique numeric prefix so prefix caching cannot serve it (disable with
11
+ --allow-prefix-cache). Requests use temperature 0, ignore_eos=true and min_tokens=max_tokens
12
+ (vLLM/SGLang extensions; disable with --no-ignore-eos) and thinking off.
13
+ Metrics per concurrency level: TTFT p50/p90/mean, per-request decode tok/s ((n_out-1)/(t_last-t_first)),
14
+ inter-token latency, E2E latency p50/p90, aggregate output tok/s (sum out tokens / level wall time),
15
+ request throughput, errors, actual generated lengths (from usage; falls back to chunk count).
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import json
21
+ import platform
22
+ import random
23
+ import statistics
24
+ import sys
25
+ import time
26
+ from concurrent.futures import ThreadPoolExecutor
27
+ from pathlib import Path
28
+
29
+ import requests
30
+
31
+ HERE = Path(__file__).resolve().parent
32
+ DEFAULT_TOK = HERE.parent / "upstream/SenseNova-Loop-9B-Preview/tokenizer.json"
33
+ WORDS = ("the of and to in is was for on that with as by at from his her an were are which this be has "
34
+ "had it not or have one their they its first new after who two been more year also other some "
35
+ "into all time only most over three city river mountain village market winter summer story "
36
+ "engine signal harbor garden library window letter bridge forest island station machine "
37
+ "children ancient quiet bright careful distant golden narrow sudden gentle measured").split()
38
+
39
+
40
+ def build_prompt_text(n_tokens: int, tokenizer_path: str | None):
41
+ rng = random.Random(1234)
42
+ sents = []
43
+ for _ in range(n_tokens): # plenty of text
44
+ k = rng.randint(8, 18)
45
+ s = " ".join(rng.choice(WORDS) for _ in range(k))
46
+ sents.append(s[0].upper() + s[1:] + ".")
47
+ text = " ".join(sents)
48
+ if tokenizer_path and Path(tokenizer_path).exists():
49
+ from tokenizers import Tokenizer
50
+ tok = Tokenizer.from_file(str(tokenizer_path))
51
+ ids = tok.encode(text, add_special_tokens=False).ids[:n_tokens]
52
+ text = tok.decode(ids)
53
+ return text, len(tok.encode(text, add_special_tokens=False).ids), "tokenizer.json"
54
+ words = text.split()[: int(n_tokens * 0.75)]
55
+ return " ".join(words), None, "approx(0.75 words/token)"
56
+
57
+
58
+ def pct(xs, q):
59
+ if not xs:
60
+ return None
61
+ xs = sorted(xs)
62
+ k = (len(xs) - 1) * q
63
+ f = int(k)
64
+ c = min(f + 1, len(xs) - 1)
65
+ return xs[f] + (xs[c] - xs[f]) * (k - f)
66
+
67
+
68
+ def one_request(args, session, text, idx):
69
+ prefix = "" if args.allow_prefix_cache else f"[req {idx:06d}] "
70
+ body = {"model": args.model, "stream": True, "stream_options": {"include_usage": True},
71
+ "messages": [{"role": "user", "content": prefix + text + "\n\nContinue this text."}],
72
+ "max_tokens": args.output_tokens, "temperature": 0.0,
73
+ "chat_template_kwargs": {"enable_thinking": False}}
74
+ if not args.no_ignore_eos:
75
+ body.update({"ignore_eos": True, "min_tokens": args.output_tokens})
76
+ t0 = time.perf_counter()
77
+ t_first = t_last = None
78
+ n_chunks = 0
79
+ usage = None
80
+ finish = None
81
+ try:
82
+ with session.post(args.base_url.rstrip("/") + "/chat/completions", json=body, stream=True,
83
+ timeout=args.timeout) as r:
84
+ if r.status_code != 200:
85
+ return {"ok": False, "error": f"HTTP {r.status_code}: {r.text[:300]}"}
86
+ for line in r.iter_lines():
87
+ if not line or not line.startswith(b"data:"):
88
+ continue
89
+ data = line[5:].strip()
90
+ if data == b"[DONE]":
91
+ break
92
+ j = json.loads(data)
93
+ if j.get("usage"):
94
+ usage = j["usage"]
95
+ for ch in j.get("choices") or []:
96
+ d = ch.get("delta") or {}
97
+ if d.get("content") or d.get("reasoning_content") or d.get("reasoning"):
98
+ now = time.perf_counter()
99
+ if t_first is None:
100
+ t_first = now
101
+ t_last = now
102
+ n_chunks += 1
103
+ if ch.get("finish_reason"):
104
+ finish = ch["finish_reason"]
105
+ except Exception as e: # noqa: BLE001
106
+ return {"ok": False, "error": f"{type(e).__name__}: {e}"}
107
+ t_end = time.perf_counter()
108
+ if t_first is None:
109
+ return {"ok": False, "error": "no tokens received"}
110
+ n_out = (usage or {}).get("completion_tokens") or n_chunks
111
+ decode_t = (t_last - t_first) if t_last and t_last > t_first else None
112
+ return {"ok": True, "ttft_s": t_first - t0, "e2e_s": t_end - t0, "output_tokens": n_out,
113
+ "prompt_tokens": (usage or {}).get("prompt_tokens"), "n_chunks": n_chunks, "finish_reason": finish,
114
+ "decode_tok_s": (n_out - 1) / decode_t if decode_t and n_out > 1 else None,
115
+ "itl_s": decode_t / (n_out - 1) if decode_t and n_out > 1 else None,
116
+ "usage_present": usage is not None}
117
+
118
+
119
+ def run_level(args, session, text, c, base_idx):
120
+ n = args.num_requests if args.num_requests else max(8, 4 * c)
121
+ t0 = time.perf_counter()
122
+ with ThreadPoolExecutor(c) as ex:
123
+ res = list(ex.map(lambda i: one_request(args, session, text, base_idx + i), range(n)))
124
+ wall = time.perf_counter() - t0
125
+ ok = [r for r in res if r["ok"]]
126
+
127
+ def stat(key):
128
+ xs = [r[key] for r in ok if r.get(key) is not None]
129
+ return {"p50": pct(xs, 0.5), "p90": pct(xs, 0.9), "mean": statistics.fmean(xs) if xs else None}
130
+ outs = [r["output_tokens"] for r in ok]
131
+ return {"concurrency": c, "n_requests": n, "n_ok": len(ok), "n_errors": len(res) - len(ok),
132
+ "errors": [r["error"] for r in res if not r["ok"]][:5], "wall_s": wall,
133
+ "ttft_s": stat("ttft_s"), "e2e_s": stat("e2e_s"), "decode_tok_s_per_req": stat("decode_tok_s"),
134
+ "itl_s": stat("itl_s"),
135
+ "output_tokens_mean": statistics.fmean(outs) if outs else None,
136
+ "output_tokens_min": min(outs) if outs else None, "output_tokens_max": max(outs) if outs else None,
137
+ "prompt_tokens_mean": statistics.fmean([r["prompt_tokens"] for r in ok if r.get("prompt_tokens")] or [0]),
138
+ "n_short_outputs": sum(1 for o in outs if o < args.output_tokens),
139
+ "aggregate_output_tok_s": sum(outs) / wall if wall else None,
140
+ "requests_per_s": len(ok) / wall if wall else None,
141
+ "usage_reported": all(r.get("usage_present") for r in ok)}
142
+
143
+
144
+ def main():
145
+ ap = argparse.ArgumentParser()
146
+ ap.add_argument("--base-url", required=True)
147
+ ap.add_argument("--model", required=True)
148
+ ap.add_argument("--concurrency", default="1,2,4,8")
149
+ ap.add_argument("--num-requests", type=int, default=16, help="per level (0 => max(8, 4*c))")
150
+ ap.add_argument("--warmup", type=int, default=2)
151
+ ap.add_argument("--input-tokens", type=int, default=1024)
152
+ ap.add_argument("--output-tokens", type=int, default=256)
153
+ ap.add_argument("--tokenizer", default=str(DEFAULT_TOK))
154
+ ap.add_argument("--allow-prefix-cache", action="store_true")
155
+ ap.add_argument("--no-ignore-eos", action="store_true")
156
+ ap.add_argument("--timeout", type=float, default=600)
157
+ ap.add_argument("--label", default="")
158
+ ap.add_argument("--out", required=True)
159
+ args = ap.parse_args()
160
+
161
+ text, n_tok, how = build_prompt_text(args.input_tokens, args.tokenizer)
162
+ levels = [int(x) for x in args.concurrency.split(",")]
163
+ session = requests.Session()
164
+ session.mount("http://", requests.adapters.HTTPAdapter(pool_maxsize=max(levels) * 2 + 4))
165
+ for i in range(args.warmup):
166
+ w = one_request(args, session, text, 900000 + i)
167
+ if not w["ok"]:
168
+ print("warmup error:", w["error"], file=sys.stderr)
169
+ res = {"label": args.label, "model": args.model, "base_url": args.base_url, "host": platform.node(),
170
+ "time": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
171
+ "config": {**vars(args), "prompt_text_tokens": n_tok, "prompt_build": how}, "levels": []}
172
+ base = 0
173
+ for c in levels:
174
+ lv = run_level(args, session, text, c, base)
175
+ base += 10000
176
+ res["levels"].append(lv)
177
+ f = lambda x: "n/a" if x is None else f"{x:.3f}" # noqa: E731
178
+ print(f"c={c:3d} ok={lv['n_ok']}/{lv['n_requests']} TTFT p50={f(lv['ttft_s']['p50'])}s "
179
+ f"p90={f(lv['ttft_s']['p90'])}s decode/req p50={f(lv['decode_tok_s_per_req']['p50'])} tok/s "
180
+ f"E2E p50={f(lv['e2e_s']['p50'])}s agg={f(lv['aggregate_output_tok_s'])} tok/s "
181
+ f"out_len={lv['output_tokens_mean']}", flush=True)
182
+ Path(args.out).parent.mkdir(parents=True, exist_ok=True)
183
+ Path(args.out).write_text(json.dumps(res, indent=2))
184
+ print("wrote", args.out)
185
+
186
+
187
+ if __name__ == "__main__":
188
+ main()
evaluation/harness/requirements.lock ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.7.1
2
+ aiohttp==3.14.5
3
+ aiosignal==1.4.0
4
+ anyio==4.15.1
5
+ attrs==26.1.0
6
+ certifi==2026.10.23
7
+ charset-normalizer==3.5.2
8
+ click==8.5.0
9
+ cloudpickle==3.1.2
10
+ datasets==5.1.0
11
+ defusedxml==0.7.1
12
+ dill==0.4.1
13
+ filelock==4.2.5
14
+ frozenlist==1.8.0
15
+ fsspec==2026.7.0
16
+ h11==0.16.0
17
+ hf-xet==1.8.0
18
+ httpcore2==2.16.0
19
+ httpx2==2.16.0
20
+ huggingface-hub==2.4.0
21
+ idna==3.20
22
+ immutabledict==4.3.1
23
+ jinja2==3.1.6
24
+ joblib==1.6.0
25
+ langdetect==1.0.9
26
+ markupsafe==3.0.4
27
+ multidict==7.2.1
28
+ multiprocess==0.70.19
29
+ nltk==3.10.4
30
+ numpy==2.5.4
31
+ packaging==26.3
32
+ pandas==3.1.0
33
+ pillow==13.0.0
34
+ propcache==0.5.4
35
+ pyarrow==26.0.0
36
+ python-dateutil==3.0.0
37
+ pyyaml==6.0.3
38
+ regex==2026.9.29
39
+ requests==2.34.2
40
+ six==1.17.0
41
+ tokenizers==0.23.3
42
+ tqdm==4.70.1
43
+ truststore==0.10.5
44
+ typing-extensions==4.16.0
45
+ tzdata==2026.6
46
+ urllib3==2.8.0
47
+ xxhash==4.0.2
48
+ yarl==1.25.1
evaluation/harness/run_eval.py ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Run the fixed eval suite against an OpenAI-compatible Chat Completions endpoint.
3
+
4
+ Example:
5
+ .venv-eval/bin/python eval/run_eval.py --base-url http://localhost:30000/v1 \
6
+ --model SenseNova-Loop-9B-Preview --out eval/runs/bf16 --concurrency 16
7
+ Writes <out>/raw_<task>.jsonl (one line per item, full response + score) and <out>/summary.json.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import hashlib
13
+ import json
14
+ import platform
15
+ import sys
16
+ import time
17
+ from concurrent.futures import ThreadPoolExecutor
18
+ from pathlib import Path
19
+
20
+ HERE = Path(__file__).resolve().parent
21
+ sys.path.insert(0, str(HERE))
22
+ import requests # noqa: E402
23
+
24
+ from common import EVAL_VERSION, chat, read_jsonl, wilson_ci, write_jsonl # noqa: E402
25
+ from tasks import MAX_TOKENS, TASKS, agreement_key # noqa: E402
26
+
27
+ DATA = HERE / "data"
28
+
29
+
30
+ def run_item(task, item, args, session):
31
+ build, score = TASKS[task]
32
+ messages, tools = build(item, DATA)
33
+ resp = chat(session, args.base_url, args.model, messages, max_tokens=MAX_TOKENS[task], tools=tools,
34
+ enable_thinking=args.enable_thinking, timeout=args.timeout)
35
+ try:
36
+ sc = score(item, resp) if resp["ok"] else {"pred": None, "correct": False}
37
+ except Exception as e: # noqa: BLE001
38
+ sc = {"pred": None, "correct": False, "score_error": f"{type(e).__name__}: {e}"}
39
+ sc["correct"] = bool(sc["correct"])
40
+ return {
41
+ "id": item["id"], "task": task,
42
+ "prompt_sha256": hashlib.sha256(json.dumps(messages, sort_keys=True)[:200000].encode()).hexdigest()[:16],
43
+ "content": resp["content"], "reasoning_content": resp["reasoning_content"],
44
+ "tool_calls": resp["tool_calls"], "finish_reason": resp["finish_reason"],
45
+ "usage": resp["usage"], "completion_tokens": (resp["usage"] or {}).get("completion_tokens"),
46
+ "latency_s": resp["latency_s"], "error": resp["error"],
47
+ "agree_key": agreement_key(task, sc, resp), **sc,
48
+ }
49
+
50
+
51
+ def summarize_task(rows):
52
+ n = len(rows)
53
+ k = sum(r["correct"] for r in rows)
54
+ lo, hi = wilson_ci(k, n)
55
+ toks = [r["completion_tokens"] for r in rows if r["completion_tokens"] is not None]
56
+ s = {"n": n, "correct": k, "accuracy": k / n if n else None, "wilson95": [lo, hi],
57
+ "n_errors": sum(1 for r in rows if r["error"]),
58
+ "n_truncated": sum(1 for r in rows if r["finish_reason"] == "length"),
59
+ "mean_completion_tokens": sum(toks) / len(toks) if toks else None,
60
+ "sum_completion_tokens": sum(toks)}
61
+ if rows and "vqa_soft_acc" in rows[0]:
62
+ s["vqa_soft_acc"] = sum(r.get("vqa_soft_acc", 0) for r in rows) / n
63
+ if rows and rows[0]["task"] == "tools":
64
+ s["names_correct"] = sum(1 for r in rows if r.get("names_correct")) / n
65
+ s["parse_source_counts"] = {}
66
+ for r in rows:
67
+ src = r.get("parse_source", "none")
68
+ s["parse_source_counts"][src] = s["parse_source_counts"].get(src, 0) + 1
69
+ fmt = [r.get("format", {}) for r in rows if r.get("parse_source") != "none"]
70
+ s["format_well_formed"] = (sum(1 for f in fmt if f.get("well_formed")) / len(fmt)) if fmt else None
71
+ s["raw_tool_call_tags_in_content"] = sum(1 for r in rows if "<tool_call>" in (r["content"] or ""))
72
+ by = {}
73
+ for r in rows:
74
+ c = r["id"].split("_")[1]
75
+ by.setdefault(c, [0, 0])
76
+ by[c][0] += r["correct"]
77
+ by[c][1] += 1
78
+ s["by_category"] = {c: f"{a}/{b}" for c, (a, b) in sorted(by.items())}
79
+ if rows and rows[0]["task"] == "ifeval":
80
+ flat = [x for r in rows for x in r.get("per_instruction", [])]
81
+ s["instruction_level_acc"] = sum(flat) / len(flat) if flat else None
82
+ s["n_instructions"] = len(flat)
83
+ return s
84
+
85
+
86
+ def main():
87
+ ap = argparse.ArgumentParser()
88
+ ap.add_argument("--base-url", required=True, help="e.g. http://localhost:30000/v1")
89
+ ap.add_argument("--model", required=True)
90
+ ap.add_argument("--out", required=True)
91
+ ap.add_argument("--tasks", default=",".join(TASKS))
92
+ ap.add_argument("--concurrency", type=int, default=8)
93
+ ap.add_argument("--limit", type=int, default=None, help="debug: first N items per task")
94
+ ap.add_argument("--enable-thinking", action="store_true", help="default OFF")
95
+ ap.add_argument("--timeout", type=float, default=900)
96
+ ap.add_argument("--api-key", default="EMPTY")
97
+ args = ap.parse_args()
98
+
99
+ out = Path(args.out)
100
+ out.mkdir(parents=True, exist_ok=True)
101
+ session = requests.Session()
102
+ session.headers["Authorization"] = f"Bearer {args.api_key}"
103
+ adapter = requests.adapters.HTTPAdapter(pool_maxsize=max(16, args.concurrency * 2))
104
+ session.mount("http://", adapter)
105
+ session.mount("https://", adapter)
106
+
107
+ manifest = json.loads((DATA / "MANIFEST.json").read_text())
108
+ summary = {"eval_version": EVAL_VERSION, "model": args.model, "base_url": args.base_url,
109
+ "started": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "enable_thinking": args.enable_thinking,
110
+ "sampling": {"temperature": 0.0, "top_p": 1.0, "seed": 0, "max_tokens": MAX_TOKENS},
111
+ "concurrency": args.concurrency, "limit": args.limit, "host": platform.node(),
112
+ "data_sha256": {t: v["sha256"] for t, v in manifest["tasks"].items()}, "tasks": {}}
113
+ try:
114
+ summary["server_models"] = session.get(args.base_url.rstrip("/") + "/models", timeout=10).json()
115
+ except Exception as e: # noqa: BLE001
116
+ summary["server_models"] = f"unavailable: {e}"
117
+
118
+ t_all = time.time()
119
+ for task in args.tasks.split(","):
120
+ items = read_jsonl(DATA / f"{task}.jsonl")
121
+ if args.limit:
122
+ items = items[: args.limit]
123
+ t0 = time.time()
124
+ with ThreadPoolExecutor(args.concurrency) as ex:
125
+ rows = list(ex.map(lambda it: run_item(task, it, args, session), items))
126
+ write_jsonl(out / f"raw_{task}.jsonl", rows)
127
+ s = summarize_task(rows)
128
+ s["wall_s"] = round(time.time() - t0, 1)
129
+ summary["tasks"][task] = s
130
+ print(f"{task:10s} n={s['n']:3d} acc={s['accuracy']:.3f} "
131
+ f"CI95=[{s['wilson95'][0]:.3f},{s['wilson95'][1]:.3f}] err={s['n_errors']} "
132
+ f"trunc={s['n_truncated']} mean_tok={s['mean_completion_tokens']} {s['wall_s']}s", flush=True)
133
+ summary["wall_s"] = round(time.time() - t_all, 1)
134
+ (out / "summary.json").write_text(json.dumps(summary, indent=2, default=str))
135
+ print("wrote", out / "summary.json")
136
+
137
+
138
+ if __name__ == "__main__":
139
+ main()
evaluation/harness/tasks.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Task definitions: prompt construction, max_tokens, answer extraction and scoring.
2
+
3
+ Every task exposes:
4
+ build_messages(item, data_dir) -> (messages, tools|None)
5
+ score(item, resp) -> dict(pred=..., correct=bool, **details) (resp = common.chat() result)
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import base64
10
+ import json
11
+ import os
12
+ import re
13
+ import subprocess
14
+ import sys
15
+ import tempfile
16
+ from pathlib import Path
17
+
18
+ import ifeval_lite
19
+ from common import calls_from_message, strip_think
20
+
21
+ # Fixed per-task generation budgets (thinking OFF). Changing these => bump EVAL_VERSION.
22
+ MAX_TOKENS = {"ifeval": 1024, "gsm8k": 1024, "mmlu_pro": 1024, "humaneval": 1024,
23
+ "tools": 512, "vqa": 64}
24
+
25
+ # ============================================================================ IFEval
26
+ def ifeval_messages(item, data_dir):
27
+ return [{"role": "user", "content": item["prompt"]}], None
28
+
29
+
30
+ def ifeval_score(item, resp):
31
+ text = strip_think(resp["content"])
32
+ per = ifeval_lite.check(text, item["instruction_id_list"], item["kwargs"])
33
+ return {"pred": None, "correct": all(per), "per_instruction": per}
34
+
35
+
36
+ # ============================================================================ GSM8K
37
+ GSM_TMPL = ("Solve the following math problem step by step. At the end, write the final "
38
+ "answer on its own line in the form \"The final answer is <number>\".\n\n{q}")
39
+ NUM_RE = r"-?\$?\s?\d[\d,]*(?:\.\d+)?"
40
+
41
+
42
+ def _norm_num(s):
43
+ if s is None:
44
+ return None
45
+ s = s.replace(",", "").replace("$", "").replace(" ", "").rstrip(".")
46
+ try:
47
+ x = float(s)
48
+ except ValueError:
49
+ return None
50
+ return str(int(x)) if x == int(x) else repr(round(x, 6))
51
+
52
+
53
+ def gsm_extract(text):
54
+ text = strip_think(text)
55
+ m = re.findall(r"final answer is[:\s]*\**\s*\\?\(?\\?(?:boxed\{)?\s*(" + NUM_RE + ")", text, re.I)
56
+ if m:
57
+ return _norm_num(m[-1])
58
+ m = re.findall(r"\\boxed\{\s*(" + NUM_RE + ")", text)
59
+ if m:
60
+ return _norm_num(m[-1])
61
+ m = re.findall(NUM_RE, text)
62
+ return _norm_num(m[-1]) if m else None
63
+
64
+
65
+ def gsm8k_messages(item, data_dir):
66
+ return [{"role": "user", "content": GSM_TMPL.format(q=item["question"])}], None
67
+
68
+
69
+ def gsm8k_score(item, resp):
70
+ pred = gsm_extract(resp["content"])
71
+ return {"pred": pred, "correct": pred is not None and pred == _norm_num(item["answer"])}
72
+
73
+
74
+ # ============================================================================ MMLU-Pro
75
+ LETTERS = "ABCDEFGHIJ"
76
+ MC_TMPL = ("The following is a multiple choice question about {cat}. Think step by step briefly, "
77
+ "then finish with \"The answer is (X)\" where X is the letter of the correct option.\n\n"
78
+ "Question: {q}\n{opts}")
79
+
80
+
81
+ def mmlu_pro_messages(item, data_dir):
82
+ opts = "\n".join(f"({LETTERS[i]}) {o}" for i, o in enumerate(item["options"]))
83
+ return [{"role": "user", "content": MC_TMPL.format(cat=item["category"], q=item["question"], opts=opts)}], None
84
+
85
+
86
+ def mc_extract(text, n_opts):
87
+ text = strip_think(text)
88
+ valid = LETTERS[:n_opts]
89
+ m = re.findall(r"answer is:?\s*\**\(?([A-J])\)?", text, re.I)
90
+ m = [x.upper() for x in m if x.upper() in valid]
91
+ if m:
92
+ return m[-1]
93
+ m = re.findall(r"(?:^|\s)\(?([A-J])\)?[\.\s]*$", text.strip())
94
+ if m and m[-1] in valid:
95
+ return m[-1]
96
+ m = re.findall(r"\(([A-J])\)", text)
97
+ m = [x for x in m if x in valid]
98
+ return m[-1] if m else None
99
+
100
+
101
+ def mmlu_pro_score(item, resp):
102
+ pred = mc_extract(resp["content"], len(item["options"]))
103
+ return {"pred": pred, "correct": pred == item["answer"]}
104
+
105
+
106
+ # ============================================================================ HumanEval
107
+ HE_TMPL = ("Complete the following Python function. Return the complete function definition "
108
+ "(including the signature and any needed imports) in a single ```python code block.\n\n"
109
+ "```python\n{p}```")
110
+
111
+
112
+ def humaneval_messages(item, data_dir):
113
+ return [{"role": "user", "content": HE_TMPL.format(p=item["prompt"])}], None
114
+
115
+
116
+ def extract_code(text, item):
117
+ text = strip_think(text)
118
+ blocks = re.findall(r"```(?:python|py|Python)?\s*\n(.*?)```", text, re.S)
119
+ code = None
120
+ for b in blocks:
121
+ if f"def {item['entry_point']}" in b:
122
+ code = b
123
+ break
124
+ if code is None and blocks:
125
+ code = blocks[0]
126
+ if code is None:
127
+ code = text
128
+ if f"def {item['entry_point']}" not in code: # body-only completion
129
+ code = item["prompt"] + code
130
+ else: # keep the prompt's imports/helper defs available
131
+ code = item["prompt"].split(f"def {item['entry_point']}")[0] + "\n" + code
132
+ return code
133
+
134
+
135
+ def run_python_sandboxed(program: str, timeout: float = 10.0) -> tuple[bool, str]:
136
+ """Execute untrusted code in a fresh isolated interpreter in a temp dir with rlimits."""
137
+ with tempfile.TemporaryDirectory(prefix="he_") as td:
138
+ path = os.path.join(td, "prog.py")
139
+ with open(path, "w") as f:
140
+ f.write(program)
141
+
142
+ def limits():
143
+ import resource
144
+ resource.setrlimit(resource.RLIMIT_AS, (2 << 30, 2 << 30))
145
+ resource.setrlimit(resource.RLIMIT_CPU, (int(timeout) + 1, int(timeout) + 1))
146
+ resource.setrlimit(resource.RLIMIT_FSIZE, (10 << 20, 10 << 20))
147
+ os.setsid()
148
+ env = {"PATH": "/usr/bin:/bin", "HOME": td, "PYTHONHASHSEED": "0"}
149
+ try:
150
+ p = subprocess.run([sys.executable, "-I", "-B", path], cwd=td, env=env, capture_output=True,
151
+ timeout=timeout, preexec_fn=limits, text=True)
152
+ return p.returncode == 0, (p.stderr or "")[-400:]
153
+ except subprocess.TimeoutExpired:
154
+ return False, "timeout"
155
+
156
+
157
+ def humaneval_score(item, resp):
158
+ code = extract_code(resp["content"], item)
159
+ prog = code + "\n\n" + item["test"] + f"\n\ncheck({item['entry_point']})\n"
160
+ ok, err = run_python_sandboxed(prog)
161
+ return {"pred": code, "correct": ok, "exec_error": None if ok else err}
162
+
163
+
164
+ # ============================================================================ Tool calling
165
+ def tools_messages(item, data_dir):
166
+ msgs = []
167
+ if item.get("system"):
168
+ msgs.append({"role": "system", "content": item["system"]})
169
+ msgs.append({"role": "user", "content": item["user"]})
170
+ return msgs, item["tools"]
171
+
172
+
173
+ def _val_match(got, allowed):
174
+ for a in allowed:
175
+ if isinstance(a, (int, float)) and not isinstance(a, bool):
176
+ try:
177
+ if abs(float(got) - float(a)) < 1e-6:
178
+ return True
179
+ except (TypeError, ValueError):
180
+ pass
181
+ elif isinstance(a, str) and isinstance(got, str):
182
+ if got.strip().lower() == a.strip().lower():
183
+ return True
184
+ elif got == a:
185
+ return True
186
+ return False
187
+
188
+
189
+ def _call_match(call, gold):
190
+ if call.get("name") != gold["name"]:
191
+ return False
192
+ args = call.get("arguments") or {}
193
+ if not isinstance(args, dict):
194
+ return False
195
+ spec = gold["arguments"] # name -> list of allowed values; [] or containing "__optional__"
196
+ for k, allowed in spec.items():
197
+ optional = "__optional__" in allowed
198
+ allowed = [a for a in allowed if a != "__optional__"]
199
+ if k not in args:
200
+ if not optional:
201
+ return False
202
+ continue
203
+ if allowed and not _val_match(args[k], allowed):
204
+ return False
205
+ return all(k in spec for k in args) # no hallucinated params
206
+
207
+
208
+ def tools_score(item, resp):
209
+ calls, source, fmt = calls_from_message(resp["message"] or {"content": resp["content"]}, item["tools"])
210
+ gold = item["gold_calls"]
211
+ if not gold: # irrelevance: must NOT call any tool
212
+ correct = len(calls) == 0
213
+ else:
214
+ remaining = list(calls)
215
+ correct = len(calls) == len(gold)
216
+ for g in gold: # order-insensitive matching for parallel calls
217
+ hit = next((c for c in remaining if _call_match(c, g)), None)
218
+ if hit is None:
219
+ correct = False
220
+ break
221
+ remaining.remove(hit)
222
+ names_ok = sorted(c.get("name") or "" for c in calls) == sorted(g["name"] for g in gold)
223
+ return {"pred": calls, "correct": correct, "names_correct": names_ok, "parse_source": source,
224
+ "format": fmt}
225
+
226
+
227
+ # ============================================================================ VQA (TextVQA)
228
+ VQA_SUFFIX = "\nAnswer the question using a single word or phrase."
229
+ _CONTRACT = {"dont": "don't", "doesnt": "doesn't", "isnt": "isn't", "cant": "can't", "wont": "won't",
230
+ "im": "i'm", "ive": "i've", "youre": "you're", "thats": "that's"}
231
+ _NUMW = {"none": "0", "zero": "0", "one": "1", "two": "2", "three": "3", "four": "4", "five": "5",
232
+ "six": "6", "seven": "7", "eight": "8", "nine": "9", "ten": "10"}
233
+ _ART = {"a", "an", "the"}
234
+
235
+
236
+ def vqa_norm(s: str) -> str:
237
+ s = (s or "").strip().lower().replace("\n", " ").replace("\t", " ")
238
+ s = s.rstrip(".")
239
+ s = re.sub(r"(?<=\d),(?=\d)", "", s)
240
+ s = re.sub(r"[;/\[\]\"{}()=+\\_\-><@`,?!*#%^&$:~|]", " ", s)
241
+ s = re.sub(r"(?<!\d)\.(?!\d)", "", s)
242
+ words = [_NUMW.get(w, _CONTRACT.get(w, w)) for w in s.split()]
243
+ return " ".join(w for w in words if w not in _ART)
244
+
245
+
246
+ def vqa_messages(item, data_dir):
247
+ img_path = Path(data_dir) / item["image"]
248
+ b64 = base64.b64encode(img_path.read_bytes()).decode()
249
+ return [{"role": "user", "content": [
250
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}},
251
+ {"type": "text", "text": item["question"] + VQA_SUFFIX},
252
+ ]}], None
253
+
254
+
255
+ def vqa_score(item, resp):
256
+ pred = vqa_norm(strip_think(resp["content"]).split("\n")[0])
257
+ golds = [vqa_norm(a) for a in item["answers"]]
258
+ matches = sum(1 for g in golds if g == pred)
259
+ soft = min(1.0, matches / 3.0)
260
+ # primary binary metric (relaxed): normalized exact match with >=2 of the 10 annotator answers
261
+ # (robust to a single noisy annotation); standard VQA soft accuracy min(1, m/3) also recorded.
262
+ return {"pred": pred, "correct": matches >= 2 and bool(pred), "vqa_soft_acc": soft,
263
+ "any_annotator_match": matches >= 1}
264
+
265
+
266
+ TASKS = {
267
+ "ifeval": (ifeval_messages, ifeval_score),
268
+ "gsm8k": (gsm8k_messages, gsm8k_score),
269
+ "mmlu_pro": (mmlu_pro_messages, mmlu_pro_score),
270
+ "humaneval": (humaneval_messages, humaneval_score),
271
+ "tools": (tools_messages, tools_score),
272
+ "vqa": (vqa_messages, vqa_score),
273
+ }
274
+
275
+
276
+ def agreement_key(task: str, scored: dict, resp: dict):
277
+ """Label-free comparable answer used for reference agreement."""
278
+ if task == "ifeval":
279
+ return strip_think(resp.get("content", "")).strip()
280
+ if task == "humaneval":
281
+ return (scored.get("pred") or "").strip()
282
+ if task == "tools":
283
+ return json.dumps(scored.get("pred"), sort_keys=True, default=str)
284
+ return scored.get("pred")
evaluation/harness/test_harness.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Offline tests for scorers + end-to-end harness against eval/mock_server.py (no GPU/model needed).
3
+
4
+ .venv-eval/bin/python eval/test_harness.py
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ import time
13
+ from pathlib import Path
14
+
15
+ HERE = Path(__file__).resolve().parent
16
+ sys.path.insert(0, str(HERE))
17
+ import ifeval_lite # noqa: E402
18
+ from common import parse_xml_tool_calls, read_jsonl, strip_think, wilson_ci # noqa: E402
19
+ from tasks import (extract_code, gsm_extract, humaneval_score, mc_extract, run_python_sandboxed, # noqa: E402
20
+ tools_score, vqa_norm, vqa_score)
21
+
22
+ PY = sys.executable
23
+ FAILS = []
24
+
25
+
26
+ def check(cond, msg):
27
+ if not cond:
28
+ FAILS.append(msg)
29
+ print("FAIL:", msg)
30
+
31
+
32
+ def unit():
33
+ lo, hi = wilson_ci(50, 100)
34
+ check(abs(lo - 0.4038) < 1e-3 and abs(hi - 0.5962) < 1e-3, f"wilson {lo},{hi}")
35
+ check(strip_think("<think>\nabc\n</think>\n\nHi") == "Hi", "strip_think")
36
+ # GSM8K
37
+ check(gsm_extract("so 3+4=7.\nThe final answer is 1,234.") == "1234", "gsm final")
38
+ check(gsm_extract("The final answer is $18.00") == "18", "gsm dollars")
39
+ check(gsm_extract("answer \\boxed{42}") == "42", "gsm boxed")
40
+ check(gsm_extract("it is -3.5 or so") == "-3.5", "gsm last num")
41
+ # MC
42
+ check(mc_extract("blah (B) ... The answer is (C).", 4) == "C", "mc 1")
43
+ check(mc_extract("The answer is J", 10) == "J", "mc 2")
44
+ check(mc_extract("The answer is (J)", 4) is None, "mc invalid letter")
45
+ # IFEval
46
+ C = ifeval_lite.check
47
+ check(C("hello world", ["punctuation:no_comma"], [{}]) == [True], "no_comma +")
48
+ check(C("hello, world", ["punctuation:no_comma"], [{}]) == [False], "no_comma -")
49
+ check(C("<<My Title>> text", ["detectable_format:title"], [{}]) == [True], "title")
50
+ check(C("* a\n* b\n- c", ["detectable_format:number_bullet_lists"], [{"num_bullets": 3}]) == [True], "bullets")
51
+ check(C('```json\n{"a": 1}\n```', ["detectable_format:json_format"], [{}]) == [True], "json")
52
+ check(C("p1\n***\np2", ["length_constraints:number_paragraphs"], [{"num_paragraphs": 2}]) == [True], "paras")
53
+ check(C("one two three", ["length_constraints:number_words"], [{"num_words": 5, "relation": "less than"}]) == [True], "words")
54
+ check(C("THIS IS ALL CAPS ENGLISH TEXT ABOUT THE WEATHER TODAY", ["change_case:english_capital"], [{}]) == [True], "caps")
55
+ check(C("text\nP.S. bye", ["detectable_content:postscript"], [{"postscript_marker": "P.S."}]) == [True], "ps")
56
+ check(C("a ****** b", ["combination:two_responses"], [{}]) == [True], "two resp")
57
+ check(C("Is it? My answer is yes.", ["detectable_format:constrained_response"], [{}]) == [True], "constrained")
58
+ check(C("Das ist ein deutscher Satz über das Wetter in Berlin heute.", ["language:response_language"],
59
+ [{"language": "de"}]) == [True], "lang")
60
+ check(C("We like apples", ["keywords:forbidden_words"], [{"forbidden_words": ["apples"]}]) == [False], "forbidden")
61
+ # Sandbox
62
+ ok, _ = run_python_sandboxed("assert 1+1==2\n")
63
+ check(ok, "sandbox pass")
64
+ ok, _ = run_python_sandboxed("assert 1+1==3\n")
65
+ check(not ok, "sandbox fail")
66
+ t = time.time()
67
+ ok, err = run_python_sandboxed("while True: pass\n", timeout=2)
68
+ check(not ok and time.time() - t < 6, f"sandbox timeout {err}")
69
+ ok, _ = run_python_sandboxed("import os; open('/tmp/../' + 'x','r')\n") # expected to fail cleanly
70
+ he = read_jsonl(HERE / "data/humaneval.jsonl")[0]
71
+ r = humaneval_score(he, {"content": f"```python\n{he['prompt']}{he['canonical_solution']}```"})
72
+ check(r["correct"], "humaneval canonical passes")
73
+ r = humaneval_score(he, {"content": f"```python\n{he['canonical_solution']}```"}) # body-only
74
+ check(r["correct"], "humaneval body-only passes")
75
+ check("def " in extract_code("no code", he), "extract fallback")
76
+ # XML tool-call parser (format from chat_template.jinja)
77
+ tools = [{"type": "function", "function": {"name": "f", "parameters": {"type": "object", "properties": {
78
+ "s": {"type": "string"}, "n": {"type": "integer"}, "b": {"type": "boolean"}, "o": {"type": "object"}}}}}]
79
+ txt = ("Sure.\n\n<tool_call>\n<function=f>\n<parameter=s>\n42\n</parameter>\n<parameter=n>\n7\n</parameter>\n"
80
+ "<parameter=b>\nTrue\n</parameter>\n<parameter=o>\n{\"k\": [1, 2]}\n</parameter>\n</function>\n</tool_call>")
81
+ calls, rep = parse_xml_tool_calls(txt, tools)
82
+ check(calls == [{"name": "f", "arguments": {"s": "42", "n": 7, "b": True, "o": {"k": [1, 2]}}}], f"xml {calls}")
83
+ check(rep["well_formed"] and not rep["suffix_after_last_call"], f"xml rep {rep}")
84
+ calls, rep = parse_xml_tool_calls("<tool_call>\n<function=f>\n<parameter=s>\nx", tools)
85
+ check(calls == [] and not rep["well_formed"], "xml truncated")
86
+ tc = read_jsonl(HERE / "data/tools.jsonl")
87
+ par = next(c for c in tc if c["id"] == "tool_parallel_03")
88
+ msg = {"content": None, "tool_calls": [
89
+ {"function": {"name": "set_timer", "arguments": '{"minutes": 10}'}},
90
+ {"function": {"name": "set_light_state", "arguments": '{"room": "Bedroom", "on": false}'}}]}
91
+ check(tools_score(par, {"message": msg, "content": ""})["correct"], "tools parallel order-insensitive")
92
+ msg["tool_calls"][0]["function"]["arguments"] = '{"minutes": 10, "bogus": 1}'
93
+ check(not tools_score(par, {"message": msg, "content": ""})["correct"], "tools hallucinated param")
94
+ irr = next(c for c in tc if c["category"] == "irrelevance")
95
+ check(tools_score(irr, {"message": {"content": "Jane Austen"}, "content": "Jane Austen"})["correct"], "irrelevance")
96
+ # VQA
97
+ check(vqa_norm("The Two Dogs.") == "2 dogs", f"vqa norm {vqa_norm('The Two Dogs.')}")
98
+ v = {"answers": ["dakota"] * 6 + ["x"] * 4}
99
+ check(vqa_score(v, {"content": "Dakota"})["correct"], "vqa")
100
+ check(not vqa_score({"answers": ["a1"] + ["z"] * 9}, {"content": "a1"})["correct"], "vqa single-annotator")
101
+
102
+
103
+ def e2e():
104
+ with tempfile.TemporaryDirectory() as td:
105
+ procs = []
106
+ try:
107
+ for port, extra in ((18991, []), (18992, ["--tool-mode", "xml"]), (18993, ["--corrupt-every", "5"])):
108
+ procs.append(subprocess.Popen([PY, "-I", str(HERE / "mock_server.py"), "--port", str(port), *extra],
109
+ stdout=subprocess.DEVNULL))
110
+ time.sleep(2.5)
111
+ for port, name in ((18991, "gold"), (18992, "xml"), (18993, "corrupt")):
112
+ subprocess.run([PY, "-I", str(HERE / "run_eval.py"), "--base-url", f"http://127.0.0.1:{port}/v1",
113
+ "--model", "mock", "--out", f"{td}/{name}", "--concurrency", "8"], check=True)
114
+ g = json.load(open(f"{td}/gold/summary.json"))["tasks"]
115
+ for t in ("gsm8k", "mmlu_pro", "humaneval", "tools", "vqa"):
116
+ check(g[t]["accuracy"] == 1.0, f"gold run {t} acc={g[t]['accuracy']}")
117
+ x = json.load(open(f"{td}/xml/summary.json"))["tasks"]["tools"]
118
+ check(x["accuracy"] == 1.0 and x["parse_source_counts"].get("fallback_xml") == 17,
119
+ f"xml fallback {x}")
120
+ check(x["format_well_formed"] == 1.0, "xml well formed")
121
+ out = subprocess.run([PY, "-I", str(HERE / "compare.py"), f"{td}/gold", f"{td}/corrupt"],
122
+ check=True, capture_output=True, text=True).stdout
123
+ print(out)
124
+ cmp_ = json.load(open(f"{td}/corrupt/compare_vs_ref.json"))
125
+ o = cmp_["overall_micro"]
126
+ check(o["delta"] < 0 and o["delta_boot95"][1] < 0 and o["ref_agreement"] < 1, f"compare {o}")
127
+ check(cmp_["tasks"]["ifeval"]["ref_agreement"] == 1.0, "ifeval agreement identical")
128
+ pb = subprocess.run([PY, "-I", str(HERE / "perf_bench.py"), "--base-url", "http://127.0.0.1:18991/v1",
129
+ "--model", "mock", "--concurrency", "1,4", "--num-requests", "4",
130
+ "--input-tokens", "256", "--output-tokens", "32", "--out", f"{td}/perf.json"],
131
+ capture_output=True, text=True)
132
+ check(pb.returncode == 0, f"perf_bench rc {pb.stderr[-800:]}")
133
+ if pb.returncode == 0:
134
+ p = json.load(open(f"{td}/perf.json"))
135
+ lv = p["levels"][-1]
136
+ check(lv["n_ok"] == 4 and lv["output_tokens_mean"] == 32, f"perf {lv}")
137
+ finally:
138
+ for p in procs:
139
+ p.terminate()
140
+
141
+
142
+ if __name__ == "__main__":
143
+ unit()
144
+ if "--unit-only" not in sys.argv:
145
+ e2e()
146
+ print("ALL OK" if not FAILS else f"{len(FAILS)} FAILURES")
147
+ sys.exit(1 if FAILS else 0)
evaluation/harness/tool_cases.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """20 hand-written function-calling cases (provenance: authored for this project, 2026-10-30,
2
+ released under Apache-2.0 alongside the harness; style modelled on BFCL categories
3
+ simple / multiple / parallel / irrelevance; no text copied from BFCL).
4
+
5
+ gold_calls: list of {name, arguments: {param: [allowed values...]}}; "__optional__" in the list
6
+ means the parameter may be omitted. Empty gold_calls => the model must NOT call a tool.
7
+ """
8
+
9
+
10
+ def fn(name, desc, props, required):
11
+ return {"type": "function", "function": {"name": name, "description": desc, "parameters": {
12
+ "type": "object", "properties": props, "required": required}}}
13
+
14
+
15
+ S = lambda d, **kw: {"type": "string", "description": d, **kw} # noqa: E731
16
+ I = lambda d: {"type": "integer", "description": d} # noqa: E731
17
+ N = lambda d: {"type": "number", "description": d} # noqa: E731
18
+ B = lambda d: {"type": "boolean", "description": d} # noqa: E731
19
+
20
+ WEATHER = fn("get_current_weather", "Get the current weather for a city.",
21
+ {"city": S("City name, e.g. 'Paris'"), "unit": S("Temperature unit", enum=["celsius", "fahrenheit"])},
22
+ ["city"])
23
+ FX = fn("convert_currency", "Convert an amount between currencies using today's rate.",
24
+ {"amount": N("Amount to convert"), "from_currency": S("ISO 4217 code"), "to_currency": S("ISO 4217 code")},
25
+ ["amount", "from_currency", "to_currency"])
26
+ EMAIL = fn("send_email", "Send an email.",
27
+ {"to": S("Recipient email address"), "subject": S("Subject line"), "body": S("Plain-text body")},
28
+ ["to", "subject", "body"])
29
+ FLIGHTS = fn("search_flights", "Search one-way flights.",
30
+ {"origin": S("IATA airport code"), "destination": S("IATA airport code"),
31
+ "date": S("Departure date YYYY-MM-DD"), "passengers": I("Number of passengers")},
32
+ ["origin", "destination", "date"])
33
+ TIMER = fn("set_timer", "Start a countdown timer.", {"minutes": I("Duration in minutes"), "label": S("Optional label")},
34
+ ["minutes"])
35
+ STOCK = fn("get_stock_price", "Get the latest stock price for a ticker symbol.", {"ticker": S("Ticker, e.g. AAPL")},
36
+ ["ticker"])
37
+ TRANSLATE = fn("translate_text", "Translate text to a target language.",
38
+ {"text": S("Text to translate"), "target_language": S("ISO 639-1 code, e.g. 'de'")},
39
+ ["text", "target_language"])
40
+ CAL = fn("create_calendar_event", "Create a calendar event.",
41
+ {"title": S("Event title"), "date": S("YYYY-MM-DD"), "start_time": S("HH:MM 24h"),
42
+ "duration_minutes": I("Length in minutes")}, ["title", "date", "start_time"])
43
+ SQL = fn("run_sql_query", "Run a read-only SQL query against the analytics warehouse.",
44
+ {"query": S("SQL SELECT statement")}, ["query"])
45
+ FILES = fn("list_files", "List files in a directory.", {"path": S("Directory path"), "recursive": B("Recurse")},
46
+ ["path"])
47
+ BMI = fn("calculate_bmi", "Calculate body-mass index.", {"weight_kg": N("Weight in kg"), "height_m": N("Height in metres")},
48
+ ["weight_kg", "height_m"])
49
+ LIGHT = fn("set_light_state", "Turn a smart light on/off and set brightness.",
50
+ {"room": S("Room name"), "on": B("Power state"), "brightness": I("0-100")}, ["room", "on"])
51
+ TICKET = fn("create_support_ticket", "Open a customer-support ticket.",
52
+ {"customer_id": S("Customer id"), "priority": S("Priority", enum=["low", "medium", "high"]),
53
+ "summary": S("Short summary")}, ["customer_id", "priority", "summary"])
54
+ TZ = fn("get_time_in_timezone", "Get the current local time for an IANA timezone.", {"timezone": S("e.g. Asia/Tokyo")},
55
+ ["timezone"])
56
+
57
+ O = "__optional__"
58
+ CASES = [
59
+ # ---------------- simple (single tool available, single call)
60
+ dict(id="tool_simple_01", category="simple", tools=[WEATHER], user="What's the weather like in Lisbon right now? Use celsius.",
61
+ gold_calls=[{"name": "get_current_weather", "arguments": {"city": ["Lisbon", "Lisbon, Portugal"], "unit": ["celsius"]}}]),
62
+ dict(id="tool_simple_02", category="simple", tools=[FX], user="How much is 250 US dollars in euros?",
63
+ gold_calls=[{"name": "convert_currency", "arguments": {"amount": [250], "from_currency": ["USD"], "to_currency": ["EUR"]}}]),
64
+ dict(id="tool_simple_03", category="simple", tools=[TIMER], user="Set a 25 minute timer labelled pomodoro.",
65
+ gold_calls=[{"name": "set_timer", "arguments": {"minutes": [25], "label": ["pomodoro", "Pomodoro"]}}]),
66
+ dict(id="tool_simple_04", category="simple", tools=[STOCK], user="What's NVIDIA trading at? (ticker NVDA)",
67
+ gold_calls=[{"name": "get_stock_price", "arguments": {"ticker": ["NVDA"]}}]),
68
+ dict(id="tool_simple_05", category="simple", tools=[BMI], user="I weigh 72.5 kg and I'm 1.80 m tall. What's my BMI?",
69
+ gold_calls=[{"name": "calculate_bmi", "arguments": {"weight_kg": [72.5], "height_m": [1.8]}}]),
70
+ dict(id="tool_simple_06", category="simple", tools=[FILES], user="List everything under /var/log recursively.",
71
+ gold_calls=[{"name": "list_files", "arguments": {"path": ["/var/log", "/var/log/"], "recursive": [True]}}]),
72
+ dict(id="tool_simple_07", category="simple", tools=[TZ], user="What time is it in Tokyo right now? The IANA zone is Asia/Tokyo.",
73
+ gold_calls=[{"name": "get_time_in_timezone", "arguments": {"timezone": ["Asia/Tokyo"]}}]),
74
+ dict(id="tool_simple_08", category="simple", tools=[LIGHT], user="Turn on the kitchen light at 40% brightness.",
75
+ gold_calls=[{"name": "set_light_state", "arguments": {"room": ["kitchen"], "on": [True], "brightness": [40]}}]),
76
+ # ---------------- multiple (several tools available, pick one)
77
+ dict(id="tool_multiple_01", category="multiple", tools=[WEATHER, FLIGHTS, FX, TIMER],
78
+ user="Find me flights from SFO to JFK on 2026-12-03 for 2 passengers.",
79
+ gold_calls=[{"name": "search_flights", "arguments": {"origin": ["SFO"], "destination": ["JFK"], "date": ["2026-12-03"], "passengers": [2]}}]),
80
+ dict(id="tool_multiple_02", category="multiple", tools=[EMAIL, CAL, TICKET],
81
+ user="Email bob@example.com with subject 'Lunch' and body 'Are we still on for noon tomorrow?'",
82
+ gold_calls=[{"name": "send_email", "arguments": {"to": ["bob@example.com"], "subject": ["Lunch"], "body": ["Are we still on for noon tomorrow?"]}}]),
83
+ dict(id="tool_multiple_03", category="multiple", tools=[TRANSLATE, STOCK, WEATHER],
84
+ user="Translate 'Where is the train station?' into German.",
85
+ gold_calls=[{"name": "translate_text", "arguments": {"text": ["Where is the train station?"], "target_language": ["de"]}}]),
86
+ dict(id="tool_multiple_04", category="multiple", tools=[CAL, TIMER, EMAIL],
87
+ user="Put 'Quarterly review' on my calendar for 2026-11-14 at 15:30, lasting 90 minutes.",
88
+ gold_calls=[{"name": "create_calendar_event", "arguments": {"title": ["Quarterly review"], "date": ["2026-11-14"], "start_time": ["15:30"], "duration_minutes": [90]}}]),
89
+ dict(id="tool_multiple_05", category="multiple", tools=[SQL, FILES, STOCK],
90
+ user="Run this query on the warehouse: SELECT COUNT(*) FROM orders",
91
+ gold_calls=[{"name": "run_sql_query", "arguments": {"query": ["SELECT COUNT(*) FROM orders", "SELECT COUNT(*) FROM orders;"]}}]),
92
+ dict(id="tool_multiple_06", category="multiple", tools=[TICKET, EMAIL, LIGHT],
93
+ system="You are a support agent for Acme Corp.",
94
+ user="Customer C-1042 says their router keeps rebooting. Open a high priority ticket with summary 'Router reboot loop'.",
95
+ gold_calls=[{"name": "create_support_ticket", "arguments": {"customer_id": ["C-1042"], "priority": ["high"], "summary": ["Router reboot loop"]}}]),
96
+ # ---------------- parallel (two calls expected)
97
+ dict(id="tool_parallel_01", category="parallel", tools=[WEATHER],
98
+ user="What's the current weather in Oslo and in Cairo? Use celsius for both.",
99
+ gold_calls=[{"name": "get_current_weather", "arguments": {"city": ["Oslo"], "unit": ["celsius"]}},
100
+ {"name": "get_current_weather", "arguments": {"city": ["Cairo"], "unit": ["celsius"]}}]),
101
+ dict(id="tool_parallel_02", category="parallel", tools=[STOCK, FX],
102
+ user="Get me the stock prices of AAPL and MSFT.",
103
+ gold_calls=[{"name": "get_stock_price", "arguments": {"ticker": ["AAPL"]}},
104
+ {"name": "get_stock_price", "arguments": {"ticker": ["MSFT"]}}]),
105
+ dict(id="tool_parallel_03", category="parallel", tools=[TIMER, LIGHT],
106
+ user="Turn off the bedroom light and set a 10 minute timer.",
107
+ gold_calls=[{"name": "set_light_state", "arguments": {"room": ["bedroom"], "on": [False], "brightness": [O, 0]}},
108
+ {"name": "set_timer", "arguments": {"minutes": [10], "label": [O]}}]),
109
+ # ---------------- irrelevance (tools present but none applicable)
110
+ dict(id="tool_irrelevance_01", category="irrelevance", tools=[WEATHER, FX],
111
+ user="Who wrote the novel 'Pride and Prejudice'?", gold_calls=[]),
112
+ dict(id="tool_irrelevance_02", category="irrelevance", tools=[STOCK, TIMER],
113
+ user="What is 17 multiplied by 23?", gold_calls=[]),
114
+ dict(id="tool_irrelevance_03", category="irrelevance", tools=[FLIGHTS, CAL],
115
+ user="Write a two-line poem about autumn leaves.", gold_calls=[]),
116
+ ]
evaluation/hf_bf16_checks.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_dir": "upstream/SenseNova-Loop-9B-Preview",
3
+ "torch": "2.11.0+cu130",
4
+ "load_s": 12.2,
5
+ "gpu_mem_after_load_GiB": 17.54,
6
+ "loop_plan_len": 56,
7
+ "mhc_param_count": 96,
8
+ "mhc_all_fp32": true,
9
+ "layer_param_dtypes": [
10
+ "torch.bfloat16"
11
+ ],
12
+ "text_mult": {
13
+ "prompt_tokens": 27,
14
+ "gen_tokens": 3,
15
+ "seconds": 1.61,
16
+ "text": "91<|im_end|>"
17
+ },
18
+ "loop_calls_per_layer": {
19
+ "0": 3,
20
+ "1": 3,
21
+ "2": 3,
22
+ "3": 3,
23
+ "4": 6,
24
+ "5": 6,
25
+ "6": 6,
26
+ "7": 6,
27
+ "8": 6,
28
+ "9": 6,
29
+ "10": 6,
30
+ "11": 6,
31
+ "12": 6,
32
+ "13": 6,
33
+ "14": 6,
34
+ "15": 6,
35
+ "16": 6,
36
+ "17": 6,
37
+ "18": 6,
38
+ "19": 6,
39
+ "20": 6,
40
+ "21": 6,
41
+ "22": 6,
42
+ "23": 6,
43
+ "24": 6,
44
+ "25": 6,
45
+ "26": 6,
46
+ "27": 6,
47
+ "28": 3,
48
+ "29": 3,
49
+ "30": 3,
50
+ "31": 3
51
+ },
52
+ "looped_layers_called_twice": true,
53
+ "text_haiku": {
54
+ "prompt_tokens": 21,
55
+ "gen_tokens": 20,
56
+ "seconds": 5.08,
57
+ "text": "Green sphere, white seam,\nBounces high in summer sun,\nA game begins.<|im_end|>"
58
+ },
59
+ "thinking": {
60
+ "prompt_tokens": 32,
61
+ "gen_tokens": 262,
62
+ "seconds": 66.44,
63
+ "text": "The train goes 60 km in 45 minutes.\n\nTo find the speed in km/h, I need to convert 45 minutes to hours.\n\n45 minutes = 45/60 hours = 3/4 hours\n\nSpeed = Distance / Time = 60 km / (3/4 hours) = 60 × 4/3 = 80 km/h\n</think>\n\n# Finding the Train's Speed\n\n## Given Information\n- Distance = 60 km\n- Time = 45 minutes\n\n## Solution\n\n**Step 1:** Convert time to hours.\n\n$$45 \\text{ minutes} = \\frac{45}{60} \\text{ hours} = \\frac{3}{4} \\text{ hours}$$\n\n**Step 2:** Use the speed formula.\n\n$$\\text{Speed} = \\frac{\\text{Distance}}{\\text{Time}} = \\frac{60 \\text{ km}}{\\frac{3}{4} \\text{ hr}}$$\n\n**Step 3:** Calculate.\n\n$$\\text{Speed} = 60 \\times \\frac{4}{3} = \\boxed{80 \\text{ km/h}}$$<|im_end|>"
64
+ },
65
+ "tool_call": {
66
+ "prompt_tokens": 300,
67
+ "gen_tokens": 39,
68
+ "seconds": 10.13,
69
+ "text": "<tool_call>\n<function=get_weather>\n<parameter=city>\nParis\n</parameter>\n<parameter=unit>\ncelsius\n</parameter>\n</function>\n</tool_call><|im_end|>",
70
+ "has_tool_call_xml": true
71
+ },
72
+ "image": {
73
+ "prompt_tokens": 1013,
74
+ "gen_tokens": 61,
75
+ "seconds": 17.08,
76
+ "text": "A yellow mobile tyre fitting service van, branded with \"ATS\" and \"Euromaster\" logos, is driving on a road with a driver visible inside wearing a blue shirt. Behind it, a white van with a red warning light on top and a dark car are also present on the road.<|im_end|>"
77
+ },
78
+ "cache_consistency": {
79
+ "n": 64,
80
+ "match": 64,
81
+ "first_mismatch": null,
82
+ "finite_logits": true
83
+ },
84
+ "peak_gpu_mem_GiB": 17.98
85
+ }
evaluation/kernel_summary.txt ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ distinct kernels: 97 total GPU us: 553403.1010000036
2
+ {'other': 273.4, 'GEMM(other)': 81.8, 'FP4-related': 198.2} (ms)
3
+ --- top 25 kernels by time ---
4
+ 130.2 ms 672x [FP4-related] cutlass3x_sm120_bstensorop_s16864gemm_block_scaled_ue4m3xe2m1_ue4m3xe2m1_f32_bf16_bf16_128x128x256_1x1x1_0_tnn_align32_o_vs16_bias_bf16_relu
5
+ 71.5 ms 216x [GEMM(other)] std::enable_if<!(false), void>::type internal::gemvx::kernel<int, int, __nv_bfloat16, __nv_bfloat16, __nv_bfloat16, float, false, true, true, false, 7
6
+ 67.4 ms 672x [FP4-related] cutlass3x_sm120_bstensorop_s16864gemm_block_scaled_ue4m3xe2m1_ue4m3xe2m1_f32_bf16_bf16_128x128x256_1x1x1_0_tnn_align32_o_vs16_bias_bf16_relu_stream_k
7
+ 41.3 ms 46080x [other] void at::native::elementwise_kernel<128, 2, at::native::gpu_kernel_impl_nocast<at::native::CUDAFunctor_add<float> >(at::TensorIteratorBase&, at::nativ
8
+ 23.7 ms 23616x [other] void at::native::vectorized_elementwise_kernel<4, at::native::exp_kernel_cuda(at::TensorIteratorBase&)::{lambda()#2}::operator()() const::{lambda()#2}
9
+ 23.4 ms 23040x [other] void at::native::vectorized_elementwise_kernel<4, at::native::log_kernel_cuda(at::TensorIteratorBase&)::{lambda()#2}::operator()() const::{lambda()#2}
10
+ 22.8 ms 23040x [other] void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespace)::masked_fill_kernel(at::TensorIterator&, c10::Scalar const&)::{lam
11
+ 21.8 ms 23040x [other] void at::native::vectorized_elementwise_kernel<4, at::native::CUDAFunctor_add<float>, std::array<char*, 3ul>, false>(int, at::native::CUDAFunctor_add<
12
+ 21.4 ms 23040x [other] void at::native::vectorized_elementwise_kernel<4, at::native::AbsFunctor<float>, std::array<char*, 2ul>, false>(int, at::native::AbsFunctor<float>, st
13
+ 21.2 ms 11520x [other] void at::native::reduce_kernel<256, 2, at::native::ReduceOp<float, at::native::func_wrapper_t<float, at::native::MaxNanFunctor<float> >, unsigned int,
14
+ 21.0 ms 23040x [other] void at::native::vectorized_elementwise_kernel<4, at::native::AUnaryFunctor<float, float, bool, at::native::(anonymous namespace)::CompareEqFunctor<fl
15
+ 20.6 ms 11520x [other] void at::native::reduce_kernel<256, 2, at::native::ReduceOp<float, at::native::func_wrapper_t<float, at::native::sum_functor<float, float, float>::ope
16
+ 18.1 ms 11520x [other] void at::native::reduce_kernel<512, 1, at::native::ReduceOp<float, at::native::func_wrapper_t<float, at::native::MaxNanFunctor<float> >, unsigned int,
17
+ 17.2 ms 11520x [other] void at::native::reduce_kernel<512, 1, at::native::ReduceOp<float, at::native::func_wrapper_t<float, at::native::sum_functor<float, float, float>::ope
18
+ 3.4 ms 96x [GEMM(other)] void sgemm_largek_lds64<true, false, 5, 5, 4, 4, 4, 34>(float*, float const*, float const*, int, int, int, int, int, int, float const*, float const*,
19
+ 2.6 ms 210x [other] fused_recurrent_gated_delta_rule_packed_decode_kernel
20
+ 2.3 ms 1092x [other] void tensorrt_llm::kernels::quantize_with_block_size<(tensorrt_llm::BlockScaleQuantizationType)0, __nv_bfloat16, 16, false, false, false, false, std::
21
+ 1.7 ms 1728x [other] void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kernel_cuda(at::TensorIteratorBase&)::{lambda(float)#1}, std::array<char*,
22
+ 1.4 ms 480x [GEMM(other)] void dot_kernel<float, 128, 0, cublasDotParams<cublasGemvTensorStridedBatched<float const>, cublasGemvTensorStridedBatched<float> > >(cublasDotParams<
23
+ 1.3 ms 576x [other] void at::native::reduce_kernel<512, 1, at::native::ReduceOp<float, at::native::MeanOps<float, float, float, float>, unsigned int, float, 4, 4> >(at::n
24
+ 1.3 ms 577x [other] void at::native::unrolled_elementwise_kernel<at::native::direct_copy_kernel_cuda(at::TensorIteratorBase&)::{lambda()#3}::operator()() const::{lambda()
25
+ 1.3 ms 96x [GEMM(other)] void cutlass::Kernel2<cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_32x32_32x1_nn_align2>(cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_32x32_32x1_n
26
+ 1.2 ms 576x [other] void at::native::elementwise_kernel<128, 4, at::native::gpu_kernel_impl_nocast<at::native::BinaryFunctor<c10::BFloat16, c10::BFloat16, c10::BFloat16,
27
+ 1.1 ms 588x [GEMM(other)] kernel_cutlass_kernel_flashinfernormkernelsrmsnormRMSNormKernel_object_at__tensorptrbf16gmemalign128oi64409640961_tensorptrbf16gmemalign16o40961_tenso
28
+ 1.0 ms 480x [GEMM(other)] void cutlass::Kernel2<cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_16x16_32x1_nn_align2>(cutlass_80_wmma_tensorop_bf16_s161616gemm_bf16_16x16_32x1_n
evaluation/nvfp4_v1_mtp_release_probe.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mult": {
3
+ "content": "91",
4
+ "reasoning": "",
5
+ "tool_calls": null,
6
+ "usage": {
7
+ "prompt_tokens": 27,
8
+ "total_tokens": 30,
9
+ "completion_tokens": 3,
10
+ "prompt_tokens_details": null,
11
+ "reasoning_tokens": 0,
12
+ "completion_tokens_details": null
13
+ }
14
+ },
15
+ "haiku": {
16
+ "content": "Green sphere, white seam,\nBounces true on court's warm ground,\nA fleeting, bright joy.",
17
+ "reasoning": "",
18
+ "tool_calls": null,
19
+ "usage": {
20
+ "prompt_tokens": 21,
21
+ "total_tokens": 45,
22
+ "completion_tokens": 24,
23
+ "prompt_tokens_details": null,
24
+ "reasoning_tokens": 0,
25
+ "completion_tokens_details": null
26
+ }
27
+ },
28
+ "thinking": {
29
+ "content": "\n\n# Finding the Train's Speed\n\n**Given:**\n- Distance = 60 km\n- Time = 45 minutes\n\n**Step 1: Convert time to hours**\n\n45 minutes = 45/60 = **3/4 hour**\n\n**Step 2: Calculate speed**\n\n$$\\text{Speed} = \\frac{\\text{Distance}}{\\text{Time}} = \\frac{60 \\text{ km}}{3/4 \\text{ hour}} = 60 \\times \\frac{4}{3} = \\boxed{80 \\text{ km/h}}$$",
30
+ "reasoning": "The train goes 60 km in 45 minutes.\n\nTo find the speed in km/h:\n\nSpeed = Distance / Time\n\nTime = 45 minutes = 45/60 hours = 3/4 hours\n\nSpeed = 60 km / (3/4 hours) = 60 × 4/3 = 80 km/h\n",
31
+ "tool_calls": null,
32
+ "usage": {
33
+ "prompt_tokens": 32,
34
+ "total_tokens": 244,
35
+ "completion_tokens": 212,
36
+ "prompt_tokens_details": null,
37
+ "reasoning_tokens": 80,
38
+ "completion_tokens_details": {
39
+ "reasoning_tokens": 80
40
+ }
41
+ }
42
+ },
43
+ "tool": {
44
+ "content": "",
45
+ "reasoning": "",
46
+ "tool_calls": [
47
+ {
48
+ "id": "call_3bdd055af15c4337a2b9fe21",
49
+ "index": 0,
50
+ "type": "function",
51
+ "function": {
52
+ "name": "get_weather",
53
+ "arguments": "{\"city\": \"Paris\", \"unit\": \"celsius\"}"
54
+ }
55
+ }
56
+ ],
57
+ "usage": {
58
+ "prompt_tokens": 306,
59
+ "total_tokens": 345,
60
+ "completion_tokens": 39,
61
+ "prompt_tokens_details": null,
62
+ "reasoning_tokens": 0,
63
+ "completion_tokens_details": null
64
+ }
65
+ },
66
+ "image": {
67
+ "content": "A yellow mobile tyre fitting service van, branded with \"ATS\" and \"Euromaster\" logos, is driving on a road with its driver visible inside. Behind it, a white van and a dark car are also traveling on the same road.",
68
+ "reasoning": "",
69
+ "tool_calls": null,
70
+ "usage": {
71
+ "prompt_tokens": 1013,
72
+ "total_tokens": 1064,
73
+ "completion_tokens": 51,
74
+ "prompt_tokens_details": {
75
+ "cached_tokens": 0,
76
+ "image_tokens": 992
77
+ },
78
+ "reasoning_tokens": 0,
79
+ "completion_tokens_details": null
80
+ }
81
+ }
82
+ }
evaluation/nvfp4_v1_probe.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mult": {
3
+ "content": "91",
4
+ "reasoning": "",
5
+ "tool_calls": null,
6
+ "usage": {
7
+ "prompt_tokens": 27,
8
+ "total_tokens": 30,
9
+ "completion_tokens": 3,
10
+ "prompt_tokens_details": null,
11
+ "reasoning_tokens": 0,
12
+ "completion_tokens_details": null
13
+ }
14
+ },
15
+ "haiku": {
16
+ "content": "Green and white, it bounces true,\nA fleeting arc through summer air,\nA brief, bright, spinning joy.",
17
+ "reasoning": "",
18
+ "tool_calls": null,
19
+ "usage": {
20
+ "prompt_tokens": 21,
21
+ "total_tokens": 48,
22
+ "completion_tokens": 27,
23
+ "prompt_tokens_details": null,
24
+ "reasoning_tokens": 0,
25
+ "completion_tokens_details": null
26
+ }
27
+ },
28
+ "thinking": {
29
+ "content": "\n\n# Finding the Train's Speed\n\n**Given:**\n- Distance = 60 km\n- Time = 45 minutes\n\n**Step 1:** Convert time to hours.\n\n$$45 \\text{ minutes} = \\frac{45}{60} \\text{ hours} = \\frac{3}{4} \\text{ hours}$$\n\n**Step 2:** Calculate speed.\n\n$$\\text{Speed} = \\frac{\\text{Distance}}{\\text{Time}} = \\frac{60}{\\frac{3}{4}} = 60 \\times \\frac{4}{3} = \\boxed{80 \\text{ km/h}}$$",
30
+ "reasoning": "The train goes 60 km in 45 minutes.\n\nTo find the speed in km/h:\n\nSpeed = Distance / Time\n\nTime = 45 minutes = 45/60 hours = 3/4 hours\n\nSpeed = 60 km / (3/4 hours) = 60 × 4/3 = 80 km/h\n",
31
+ "tool_calls": null,
32
+ "usage": {
33
+ "prompt_tokens": 32,
34
+ "total_tokens": 258,
35
+ "completion_tokens": 226,
36
+ "prompt_tokens_details": null,
37
+ "reasoning_tokens": 80,
38
+ "completion_tokens_details": {
39
+ "reasoning_tokens": 80
40
+ }
41
+ }
42
+ },
43
+ "tool": {
44
+ "content": "",
45
+ "reasoning": "",
46
+ "tool_calls": [
47
+ {
48
+ "id": "call_f059ff08a40946de871cc501",
49
+ "index": 0,
50
+ "type": "function",
51
+ "function": {
52
+ "name": "get_weather",
53
+ "arguments": "{\"city\": \"Paris\", \"unit\": \"celsius\"}"
54
+ }
55
+ }
56
+ ],
57
+ "usage": {
58
+ "prompt_tokens": 306,
59
+ "total_tokens": 345,
60
+ "completion_tokens": 39,
61
+ "prompt_tokens_details": null,
62
+ "reasoning_tokens": 0,
63
+ "completion_tokens_details": null
64
+ }
65
+ },
66
+ "image": {
67
+ "content": "A yellow mobile tyre fitting service van, branded with \"ATS\" and \"Euromaster\" logos, is driving on a road with its amber warning lights on. Behind it, a white van with the license plate \"WG53 JNU\" and a dark car are also visible on the road.",
68
+ "reasoning": "",
69
+ "tool_calls": null,
70
+ "usage": {
71
+ "prompt_tokens": 1013,
72
+ "total_tokens": 1075,
73
+ "completion_tokens": 62,
74
+ "prompt_tokens_details": {
75
+ "cached_tokens": 0,
76
+ "image_tokens": 992
77
+ },
78
+ "reasoning_tokens": 0,
79
+ "completion_tokens_details": null
80
+ }
81
+ }
82
+ }
evaluation/perf_sglang_bf16.json ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "",
3
+ "model": "sensenova-loop-bf16",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "host": "tux",
6
+ "time": "2026-10-30T15:47:00-0700",
7
+ "config": {
8
+ "base_url": "http://127.0.0.1:30080/v1",
9
+ "model": "sensenova-loop-bf16",
10
+ "concurrency": "1,2,4,8",
11
+ "num_requests": 16,
12
+ "warmup": 2,
13
+ "input_tokens": 1024,
14
+ "output_tokens": 256,
15
+ "tokenizer": "upstream/SenseNova-Loop-9B-Preview/tokenizer.json",
16
+ "allow_prefix_cache": false,
17
+ "no_ignore_eos": false,
18
+ "timeout": 600,
19
+ "label": "",
20
+ "out": "eval/runs/perf_sglang_bf16.json",
21
+ "prompt_text_tokens": 1024,
22
+ "prompt_build": "tokenizer.json"
23
+ },
24
+ "levels": [
25
+ {
26
+ "concurrency": 1,
27
+ "n_requests": 16,
28
+ "n_ok": 16,
29
+ "n_errors": 0,
30
+ "errors": [],
31
+ "wall_s": 712.4458575863391,
32
+ "ttft_s": {
33
+ "p50": 0.7494839327409863,
34
+ "p90": 0.752388886641711,
35
+ "mean": 0.7490631011896767
36
+ },
37
+ "e2e_s": {
38
+ "p50": 44.54232191387564,
39
+ "p90": 44.74176003783941,
40
+ "mean": 44.52777633216465
41
+ },
42
+ "decode_tok_s_per_req": {
43
+ "p50": 5.82298062979468,
44
+ "p90": 5.857639877869307,
45
+ "mean": 5.8248643173510715
46
+ },
47
+ "itl_s": {
48
+ "p50": 0.17173366795531383,
49
+ "p90": 0.17253968634587877,
50
+ "mean": 0.17168066543298682
51
+ },
52
+ "output_tokens_mean": 256.0,
53
+ "output_tokens_min": 256,
54
+ "output_tokens_max": 256,
55
+ "prompt_tokens_mean": 1051.0,
56
+ "n_short_outputs": 0,
57
+ "aggregate_output_tok_s": 5.7492088084793425,
58
+ "requests_per_s": 0.02245784690812243,
59
+ "usage_reported": true
60
+ },
61
+ {
62
+ "concurrency": 2,
63
+ "n_requests": 16,
64
+ "n_ok": 16,
65
+ "n_errors": 0,
66
+ "errors": [],
67
+ "wall_s": 358.13540337421,
68
+ "ttft_s": {
69
+ "p50": 1.4381381664425135,
70
+ "p90": 1.493556933477521,
71
+ "mean": 1.451384060550481
72
+ },
73
+ "e2e_s": {
74
+ "p50": 44.76612635422498,
75
+ "p90": 45.01789597328752,
76
+ "mean": 44.76674999552779
77
+ },
78
+ "decode_tok_s_per_req": {
79
+ "p50": 5.888547972827849,
80
+ "p90": 5.919728871447871,
81
+ "mean": 5.8871749230165396
82
+ },
83
+ "itl_s": {
84
+ "p50": 0.16982132055997556,
85
+ "p90": 0.1708354555380841,
86
+ "mean": 0.16986371961846838
87
+ },
88
+ "output_tokens_mean": 256.0,
89
+ "output_tokens_min": 256,
90
+ "output_tokens_max": 256,
91
+ "prompt_tokens_mean": 1051.0,
92
+ "n_short_outputs": 0,
93
+ "aggregate_output_tok_s": 11.43701505466678,
94
+ "requests_per_s": 0.04467584005729211,
95
+ "usage_reported": true
96
+ },
97
+ {
98
+ "concurrency": 4,
99
+ "n_requests": 16,
100
+ "n_ok": 16,
101
+ "n_errors": 0,
102
+ "errors": [],
103
+ "wall_s": 187.37603424675763,
104
+ "ttft_s": {
105
+ "p50": 2.734584681224078,
106
+ "p90": 2.7569230226799846,
107
+ "mean": 2.7020455568563193
108
+ },
109
+ "e2e_s": {
110
+ "p50": 46.840107245370746,
111
+ "p90": 46.998931530397385,
112
+ "mean": 46.84355494793272
113
+ },
114
+ "decode_tok_s_per_req": {
115
+ "p50": 5.772806069829697,
116
+ "p90": 5.803642233569628,
117
+ "mean": 5.776986593431426
118
+ },
119
+ "itl_s": {
120
+ "p50": 0.17322622279947003,
121
+ "p90": 0.1738359775942038,
122
+ "mean": 0.1731030160166305
123
+ },
124
+ "output_tokens_mean": 256.0,
125
+ "output_tokens_min": 256,
126
+ "output_tokens_max": 256,
127
+ "prompt_tokens_mean": 1051.0,
128
+ "n_short_outputs": 0,
129
+ "aggregate_output_tok_s": 21.859785945762578,
130
+ "requests_per_s": 0.08538978885063507,
131
+ "usage_reported": true
132
+ },
133
+ {
134
+ "concurrency": 8,
135
+ "n_requests": 16,
136
+ "n_ok": 16,
137
+ "n_errors": 0,
138
+ "errors": [],
139
+ "wall_s": 102.59108853153884,
140
+ "ttft_s": {
141
+ "p50": 4.974256161600351,
142
+ "p90": 5.0004629092291,
143
+ "mean": 4.947074577212334
144
+ },
145
+ "e2e_s": {
146
+ "p50": 51.294219580013305,
147
+ "p90": 51.56152600981295,
148
+ "mean": 51.29352270014351
149
+ },
150
+ "decode_tok_s_per_req": {
151
+ "p50": 5.488342640760974,
152
+ "p90": 5.537196663306641,
153
+ "mean": 5.502280519349593
154
+ },
155
+ "itl_s": {
156
+ "p50": 0.18220517470811803,
157
+ "p90": 0.18259358133244163,
158
+ "mean": 0.18174918959711622
159
+ },
160
+ "output_tokens_mean": 256.0,
161
+ "output_tokens_min": 256,
162
+ "output_tokens_max": 256,
163
+ "prompt_tokens_mean": 1051.0,
164
+ "n_short_outputs": 0,
165
+ "aggregate_output_tok_s": 39.925495075927536,
166
+ "requests_per_s": 0.15595896514034194,
167
+ "usage_reported": true
168
+ }
169
+ ]
170
+ }
evaluation/perf_sglang_bf16_mtp.json ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "",
3
+ "model": "sensenova-loop-bf16",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "host": "tux",
6
+ "time": "2026-10-30T16:33:57-0700",
7
+ "config": {
8
+ "base_url": "http://127.0.0.1:30080/v1",
9
+ "model": "sensenova-loop-bf16",
10
+ "concurrency": "1,2,4,8",
11
+ "num_requests": 8,
12
+ "warmup": 2,
13
+ "input_tokens": 1024,
14
+ "output_tokens": 256,
15
+ "tokenizer": "upstream/SenseNova-Loop-9B-Preview/tokenizer.json",
16
+ "allow_prefix_cache": false,
17
+ "no_ignore_eos": false,
18
+ "timeout": 600,
19
+ "label": "",
20
+ "out": "eval/runs/perf_sglang_bf16_mtp.json",
21
+ "prompt_text_tokens": 1024,
22
+ "prompt_build": "tokenizer.json"
23
+ },
24
+ "levels": [
25
+ {
26
+ "concurrency": 1,
27
+ "n_requests": 8,
28
+ "n_ok": 8,
29
+ "n_errors": 0,
30
+ "errors": [],
31
+ "wall_s": 272.73132864851505,
32
+ "ttft_s": {
33
+ "p50": 0.7848195834085345,
34
+ "p90": 0.9759742130525411,
35
+ "mean": 0.8312031434616074
36
+ },
37
+ "e2e_s": {
38
+ "p50": 34.21052242349833,
39
+ "p90": 34.438923329673706,
40
+ "mean": 34.09125282650348
41
+ },
42
+ "decode_tok_s_per_req": {
43
+ "p50": 7.627452517131835,
44
+ "p90": 7.763279273008121,
45
+ "mean": 7.669396340870895
46
+ },
47
+ "itl_s": {
48
+ "p50": 0.1311053762084567,
49
+ "p90": 0.13172920723317885,
50
+ "mean": 0.13043050938103276
51
+ },
52
+ "output_tokens_mean": 256.0,
53
+ "output_tokens_min": 256,
54
+ "output_tokens_max": 256,
55
+ "prompt_tokens_mean": 1051.0,
56
+ "n_short_outputs": 0,
57
+ "aggregate_output_tok_s": 7.50922165835733,
58
+ "requests_per_s": 0.02933289710295832,
59
+ "usage_reported": true
60
+ },
61
+ {
62
+ "concurrency": 2,
63
+ "n_requests": 8,
64
+ "n_ok": 8,
65
+ "n_errors": 0,
66
+ "errors": [],
67
+ "wall_s": 138.61350492294878,
68
+ "ttft_s": {
69
+ "p50": 1.1976498886942863,
70
+ "p90": 1.6063555277884007,
71
+ "mean": 1.2973694901447743
72
+ },
73
+ "e2e_s": {
74
+ "p50": 34.72376540116966,
75
+ "p90": 36.45519359242171,
76
+ "mean": 34.5663175820373
77
+ },
78
+ "decode_tok_s_per_req": {
79
+ "p50": 7.653260406224514,
80
+ "p90": 8.09628260642012,
81
+ "mean": 7.677757556692128
82
+ },
83
+ "itl_s": {
84
+ "p50": 0.13067035353651235,
85
+ "p90": 0.13736926173243452,
86
+ "mean": 0.13046496708274763
87
+ },
88
+ "output_tokens_mean": 256.0,
89
+ "output_tokens_min": 256,
90
+ "output_tokens_max": 256,
91
+ "prompt_tokens_mean": 1051.0,
92
+ "n_short_outputs": 0,
93
+ "aggregate_output_tok_s": 14.774895138380808,
94
+ "requests_per_s": 0.05771443413430003,
95
+ "usage_reported": true
96
+ },
97
+ {
98
+ "concurrency": 4,
99
+ "n_requests": 8,
100
+ "n_ok": 8,
101
+ "n_errors": 0,
102
+ "errors": [],
103
+ "wall_s": 76.38755708001554,
104
+ "ttft_s": {
105
+ "p50": 2.5858272635377944,
106
+ "p90": 2.779972269944847,
107
+ "mean": 2.2637667186791077
108
+ },
109
+ "e2e_s": {
110
+ "p50": 36.801751386839896,
111
+ "p90": 39.171385447308424,
112
+ "mean": 37.270905990269966
113
+ },
114
+ "decode_tok_s_per_req": {
115
+ "p50": 7.2633816269398785,
116
+ "p90": 7.592177491148207,
117
+ "mean": 7.294118130503968
118
+ },
119
+ "itl_s": {
120
+ "p50": 0.1376772181057901,
121
+ "p90": 0.1427121850625411,
122
+ "mean": 0.13728188767883123
123
+ },
124
+ "output_tokens_mean": 256.0,
125
+ "output_tokens_min": 256,
126
+ "output_tokens_max": 256,
127
+ "prompt_tokens_mean": 1051.0,
128
+ "n_short_outputs": 0,
129
+ "aggregate_output_tok_s": 26.810649250829314,
130
+ "requests_per_s": 0.10472909863605201,
131
+ "usage_reported": true
132
+ },
133
+ {
134
+ "concurrency": 8,
135
+ "n_requests": 8,
136
+ "n_ok": 8,
137
+ "n_errors": 0,
138
+ "errors": [],
139
+ "wall_s": 44.81087386980653,
140
+ "ttft_s": {
141
+ "p50": 5.374245874583721,
142
+ "p90": 5.41043642014265,
143
+ "mean": 5.388463915442117
144
+ },
145
+ "e2e_s": {
146
+ "p50": 41.73927395185456,
147
+ "p90": 43.86301381308586,
148
+ "mean": 41.677275480818935
149
+ },
150
+ "decode_tok_s_per_req": {
151
+ "p50": 7.0119672437329355,
152
+ "p90": 7.471562651588895,
153
+ "mean": 7.0494192349641605
154
+ },
155
+ "itl_s": {
156
+ "p50": 0.1426148278529153,
157
+ "p90": 0.1509358527029262,
158
+ "mean": 0.14230802183349928
159
+ },
160
+ "output_tokens_mean": 256.0,
161
+ "output_tokens_min": 256,
162
+ "output_tokens_max": 256,
163
+ "prompt_tokens_mean": 1051.0,
164
+ "n_short_outputs": 0,
165
+ "aggregate_output_tok_s": 45.7031926212878,
166
+ "requests_per_s": 0.17852809617690546,
167
+ "usage_reported": true
168
+ }
169
+ ]
170
+ }
evaluation/perf_sglang_nvfp4_v1.json ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "host": "tux",
6
+ "time": "2026-10-30T17:00:57-0700",
7
+ "config": {
8
+ "base_url": "http://127.0.0.1:30080/v1",
9
+ "model": "sensenova-loop-nvfp4",
10
+ "concurrency": "1,2,4,8",
11
+ "num_requests": 16,
12
+ "warmup": 2,
13
+ "input_tokens": 1024,
14
+ "output_tokens": 256,
15
+ "tokenizer": "upstream/SenseNova-Loop-9B-Preview/tokenizer.json",
16
+ "allow_prefix_cache": false,
17
+ "no_ignore_eos": false,
18
+ "timeout": 600,
19
+ "label": "",
20
+ "out": "eval/runs/perf_sglang_nvfp4_v1.json",
21
+ "prompt_text_tokens": 1024,
22
+ "prompt_build": "tokenizer.json"
23
+ },
24
+ "levels": [
25
+ {
26
+ "concurrency": 1,
27
+ "n_requests": 16,
28
+ "n_ok": 16,
29
+ "n_errors": 0,
30
+ "errors": [],
31
+ "wall_s": 436.37869714573026,
32
+ "ttft_s": {
33
+ "p50": 0.5826272442936897,
34
+ "p90": 0.5880275997333229,
35
+ "mean": 0.5825185888679698
36
+ },
37
+ "e2e_s": {
38
+ "p50": 27.272629408165812,
39
+ "p90": 27.33341192267835,
40
+ "mean": 27.273564630304463
41
+ },
42
+ "decode_tok_s_per_req": {
43
+ "p50": 9.555136327634862,
44
+ "p90": 9.571729349173973,
45
+ "mean": 9.55383492852507
46
+ },
47
+ "itl_s": {
48
+ "p50": 0.10465575453697466,
49
+ "p90": 0.10489086669814937,
50
+ "mean": 0.10467025827383622
51
+ },
52
+ "output_tokens_mean": 256.0,
53
+ "output_tokens_min": 256,
54
+ "output_tokens_max": 256,
55
+ "prompt_tokens_mean": 1051.0,
56
+ "n_short_outputs": 0,
57
+ "aggregate_output_tok_s": 9.386342703690978,
58
+ "requests_per_s": 0.036665401186292884,
59
+ "usage_reported": true
60
+ },
61
+ {
62
+ "concurrency": 2,
63
+ "n_requests": 16,
64
+ "n_ok": 16,
65
+ "n_errors": 0,
66
+ "errors": [],
67
+ "wall_s": 232.5841392017901,
68
+ "ttft_s": {
69
+ "p50": 1.1243764483369887,
70
+ "p90": 1.1582226920872927,
71
+ "mean": 1.117659447598271
72
+ },
73
+ "e2e_s": {
74
+ "p50": 29.07659900886938,
75
+ "p90": 29.158572073560208,
76
+ "mean": 29.072850470547564
77
+ },
78
+ "decode_tok_s_per_req": {
79
+ "p50": 9.119639853361988,
80
+ "p90": 9.150102465833314,
81
+ "mean": 9.121841848607799
82
+ },
83
+ "itl_s": {
84
+ "p50": 0.1096534530486108,
85
+ "p90": 0.10988617092918823,
86
+ "mean": 0.10962762238854981
87
+ },
88
+ "output_tokens_mean": 256.0,
89
+ "output_tokens_min": 256,
90
+ "output_tokens_max": 256,
91
+ "prompt_tokens_mean": 1051.0,
92
+ "n_short_outputs": 0,
93
+ "aggregate_output_tok_s": 17.610831134303226,
94
+ "requests_per_s": 0.06879230911837197,
95
+ "usage_reported": true
96
+ },
97
+ {
98
+ "concurrency": 4,
99
+ "n_requests": 16,
100
+ "n_ok": 16,
101
+ "n_errors": 0,
102
+ "errors": [],
103
+ "wall_s": 122.35653864778578,
104
+ "ttft_s": {
105
+ "p50": 2.017746684141457,
106
+ "p90": 2.0371825736947358,
107
+ "mean": 2.0016646458534524
108
+ },
109
+ "e2e_s": {
110
+ "p50": 30.58990833349526,
111
+ "p90": 30.627535844687372,
112
+ "mean": 30.58872388175223
113
+ },
114
+ "decode_tok_s_per_req": {
115
+ "p50": 8.92044357838708,
116
+ "p90": 8.939535982689941,
117
+ "mean": 8.920202608225608
118
+ },
119
+ "itl_s": {
120
+ "p50": 0.11210204872540107,
121
+ "p90": 0.11229545274091994,
122
+ "mean": 0.1121052856137063
123
+ },
124
+ "output_tokens_mean": 256.0,
125
+ "output_tokens_min": 256,
126
+ "output_tokens_max": 256,
127
+ "prompt_tokens_mean": 1051.0,
128
+ "n_short_outputs": 0,
129
+ "aggregate_output_tok_s": 33.47593880365235,
130
+ "requests_per_s": 0.13076538595176698,
131
+ "usage_reported": true
132
+ },
133
+ {
134
+ "concurrency": 8,
135
+ "n_requests": 16,
136
+ "n_ok": 16,
137
+ "n_errors": 0,
138
+ "errors": [],
139
+ "wall_s": 63.220375599339604,
140
+ "ttft_s": {
141
+ "p50": 4.205650780815631,
142
+ "p90": 4.5575914164073765,
143
+ "mean": 4.21742238936713
144
+ },
145
+ "e2e_s": {
146
+ "p50": 31.60961770033464,
147
+ "p90": 32.07297795498744,
148
+ "mean": 31.608251873170957
149
+ },
150
+ "decode_tok_s_per_req": {
151
+ "p50": 9.306845748863292,
152
+ "p90": 9.356793657252013,
153
+ "mean": 9.310041888446369
154
+ },
155
+ "itl_s": {
156
+ "p50": 0.10744962619946283,
157
+ "p90": 0.10790295124090478,
158
+ "mean": 0.10741330675379027
159
+ },
160
+ "output_tokens_mean": 256.0,
161
+ "output_tokens_min": 256,
162
+ "output_tokens_max": 256,
163
+ "prompt_tokens_mean": 1051.0,
164
+ "n_short_outputs": 0,
165
+ "aggregate_output_tok_s": 64.78923861443788,
166
+ "requests_per_s": 0.25308296333764796,
167
+ "usage_reported": true
168
+ }
169
+ ]
170
+ }
evaluation/perf_sglang_nvfp4_v1_marlin_w4a16.json ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "host": "tux",
6
+ "time": "2026-10-30T17:34:28-0700",
7
+ "config": {
8
+ "base_url": "http://127.0.0.1:30080/v1",
9
+ "model": "sensenova-loop-nvfp4",
10
+ "concurrency": "1,8",
11
+ "num_requests": 8,
12
+ "warmup": 2,
13
+ "input_tokens": 1024,
14
+ "output_tokens": 256,
15
+ "tokenizer": "upstream/SenseNova-Loop-9B-Preview/tokenizer.json",
16
+ "allow_prefix_cache": false,
17
+ "no_ignore_eos": false,
18
+ "timeout": 600,
19
+ "label": "",
20
+ "out": "eval/runs/perf_sglang_nvfp4_v1_marlin_w4a16.json",
21
+ "prompt_text_tokens": 1024,
22
+ "prompt_build": "tokenizer.json"
23
+ },
24
+ "levels": [
25
+ {
26
+ "concurrency": 1,
27
+ "n_requests": 8,
28
+ "n_ok": 8,
29
+ "n_errors": 0,
30
+ "errors": [],
31
+ "wall_s": 186.50683353003114,
32
+ "ttft_s": {
33
+ "p50": 0.7810423010960221,
34
+ "p90": 0.7850782623514533,
35
+ "mean": 0.7793308835243806
36
+ },
37
+ "e2e_s": {
38
+ "p50": 23.208507381845266,
39
+ "p90": 23.798335312213748,
40
+ "mean": 23.31315671908669
41
+ },
42
+ "decode_tok_s_per_req": {
43
+ "p50": 11.369868579529511,
44
+ "p90": 11.574229702673124,
45
+ "mean": 11.320382232025935
46
+ },
47
+ "itl_s": {
48
+ "p50": 0.08795635091761747,
49
+ "p90": 0.09028583205250257,
50
+ "mean": 0.0883674864250911
51
+ },
52
+ "output_tokens_mean": 256.0,
53
+ "output_tokens_min": 256,
54
+ "output_tokens_max": 256,
55
+ "prompt_tokens_mean": 1051.0,
56
+ "n_short_outputs": 0,
57
+ "aggregate_output_tok_s": 10.980830896312618,
58
+ "requests_per_s": 0.042893870688721164,
59
+ "usage_reported": true
60
+ },
61
+ {
62
+ "concurrency": 8,
63
+ "n_requests": 8,
64
+ "n_ok": 8,
65
+ "n_errors": 0,
66
+ "errors": [],
67
+ "wall_s": 35.205060383304954,
68
+ "ttft_s": {
69
+ "p50": 6.181970591191202,
70
+ "p90": 6.18461575973779,
71
+ "mean": 6.14046993048396
72
+ },
73
+ "e2e_s": {
74
+ "p50": 35.20080000348389,
75
+ "p90": 35.20238197837025,
76
+ "mean": 35.2009118568385
77
+ },
78
+ "decode_tok_s_per_req": {
79
+ "p50": 8.787708328163664,
80
+ "p90": 8.78777517236657,
81
+ "mean": 8.774984883438263
82
+ },
83
+ "itl_s": {
84
+ "p50": 0.11379531075191848,
85
+ "p90": 0.11419632067502129,
86
+ "mean": 0.11396200469439374
87
+ },
88
+ "output_tokens_mean": 256.0,
89
+ "output_tokens_min": 256,
90
+ "output_tokens_max": 256,
91
+ "prompt_tokens_mean": 1051.0,
92
+ "n_short_outputs": 0,
93
+ "aggregate_output_tok_s": 58.17345511417468,
94
+ "requests_per_s": 0.22724005903974484,
95
+ "usage_reported": true
96
+ }
97
+ ]
98
+ }
evaluation/perf_sglang_nvfp4_v1_mtp.json ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "label": "",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "host": "tux",
6
+ "time": "2026-10-30T17:21:08-0700",
7
+ "config": {
8
+ "base_url": "http://127.0.0.1:30080/v1",
9
+ "model": "sensenova-loop-nvfp4",
10
+ "concurrency": "1,2,4,8",
11
+ "num_requests": 8,
12
+ "warmup": 2,
13
+ "input_tokens": 1024,
14
+ "output_tokens": 256,
15
+ "tokenizer": "upstream/SenseNova-Loop-9B-Preview/tokenizer.json",
16
+ "allow_prefix_cache": false,
17
+ "no_ignore_eos": false,
18
+ "timeout": 600,
19
+ "label": "",
20
+ "out": "eval/runs/perf_sglang_nvfp4_v1_mtp.json",
21
+ "prompt_text_tokens": 1024,
22
+ "prompt_build": "tokenizer.json"
23
+ },
24
+ "levels": [
25
+ {
26
+ "concurrency": 1,
27
+ "n_requests": 8,
28
+ "n_ok": 8,
29
+ "n_errors": 0,
30
+ "errors": [],
31
+ "wall_s": 167.7497703190893,
32
+ "ttft_s": {
33
+ "p50": 0.7368478225544095,
34
+ "p90": 0.7410881330259145,
35
+ "mean": 0.7100226980401203
36
+ },
37
+ "e2e_s": {
38
+ "p50": 20.956795112695545,
39
+ "p90": 22.442169491481035,
40
+ "mean": 20.96854761627037
41
+ },
42
+ "decode_tok_s_per_req": {
43
+ "p50": 12.574976715274474,
44
+ "p90": 13.522581057360785,
45
+ "mean": 12.629198610683233
46
+ },
47
+ "itl_s": {
48
+ "p50": 0.07952795094609552,
49
+ "p90": 0.08523689763364839,
50
+ "mean": 0.07944462552721447
51
+ },
52
+ "output_tokens_mean": 256.0,
53
+ "output_tokens_min": 256,
54
+ "output_tokens_max": 256,
55
+ "prompt_tokens_mean": 1051.0,
56
+ "n_short_outputs": 0,
57
+ "aggregate_output_tok_s": 12.208660531125302,
58
+ "requests_per_s": 0.04769008019970821,
59
+ "usage_reported": true
60
+ },
61
+ {
62
+ "concurrency": 2,
63
+ "n_requests": 8,
64
+ "n_ok": 8,
65
+ "n_errors": 0,
66
+ "errors": [],
67
+ "wall_s": 82.62508355267346,
68
+ "ttft_s": {
69
+ "p50": 0.8884775410406291,
70
+ "p90": 1.168848348222673,
71
+ "mean": 0.9600589442998171
72
+ },
73
+ "e2e_s": {
74
+ "p50": 20.495888442732394,
75
+ "p90": 23.237484936509283,
76
+ "mean": 20.539230286842212
77
+ },
78
+ "decode_tok_s_per_req": {
79
+ "p50": 13.113690417487145,
80
+ "p90": 15.40404216440473,
81
+ "mean": 13.185971019742919
82
+ },
83
+ "itl_s": {
84
+ "p50": 0.07629031224687602,
85
+ "p90": 0.08763312555082581,
86
+ "mean": 0.0767804972402861
87
+ },
88
+ "output_tokens_mean": 256.0,
89
+ "output_tokens_min": 256,
90
+ "output_tokens_max": 256,
91
+ "prompt_tokens_mean": 1051.0,
92
+ "n_short_outputs": 0,
93
+ "aggregate_output_tok_s": 24.786661773169655,
94
+ "requests_per_s": 0.09682289755144396,
95
+ "usage_reported": true
96
+ },
97
+ {
98
+ "concurrency": 4,
99
+ "n_requests": 8,
100
+ "n_ok": 8,
101
+ "n_errors": 0,
102
+ "errors": [],
103
+ "wall_s": 48.18132451828569,
104
+ "ttft_s": {
105
+ "p50": 1.4830374312587082,
106
+ "p90": 2.075450401753187,
107
+ "mean": 1.469463941641152
108
+ },
109
+ "e2e_s": {
110
+ "p50": 22.7819732921198,
111
+ "p90": 24.27312577702105,
112
+ "mean": 22.48564152873587
113
+ },
114
+ "decode_tok_s_per_req": {
115
+ "p50": 12.02420852876317,
116
+ "p90": 13.446927005044758,
117
+ "mean": 12.240232030320389
118
+ },
119
+ "itl_s": {
120
+ "p50": 0.08323863003512516,
121
+ "p90": 0.09169133495101157,
122
+ "mean": 0.08241570999380202
123
+ },
124
+ "output_tokens_mean": 256.0,
125
+ "output_tokens_min": 256,
126
+ "output_tokens_max": 256,
127
+ "prompt_tokens_mean": 1051.0,
128
+ "n_short_outputs": 0,
129
+ "aggregate_output_tok_s": 42.50609588830931,
130
+ "requests_per_s": 0.16603943706370825,
131
+ "usage_reported": true
132
+ },
133
+ {
134
+ "concurrency": 8,
135
+ "n_requests": 8,
136
+ "n_ok": 8,
137
+ "n_errors": 0,
138
+ "errors": [],
139
+ "wall_s": 31.786477480083704,
140
+ "ttft_s": {
141
+ "p50": 5.049089141655713,
142
+ "p90": 5.0829355555586515,
143
+ "mean": 5.062669638660736
144
+ },
145
+ "e2e_s": {
146
+ "p50": 25.993340300396085,
147
+ "p90": 29.076695342641322,
148
+ "mean": 26.93958732835017
149
+ },
150
+ "decode_tok_s_per_req": {
151
+ "p50": 12.175041260252009,
152
+ "p90": 12.51746500245328,
153
+ "mean": 11.74804181220008
154
+ },
155
+ "itl_s": {
156
+ "p50": 0.08213639315948182,
157
+ "p90": 0.09391973348115297,
158
+ "mean": 0.08579115118813135
159
+ },
160
+ "output_tokens_mean": 256.0,
161
+ "output_tokens_min": 256,
162
+ "output_tokens_max": 256,
163
+ "prompt_tokens_mean": 1051.0,
164
+ "n_short_outputs": 0,
165
+ "aggregate_output_tok_s": 64.42991367266805,
166
+ "requests_per_s": 0.25167935028385957,
167
+ "usage_reported": true
168
+ }
169
+ ]
170
+ }
evaluation/sglang_bf16_mtp_probe.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mult": {
3
+ "content": "91",
4
+ "reasoning": "",
5
+ "tool_calls": null,
6
+ "usage": {
7
+ "prompt_tokens": 27,
8
+ "total_tokens": 30,
9
+ "completion_tokens": 3,
10
+ "prompt_tokens_details": null,
11
+ "reasoning_tokens": 0,
12
+ "completion_tokens_details": null
13
+ }
14
+ },
15
+ "haiku": {
16
+ "content": "Green and white, it flies,\nBounces high in summer air,\nA game of joy.",
17
+ "reasoning": "",
18
+ "tool_calls": null,
19
+ "usage": {
20
+ "prompt_tokens": 21,
21
+ "total_tokens": 43,
22
+ "completion_tokens": 22,
23
+ "prompt_tokens_details": null,
24
+ "reasoning_tokens": 0,
25
+ "completion_tokens_details": null
26
+ }
27
+ },
28
+ "thinking": {
29
+ "content": "\n\n# Finding the Train's Speed\n\n## Given Information\n- Distance = 60 km\n- Time = 45 minutes\n\n## Solution\n\n**Step 1:** Convert time to hours.\n\n$$45 \\text{ minutes} = \\frac{45}{60} \\text{ hours} = \\frac{3}{4} \\text{ hours}$$\n\n**Step 2:** Use the speed formula.\n\n$$\\text{Speed} = \\frac{\\text{Distance}}{\\text{Time}} = \\frac{60 \\text{ km}}{\\frac{3}{4} \\text{ hr}}$$\n\n**Step 3:** Calculate.\n\n$$\\text{Speed} = 60 \\times \\frac{4}{3} = \\boxed{80 \\text{ km/h}}$$",
30
+ "reasoning": "The train goes 60 km in 45 minutes.\n\nTo find the speed in km/h, I need to convert 45 minutes to hours.\n\n45 minutes = 45/60 hours = 3/4 hours\n\nSpeed = Distance / Time = 60 km / (3/4 hours) = 60 × 4/3 = 80 km/h\n",
31
+ "tool_calls": null,
32
+ "usage": {
33
+ "prompt_tokens": 32,
34
+ "total_tokens": 294,
35
+ "completion_tokens": 262,
36
+ "prompt_tokens_details": null,
37
+ "reasoning_tokens": 86,
38
+ "completion_tokens_details": {
39
+ "reasoning_tokens": 86
40
+ }
41
+ }
42
+ },
43
+ "tool": {
44
+ "content": "",
45
+ "reasoning": "",
46
+ "tool_calls": [
47
+ {
48
+ "id": "call_ecd429c744ef4cdc945856fd",
49
+ "index": 0,
50
+ "type": "function",
51
+ "function": {
52
+ "name": "get_weather",
53
+ "arguments": "{\"city\": \"Paris\", \"unit\": \"celsius\"}"
54
+ }
55
+ }
56
+ ],
57
+ "usage": {
58
+ "prompt_tokens": 306,
59
+ "total_tokens": 345,
60
+ "completion_tokens": 39,
61
+ "prompt_tokens_details": null,
62
+ "reasoning_tokens": 0,
63
+ "completion_tokens_details": null
64
+ }
65
+ },
66
+ "image": {
67
+ "content": "A yellow mobile tyre fitting service van, branded with \"ATS\" and \"Euromaster\" logos, is driving on a road with a white van and a dark car visible in the background. The yellow van's license plate reads \"AJ52 UYV,\" and it features orange warning lights on its roof along with text advertising services such as tyres, batteries, exhausts, shocks, and brakes.",
68
+ "reasoning": "",
69
+ "tool_calls": null,
70
+ "usage": {
71
+ "prompt_tokens": 1013,
72
+ "total_tokens": 1096,
73
+ "completion_tokens": 83,
74
+ "prompt_tokens_details": {
75
+ "cached_tokens": 0,
76
+ "image_tokens": 992
77
+ },
78
+ "reasoning_tokens": 0,
79
+ "completion_tokens_details": null
80
+ }
81
+ }
82
+ }
evaluation/sglang_bf16_probe.json ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mult": {
3
+ "content": "91",
4
+ "reasoning": "",
5
+ "tool_calls": null,
6
+ "usage": {
7
+ "prompt_tokens": 27,
8
+ "total_tokens": 30,
9
+ "completion_tokens": 3,
10
+ "prompt_tokens_details": null,
11
+ "reasoning_tokens": 0,
12
+ "completion_tokens_details": null
13
+ }
14
+ },
15
+ "haiku": {
16
+ "content": "Green sphere, white seam,\nBounces high in summer sun,\nA game begins.",
17
+ "reasoning": "",
18
+ "tool_calls": null,
19
+ "usage": {
20
+ "prompt_tokens": 21,
21
+ "total_tokens": 41,
22
+ "completion_tokens": 20,
23
+ "prompt_tokens_details": null,
24
+ "reasoning_tokens": 0,
25
+ "completion_tokens_details": null
26
+ }
27
+ },
28
+ "thinking": {
29
+ "content": "\n\n# Finding the Train's Speed\n\n## Given Information\n- Distance = 60 km\n- Time = 45 minutes\n\n## Solution\n\n**Step 1:** Convert time to hours.\n\n$$45 \\text{ minutes} = \\frac{45}{60} \\text{ hours} = \\frac{3}{4} \\text{ hours}$$\n\n**Step 2:** Calculate speed using the formula:\n\n$$\\text{Speed} = \\frac{\\text{Distance}}{\\text{Time}}$$\n\n$$\\text{Speed} = \\frac{60 \\text{ km}}{\\frac{3}{4} \\text{ hours}} = 60 \\times \\frac{4}{3} = \\boxed{80 \\text{ km/h}}$$",
30
+ "reasoning": "The train goes 60 km in 45 minutes.\n\nTo find the speed in km/h, I need to convert 45 minutes to hours.\n\n45 minutes = 45/60 hours = 3/4 hours\n\nSpeed = Distance / Time = 60 km / (3/4 hours) = 60 × 4/3 = 80 km/h\n",
31
+ "tool_calls": null,
32
+ "usage": {
33
+ "prompt_tokens": 32,
34
+ "total_tokens": 287,
35
+ "completion_tokens": 255,
36
+ "prompt_tokens_details": null,
37
+ "reasoning_tokens": 86,
38
+ "completion_tokens_details": {
39
+ "reasoning_tokens": 86
40
+ }
41
+ }
42
+ },
43
+ "tool": {
44
+ "content": "",
45
+ "reasoning": "",
46
+ "tool_calls": [
47
+ {
48
+ "id": "call_5048fd622c914bf7acade944",
49
+ "index": 0,
50
+ "type": "function",
51
+ "function": {
52
+ "name": "get_weather",
53
+ "arguments": "{\"city\": \"Paris\", \"unit\": \"celsius\"}"
54
+ }
55
+ }
56
+ ],
57
+ "usage": {
58
+ "prompt_tokens": 306,
59
+ "total_tokens": 345,
60
+ "completion_tokens": 39,
61
+ "prompt_tokens_details": null,
62
+ "reasoning_tokens": 0,
63
+ "completion_tokens_details": null
64
+ }
65
+ },
66
+ "image": {
67
+ "content": "A yellow mobile tyre fitting service van, branded with \"ATS\" and \"Euromaster\" logos, is driving on a road with a white van and a dark car visible in the background. The yellow van's license plate reads \"AJ52 UYV,\" and it features orange warning lights on its roof along with text advertising services such as tyres, batteries, exhausts, shocks, and brakes.",
68
+ "reasoning": "",
69
+ "tool_calls": null,
70
+ "usage": {
71
+ "prompt_tokens": 1013,
72
+ "total_tokens": 1096,
73
+ "completion_tokens": 83,
74
+ "prompt_tokens_details": {
75
+ "cached_tokens": 0,
76
+ "image_tokens": 992
77
+ },
78
+ "reasoning_tokens": 0,
79
+ "completion_tokens_details": null
80
+ }
81
+ }
82
+ }
evaluation/summary_sglang_bf16.json ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "model": "sensenova-loop-bf16",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "started": "2026-10-30T15:15:24-0700",
6
+ "enable_thinking": false,
7
+ "sampling": {
8
+ "temperature": 0.0,
9
+ "top_p": 1.0,
10
+ "seed": 0,
11
+ "max_tokens": {
12
+ "ifeval": 1024,
13
+ "gsm8k": 1024,
14
+ "mmlu_pro": 1024,
15
+ "humaneval": 1024,
16
+ "tools": 512,
17
+ "vqa": 64
18
+ }
19
+ },
20
+ "concurrency": 16,
21
+ "limit": null,
22
+ "host": "tux",
23
+ "data_sha256": {
24
+ "ifeval": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
25
+ "gsm8k": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
26
+ "mmlu_pro": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
27
+ "humaneval": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
28
+ "tools": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
29
+ "vqa": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d"
30
+ },
31
+ "tasks": {
32
+ "ifeval": {
33
+ "n": 60,
34
+ "correct": 47,
35
+ "accuracy": 0.7833333333333333,
36
+ "wilson95": [
37
+ 0.6637997232345094,
38
+ 0.8687695600460952
39
+ ],
40
+ "n_errors": 0,
41
+ "n_truncated": 5,
42
+ "mean_completion_tokens": 318.95,
43
+ "sum_completion_tokens": 19137,
44
+ "instruction_level_acc": 0.8433734939759037,
45
+ "n_instructions": 83,
46
+ "wall_s": 392.1
47
+ },
48
+ "gsm8k": {
49
+ "n": 80,
50
+ "correct": 74,
51
+ "accuracy": 0.925,
52
+ "wilson95": [
53
+ 0.8458798383979047,
54
+ 0.9651747509106547
55
+ ],
56
+ "n_errors": 0,
57
+ "n_truncated": 1,
58
+ "mean_completion_tokens": 191.5375,
59
+ "sum_completion_tokens": 15323,
60
+ "wall_s": 370.3
61
+ },
62
+ "mmlu_pro": {
63
+ "n": 60,
64
+ "correct": 39,
65
+ "accuracy": 0.65,
66
+ "wilson95": [
67
+ 0.5236261874086521,
68
+ 0.7583222566810799
69
+ ],
70
+ "n_errors": 0,
71
+ "n_truncated": 13,
72
+ "mean_completion_tokens": 622.15,
73
+ "sum_completion_tokens": 37329,
74
+ "wall_s": 707.9
75
+ },
76
+ "humaneval": {
77
+ "n": 40,
78
+ "correct": 33,
79
+ "accuracy": 0.825,
80
+ "wilson95": [
81
+ 0.6805000953869541,
82
+ 0.9125458630150444
83
+ ],
84
+ "n_errors": 0,
85
+ "n_truncated": 0,
86
+ "mean_completion_tokens": 226.575,
87
+ "sum_completion_tokens": 9063,
88
+ "wall_s": 233.7
89
+ },
90
+ "tools": {
91
+ "n": 20,
92
+ "correct": 19,
93
+ "accuracy": 0.95,
94
+ "wilson95": [
95
+ 0.7638688045357189,
96
+ 0.9911185512976646
97
+ ],
98
+ "n_errors": 0,
99
+ "n_truncated": 0,
100
+ "mean_completion_tokens": 64.25,
101
+ "sum_completion_tokens": 1285,
102
+ "names_correct": 0.95,
103
+ "parse_source_counts": {
104
+ "server": 16,
105
+ "none": 4
106
+ },
107
+ "format_well_formed": 1.0,
108
+ "raw_tool_call_tags_in_content": 0,
109
+ "by_category": {
110
+ "irrelevance": "3/3",
111
+ "multiple": "6/6",
112
+ "parallel": "3/3",
113
+ "simple": "7/8"
114
+ },
115
+ "wall_s": 55.8
116
+ },
117
+ "vqa": {
118
+ "n": 40,
119
+ "correct": 36,
120
+ "accuracy": 0.9,
121
+ "wilson95": [
122
+ 0.7694822464406407,
123
+ 0.9604204715925885
124
+ ],
125
+ "n_errors": 0,
126
+ "n_truncated": 0,
127
+ "mean_completion_tokens": 4.125,
128
+ "sum_completion_tokens": 165,
129
+ "vqa_soft_acc": 0.8833333333333334,
130
+ "wall_s": 38.3
131
+ }
132
+ },
133
+ "server_models": {
134
+ "object": "list",
135
+ "data": [
136
+ {
137
+ "id": "sensenova-loop-bf16",
138
+ "object": "model",
139
+ "created": 1793398524,
140
+ "owned_by": "sglang",
141
+ "root": "sensenova-loop-bf16",
142
+ "parent": null,
143
+ "max_model_len": 32768
144
+ }
145
+ ]
146
+ },
147
+ "wall_s": 1798.0
148
+ }
evaluation/summary_sglang_bf16_mtp.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "model": "sensenova-loop-bf16",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "started": "2026-10-30T16:28:25-0700",
6
+ "enable_thinking": false,
7
+ "sampling": {
8
+ "temperature": 0.0,
9
+ "top_p": 1.0,
10
+ "seed": 0,
11
+ "max_tokens": {
12
+ "ifeval": 1024,
13
+ "gsm8k": 1024,
14
+ "mmlu_pro": 1024,
15
+ "humaneval": 1024,
16
+ "tools": 512,
17
+ "vqa": 64
18
+ }
19
+ },
20
+ "concurrency": 16,
21
+ "limit": null,
22
+ "host": "tux",
23
+ "data_sha256": {
24
+ "ifeval": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
25
+ "gsm8k": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
26
+ "mmlu_pro": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
27
+ "humaneval": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
28
+ "tools": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
29
+ "vqa": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d"
30
+ },
31
+ "tasks": {
32
+ "gsm8k": {
33
+ "n": 80,
34
+ "correct": 74,
35
+ "accuracy": 0.925,
36
+ "wilson95": [
37
+ 0.8458798383979047,
38
+ 0.9651747509106547
39
+ ],
40
+ "n_errors": 0,
41
+ "n_truncated": 1,
42
+ "mean_completion_tokens": 186.425,
43
+ "sum_completion_tokens": 14914,
44
+ "wall_s": 138.4
45
+ },
46
+ "humaneval": {
47
+ "n": 40,
48
+ "correct": 32,
49
+ "accuracy": 0.8,
50
+ "wilson95": [
51
+ 0.6524269352437855,
52
+ 0.8950001032811363
53
+ ],
54
+ "n_errors": 0,
55
+ "n_truncated": 0,
56
+ "mean_completion_tokens": 230.5,
57
+ "sum_completion_tokens": 9220,
58
+ "wall_s": 94.4
59
+ },
60
+ "tools": {
61
+ "n": 20,
62
+ "correct": 19,
63
+ "accuracy": 0.95,
64
+ "wilson95": [
65
+ 0.7638688045357189,
66
+ 0.9911185512976646
67
+ ],
68
+ "n_errors": 0,
69
+ "n_truncated": 0,
70
+ "mean_completion_tokens": 64.9,
71
+ "sum_completion_tokens": 1298,
72
+ "names_correct": 0.95,
73
+ "parse_source_counts": {
74
+ "server": 16,
75
+ "none": 4
76
+ },
77
+ "format_well_formed": 1.0,
78
+ "raw_tool_call_tags_in_content": 0,
79
+ "by_category": {
80
+ "irrelevance": "3/3",
81
+ "multiple": "6/6",
82
+ "parallel": "3/3",
83
+ "simple": "7/8"
84
+ },
85
+ "wall_s": 24.8
86
+ }
87
+ },
88
+ "server_models": {
89
+ "object": "list",
90
+ "data": [
91
+ {
92
+ "id": "sensenova-loop-bf16",
93
+ "object": "model",
94
+ "created": 1793402905,
95
+ "owned_by": "sglang",
96
+ "root": "sensenova-loop-bf16",
97
+ "parent": null,
98
+ "max_model_len": 32768
99
+ }
100
+ ]
101
+ },
102
+ "wall_s": 257.7
103
+ }
evaluation/summary_sglang_nvfp4_v1.json ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "started": "2026-10-30T16:45:48-0700",
6
+ "enable_thinking": false,
7
+ "sampling": {
8
+ "temperature": 0.0,
9
+ "top_p": 1.0,
10
+ "seed": 0,
11
+ "max_tokens": {
12
+ "ifeval": 1024,
13
+ "gsm8k": 1024,
14
+ "mmlu_pro": 1024,
15
+ "humaneval": 1024,
16
+ "tools": 512,
17
+ "vqa": 64
18
+ }
19
+ },
20
+ "concurrency": 16,
21
+ "limit": null,
22
+ "host": "tux",
23
+ "data_sha256": {
24
+ "ifeval": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
25
+ "gsm8k": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
26
+ "mmlu_pro": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
27
+ "humaneval": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
28
+ "tools": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
29
+ "vqa": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d"
30
+ },
31
+ "tasks": {
32
+ "ifeval": {
33
+ "n": 60,
34
+ "correct": 45,
35
+ "accuracy": 0.75,
36
+ "wilson95": [
37
+ 0.6276792981955739,
38
+ 0.8422347752873125
39
+ ],
40
+ "n_errors": 0,
41
+ "n_truncated": 4,
42
+ "mean_completion_tokens": 272.6666666666667,
43
+ "sum_completion_tokens": 16360,
44
+ "instruction_level_acc": 0.7831325301204819,
45
+ "n_instructions": 83,
46
+ "wall_s": 196.0
47
+ },
48
+ "gsm8k": {
49
+ "n": 80,
50
+ "correct": 76,
51
+ "accuracy": 0.95,
52
+ "wilson95": [
53
+ 0.8783772371965909,
54
+ 0.9803864456007073
55
+ ],
56
+ "n_errors": 0,
57
+ "n_truncated": 1,
58
+ "mean_completion_tokens": 187.2,
59
+ "sum_completion_tokens": 14976,
60
+ "wall_s": 156.1
61
+ },
62
+ "mmlu_pro": {
63
+ "n": 60,
64
+ "correct": 41,
65
+ "accuracy": 0.6833333333333333,
66
+ "wilson95": [
67
+ 0.5576623287169538,
68
+ 0.7869413251704964
69
+ ],
70
+ "n_errors": 0,
71
+ "n_truncated": 9,
72
+ "mean_completion_tokens": 588.55,
73
+ "sum_completion_tokens": 35313,
74
+ "wall_s": 352.6
75
+ },
76
+ "humaneval": {
77
+ "n": 40,
78
+ "correct": 32,
79
+ "accuracy": 0.8,
80
+ "wilson95": [
81
+ 0.6524269352437855,
82
+ 0.8950001032811363
83
+ ],
84
+ "n_errors": 0,
85
+ "n_truncated": 0,
86
+ "mean_completion_tokens": 197.975,
87
+ "sum_completion_tokens": 7919,
88
+ "wall_s": 79.5
89
+ },
90
+ "tools": {
91
+ "n": 20,
92
+ "correct": 19,
93
+ "accuracy": 0.95,
94
+ "wilson95": [
95
+ 0.7638688045357189,
96
+ 0.9911185512976646
97
+ ],
98
+ "n_errors": 0,
99
+ "n_truncated": 0,
100
+ "mean_completion_tokens": 54.3,
101
+ "sum_completion_tokens": 1086,
102
+ "names_correct": 0.95,
103
+ "parse_source_counts": {
104
+ "server": 16,
105
+ "none": 4
106
+ },
107
+ "format_well_formed": 1.0,
108
+ "raw_tool_call_tags_in_content": 0,
109
+ "by_category": {
110
+ "irrelevance": "3/3",
111
+ "multiple": "5/6",
112
+ "parallel": "3/3",
113
+ "simple": "8/8"
114
+ },
115
+ "wall_s": 24.6
116
+ },
117
+ "vqa": {
118
+ "n": 40,
119
+ "correct": 36,
120
+ "accuracy": 0.9,
121
+ "wilson95": [
122
+ 0.7694822464406407,
123
+ 0.9604204715925885
124
+ ],
125
+ "n_errors": 0,
126
+ "n_truncated": 0,
127
+ "mean_completion_tokens": 4.075,
128
+ "sum_completion_tokens": 163,
129
+ "vqa_soft_acc": 0.8833333333333334,
130
+ "wall_s": 25.5
131
+ }
132
+ },
133
+ "server_models": {
134
+ "object": "list",
135
+ "data": [
136
+ {
137
+ "id": "sensenova-loop-nvfp4",
138
+ "object": "model",
139
+ "created": 1793403948,
140
+ "owned_by": "sglang",
141
+ "root": "sensenova-loop-nvfp4",
142
+ "parent": null,
143
+ "max_model_len": 32768
144
+ }
145
+ ]
146
+ },
147
+ "wall_s": 834.3
148
+ }
evaluation/summary_sglang_nvfp4_v1_marlin_w4a16_gsm8k.json ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "started": "2026-10-30T17:38:10-0700",
6
+ "enable_thinking": false,
7
+ "sampling": {
8
+ "temperature": 0.0,
9
+ "top_p": 1.0,
10
+ "seed": 0,
11
+ "max_tokens": {
12
+ "ifeval": 1024,
13
+ "gsm8k": 1024,
14
+ "mmlu_pro": 1024,
15
+ "humaneval": 1024,
16
+ "tools": 512,
17
+ "vqa": 64
18
+ }
19
+ },
20
+ "concurrency": 16,
21
+ "limit": null,
22
+ "host": "tux",
23
+ "data_sha256": {
24
+ "ifeval": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
25
+ "gsm8k": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
26
+ "mmlu_pro": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
27
+ "humaneval": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
28
+ "tools": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
29
+ "vqa": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d"
30
+ },
31
+ "tasks": {
32
+ "gsm8k": {
33
+ "n": 80,
34
+ "correct": 77,
35
+ "accuracy": 0.9625,
36
+ "wilson95": [
37
+ 0.8954527983617241,
38
+ 0.9871654311799435
39
+ ],
40
+ "n_errors": 0,
41
+ "n_truncated": 0,
42
+ "mean_completion_tokens": 184.425,
43
+ "sum_completion_tokens": 14754,
44
+ "wall_s": 137.2
45
+ }
46
+ },
47
+ "server_models": {
48
+ "object": "list",
49
+ "data": [
50
+ {
51
+ "id": "sensenova-loop-nvfp4",
52
+ "object": "model",
53
+ "created": 1793407090,
54
+ "owned_by": "sglang",
55
+ "root": "sensenova-loop-nvfp4",
56
+ "parent": null,
57
+ "max_model_len": 32768
58
+ }
59
+ ]
60
+ },
61
+ "wall_s": 137.2
62
+ }
evaluation/summary_sglang_nvfp4_v1_mtp.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "eval_version": "sn-loop-eval-v1",
3
+ "model": "sensenova-loop-nvfp4",
4
+ "base_url": "http://127.0.0.1:30080/v1",
5
+ "started": "2026-10-30T17:17:37-0700",
6
+ "enable_thinking": false,
7
+ "sampling": {
8
+ "temperature": 0.0,
9
+ "top_p": 1.0,
10
+ "seed": 0,
11
+ "max_tokens": {
12
+ "ifeval": 1024,
13
+ "gsm8k": 1024,
14
+ "mmlu_pro": 1024,
15
+ "humaneval": 1024,
16
+ "tools": 512,
17
+ "vqa": 64
18
+ }
19
+ },
20
+ "concurrency": 16,
21
+ "limit": null,
22
+ "host": "tux",
23
+ "data_sha256": {
24
+ "ifeval": "3e9e9e7bd42ef54862f0c71c9a4e6f0b17a9c259f8f4b2b4e5fbcad917228118",
25
+ "gsm8k": "5bb91d328c1ffc424d6679fe8edb36ed08152e4c75e75b343303e15cacc2da49",
26
+ "mmlu_pro": "e9d8d5717def43a0b995710ab093a13de48b4cfb4db19cd7c66affacfb492ea7",
27
+ "humaneval": "1e775e30085b890c19c48a583bfabb8f5f54598b5bf54796839b92e5bafdab9f",
28
+ "tools": "037b2e288f4f063e8a2d994799b4a51e4d97740b1cf62da75844243749dbb08a",
29
+ "vqa": "7d47303ecdf3386d04a852ab8b7e8be8d34137a1c4d10143331cc7175ce80d9d"
30
+ },
31
+ "tasks": {
32
+ "gsm8k": {
33
+ "n": 80,
34
+ "correct": 77,
35
+ "accuracy": 0.9625,
36
+ "wilson95": [
37
+ 0.8954527983617241,
38
+ 0.9871654311799435
39
+ ],
40
+ "n_errors": 0,
41
+ "n_truncated": 1,
42
+ "mean_completion_tokens": 190.3875,
43
+ "sum_completion_tokens": 15231,
44
+ "wall_s": 94.6
45
+ },
46
+ "humaneval": {
47
+ "n": 40,
48
+ "correct": 32,
49
+ "accuracy": 0.8,
50
+ "wilson95": [
51
+ 0.6524269352437855,
52
+ 0.8950001032811363
53
+ ],
54
+ "n_errors": 0,
55
+ "n_truncated": 0,
56
+ "mean_completion_tokens": 191.3,
57
+ "sum_completion_tokens": 7652,
58
+ "wall_s": 46.1
59
+ },
60
+ "tools": {
61
+ "n": 20,
62
+ "correct": 18,
63
+ "accuracy": 0.9,
64
+ "wilson95": [
65
+ 0.6989663528135878,
66
+ 0.9721335190383086
67
+ ],
68
+ "n_errors": 0,
69
+ "n_truncated": 0,
70
+ "mean_completion_tokens": 60.2,
71
+ "sum_completion_tokens": 1204,
72
+ "names_correct": 0.9,
73
+ "parse_source_counts": {
74
+ "server": 15,
75
+ "none": 5
76
+ },
77
+ "format_well_formed": 1.0,
78
+ "raw_tool_call_tags_in_content": 0,
79
+ "by_category": {
80
+ "irrelevance": "3/3",
81
+ "multiple": "5/6",
82
+ "parallel": "3/3",
83
+ "simple": "7/8"
84
+ },
85
+ "wall_s": 16.6
86
+ }
87
+ },
88
+ "server_models": {
89
+ "object": "list",
90
+ "data": [
91
+ {
92
+ "id": "sensenova-loop-nvfp4",
93
+ "object": "model",
94
+ "created": 1793405857,
95
+ "owned_by": "sglang",
96
+ "root": "sensenova-loop-nvfp4",
97
+ "parent": null,
98
+ "max_model_len": 32768
99
+ }
100
+ ]
101
+ },
102
+ "wall_s": 157.4
103
+ }
generation_config.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "eos_token_id": [
4
+ 248044,
5
+ 248046
6
+ ],
7
+ "transformers_version": "5.18.0",
8
+ "use_cache": true,
9
+ "temperature": 1.0,
10
+ "do_sample": true,
11
+ "top_p": 0.95
12
+ }
hf_quant_config.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "producer": {
3
+ "name": "modelopt",
4
+ "version": "0.48.0rc1"
5
+ },
6
+ "quantization": {
7
+ "quant_algo": "NVFP4",
8
+ "kv_cache_quant_algo": null,
9
+ "group_size": 16,
10
+ "exclude_modules": [
11
+ "lm_head",
12
+ "model.language_model.embed_tokens",
13
+ "model.language_model.layers.0.linear_attn.conv1d",
14
+ "model.language_model.layers.0.linear_attn.in_proj_a",
15
+ "model.language_model.layers.0.linear_attn.in_proj_b",
16
+ "model.language_model.layers.1.linear_attn.conv1d",
17
+ "model.language_model.layers.1.linear_attn.in_proj_a",
18
+ "model.language_model.layers.1.linear_attn.in_proj_b",
19
+ "model.language_model.layers.10.linear_attn.conv1d",
20
+ "model.language_model.layers.10.linear_attn.in_proj_a",
21
+ "model.language_model.layers.10.linear_attn.in_proj_b",
22
+ "model.language_model.layers.12.linear_attn.conv1d",
23
+ "model.language_model.layers.12.linear_attn.in_proj_a",
24
+ "model.language_model.layers.12.linear_attn.in_proj_b",
25
+ "model.language_model.layers.13.linear_attn.conv1d",
26
+ "model.language_model.layers.13.linear_attn.in_proj_a",
27
+ "model.language_model.layers.13.linear_attn.in_proj_b",
28
+ "model.language_model.layers.14.linear_attn.conv1d",
29
+ "model.language_model.layers.14.linear_attn.in_proj_a",
30
+ "model.language_model.layers.14.linear_attn.in_proj_b",
31
+ "model.language_model.layers.16.linear_attn.conv1d",
32
+ "model.language_model.layers.16.linear_attn.in_proj_a",
33
+ "model.language_model.layers.16.linear_attn.in_proj_b",
34
+ "model.language_model.layers.17.linear_attn.conv1d",
35
+ "model.language_model.layers.17.linear_attn.in_proj_a",
36
+ "model.language_model.layers.17.linear_attn.in_proj_b",
37
+ "model.language_model.layers.18.linear_attn.conv1d",
38
+ "model.language_model.layers.18.linear_attn.in_proj_a",
39
+ "model.language_model.layers.18.linear_attn.in_proj_b",
40
+ "model.language_model.layers.2.linear_attn.conv1d",
41
+ "model.language_model.layers.2.linear_attn.in_proj_a",
42
+ "model.language_model.layers.2.linear_attn.in_proj_b",
43
+ "model.language_model.layers.20.linear_attn.conv1d",
44
+ "model.language_model.layers.20.linear_attn.in_proj_a",
45
+ "model.language_model.layers.20.linear_attn.in_proj_b",
46
+ "model.language_model.layers.21.linear_attn.conv1d",
47
+ "model.language_model.layers.21.linear_attn.in_proj_a",
48
+ "model.language_model.layers.21.linear_attn.in_proj_b",
49
+ "model.language_model.layers.22.linear_attn.conv1d",
50
+ "model.language_model.layers.22.linear_attn.in_proj_a",
51
+ "model.language_model.layers.22.linear_attn.in_proj_b",
52
+ "model.language_model.layers.24.linear_attn.conv1d",
53
+ "model.language_model.layers.24.linear_attn.in_proj_a",
54
+ "model.language_model.layers.24.linear_attn.in_proj_b",
55
+ "model.language_model.layers.25.linear_attn.conv1d",
56
+ "model.language_model.layers.25.linear_attn.in_proj_a",
57
+ "model.language_model.layers.25.linear_attn.in_proj_b",
58
+ "model.language_model.layers.26.linear_attn.conv1d",
59
+ "model.language_model.layers.26.linear_attn.in_proj_a",
60
+ "model.language_model.layers.26.linear_attn.in_proj_b",
61
+ "model.language_model.layers.28.linear_attn.conv1d",
62
+ "model.language_model.layers.28.linear_attn.in_proj_a",
63
+ "model.language_model.layers.28.linear_attn.in_proj_b",
64
+ "model.language_model.layers.29.linear_attn.conv1d",
65
+ "model.language_model.layers.29.linear_attn.in_proj_a",
66
+ "model.language_model.layers.29.linear_attn.in_proj_b",
67
+ "model.language_model.layers.30.linear_attn.conv1d",
68
+ "model.language_model.layers.30.linear_attn.in_proj_a",
69
+ "model.language_model.layers.30.linear_attn.in_proj_b",
70
+ "model.language_model.layers.4.linear_attn.conv1d",
71
+ "model.language_model.layers.4.linear_attn.in_proj_a",
72
+ "model.language_model.layers.4.linear_attn.in_proj_b",
73
+ "model.language_model.layers.5.linear_attn.conv1d",
74
+ "model.language_model.layers.5.linear_attn.in_proj_a",
75
+ "model.language_model.layers.5.linear_attn.in_proj_b",
76
+ "model.language_model.layers.6.linear_attn.conv1d",
77
+ "model.language_model.layers.6.linear_attn.in_proj_a",
78
+ "model.language_model.layers.6.linear_attn.in_proj_b",
79
+ "model.language_model.layers.8.linear_attn.conv1d",
80
+ "model.language_model.layers.8.linear_attn.in_proj_a",
81
+ "model.language_model.layers.8.linear_attn.in_proj_b",
82
+ "model.language_model.layers.9.linear_attn.conv1d",
83
+ "model.language_model.layers.9.linear_attn.in_proj_a",
84
+ "model.language_model.layers.9.linear_attn.in_proj_b",
85
+ "model.visual*",
86
+ "mtp.fc",
87
+ "mtp.layers.0.input_layernorm",
88
+ "mtp.layers.0.mlp.down_proj",
89
+ "mtp.layers.0.mlp.gate_proj",
90
+ "mtp.layers.0.mlp.up_proj",
91
+ "mtp.layers.0.post_attention_layernorm",
92
+ "mtp.layers.0.self_attn.k_norm",
93
+ "mtp.layers.0.self_attn.k_proj",
94
+ "mtp.layers.0.self_attn.o_proj",
95
+ "mtp.layers.0.self_attn.q_norm",
96
+ "mtp.layers.0.self_attn.q_proj",
97
+ "mtp.layers.0.self_attn.v_proj",
98
+ "mtp.norm",
99
+ "mtp.pre_fc_norm_embedding",
100
+ "mtp.pre_fc_norm_hidden"
101
+ ]
102
+ }
103
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1e420ea69111b7bb3c7c529cdbbb4ff4e6ebb531c0cd2be5c634cd88cd40eaca
3
+ size 9382671592
modeling_sensenova_loop.py ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face remote-code support for shared-weight SenseNova Loop and MHC.
2
+
3
+ This single file contains configuration, plan validation, MHC routing and the
4
+ model implementation. The original physical layer weights are reused on every
5
+ Loop visit; autoregressive cache states are independent per visit.
6
+ """
7
+ import copy
8
+ import math
9
+ from contextvars import ContextVar
10
+
11
+ import torch
12
+ from torch import nn
13
+ from transformers import Qwen3_5Config
14
+ from transformers.cache_utils import DynamicCache
15
+ from transformers.models.qwen3_5 import modeling_qwen3_5 as hf
16
+
17
+
18
+ _attention_capture = ContextVar("sensenova_attention_capture", default=None)
19
+
20
+
21
+ class SenseNovaLoopConfig(Qwen3_5Config):
22
+ model_type = "sensenova_loop"
23
+
24
+
25
+ def execution_plan(config):
26
+ """Return physical layer indices in the order they execute during inference."""
27
+ loop = config.get("loop_settings")
28
+ if not isinstance(loop, dict):
29
+ raise ValueError("This implementation requires explicit loop_settings")
30
+
31
+ text_config = config["text_config"]
32
+ num_layers = text_config["num_hidden_layers"]
33
+ start, end, count = loop.get("start"), loop.get("end"), loop.get("eval_count")
34
+ if any(type(value) is not int for value in (num_layers, start, end, count)) or not (
35
+ 0 <= start < end <= num_layers and count >= 1
36
+ ):
37
+ raise ValueError("Invalid Loop interval or eval_count")
38
+ if loop.get("counts") != [count]:
39
+ raise ValueError("Only fixed-count Loop checkpoints are supported")
40
+
41
+ layer_types = text_config["layer_types"]
42
+ if len(layer_types) != num_layers or set(layer_types) - {"full_attention", "linear_attention"}:
43
+ raise ValueError("Unexpected physical layer types")
44
+ if "full_attention" not in layer_types:
45
+ raise ValueError("At least one full-attention layer is required for sequence-length tracking")
46
+
47
+ mhc = config.get("mhc")
48
+ if mhc is not None:
49
+ mhc_start, mhc_end, streams, iterations = (
50
+ mhc.get(key) for key in ("start", "end", "streams", "iterations")
51
+ )
52
+ if (
53
+ any(type(value) is not int for value in (mhc_start, mhc_end, streams, iterations))
54
+ or not 0 <= mhc_start < mhc_end <= num_layers
55
+ or streams not in (2, 4)
56
+ or iterations < 1
57
+ ):
58
+ raise ValueError("Invalid MHC range, stream count, or iterations")
59
+ if type(mhc.get("fixed_identity")) is not bool:
60
+ raise ValueError("MHC fixed_identity must be a boolean")
61
+ scale = mhc.get("pre_scale", 1.0)
62
+ if not isinstance(scale, (int, float)) or not math.isfinite(scale) or scale <= 0:
63
+ raise ValueError("Invalid MHC pre_scale")
64
+ allowed = {"start", "end", "streams", "iterations", "fixed_identity", "pre_scale"}
65
+ if set(mhc) - allowed:
66
+ raise ValueError("Unknown MHC configuration fields")
67
+ phase_boundaries = (0, start, end, num_layers)
68
+ if not any(
69
+ left <= mhc_start < mhc_end <= right
70
+ for left, right in zip(phase_boundaries, phase_boundaries[1:])
71
+ ):
72
+ raise ValueError("MHC cannot cross a Loop phase boundary")
73
+
74
+ return tuple(range(start)) + tuple(range(start, end)) * count + tuple(range(end, num_layers))
75
+
76
+
77
+ def sinkhorn(logits, iterations):
78
+ """Normalize routing logits across rows and columns in log space."""
79
+ logits = logits.float()
80
+ for _ in range(iterations):
81
+ logits = logits - torch.logsumexp(logits, dim=-1, keepdim=True)
82
+ logits = logits - torch.logsumexp(logits, dim=-2, keepdim=True)
83
+ return logits.exp()
84
+
85
+
86
+ class MHCRouter(nn.Module):
87
+ def __init__(self, dim, settings):
88
+ super().__init__()
89
+ self.streams = settings["streams"]
90
+ self.iterations = settings["iterations"]
91
+ self.fixed_identity = settings["fixed_identity"]
92
+ self.pre_scale = settings.get("pre_scale", 1.0)
93
+ streams = self.streams
94
+ output_size = 2 * streams + (0 if self.fixed_identity else streams * streams)
95
+ self.weight = nn.Parameter(torch.zeros(output_size, streams * dim, dtype=torch.float32))
96
+ self.bias = nn.Parameter(torch.zeros(output_size, dtype=torch.float32))
97
+ with torch.no_grad():
98
+ initial_probabilities = {2: (0.25, 0.75), 4: (0.0625, 0.1875, 0.25, 0.5)}[streams]
99
+ self.bias[:streams].copy_(torch.logit(torch.tensor(initial_probabilities, device=self.bias.device)))
100
+ if not self.fixed_identity:
101
+ diagonal = math.log((1 - (streams - 1) / 16) * 16)
102
+ self.bias[2 * streams:].copy_((torch.eye(streams, device=self.bias.device) * diagonal).flatten())
103
+
104
+ def _apply(self, fn, recurse=True):
105
+ # Keep the original FP32 values when the surrounding model is cast to BF16.
106
+ def keep_fp32(tensor):
107
+ result = fn(tensor)
108
+ if tensor.is_floating_point() and result.dtype != torch.float32:
109
+ if tensor.device.type == "meta" and result.device.type != "meta":
110
+ return torch.empty_like(result, dtype=torch.float32)
111
+ return tensor.to(device=result.device, dtype=torch.float32)
112
+ return result
113
+
114
+ return super()._apply(keep_fp32, recurse=recurse)
115
+
116
+ def forward(self, residual):
117
+ if self.weight.dtype != torch.float32 or self.bias.dtype != torch.float32:
118
+ raise RuntimeError("mHC routers require FP32 parameters; configure FSDP per-module precision")
119
+ with torch.autocast(device_type=residual.device.type, enabled=False):
120
+ flat = residual.flatten(-2).float()
121
+ normalized = flat * torch.rsqrt(flat.square().mean(-1, keepdim=True) + 1e-6)
122
+ logits = torch.nn.functional.linear(normalized, self.weight, self.bias)
123
+ streams = self.streams
124
+ pre = logits[..., :streams].sigmoid()
125
+ post = 2 * logits[..., streams:2 * streams].sigmoid()
126
+ if self.fixed_identity:
127
+ mix = torch.eye(streams, device=flat.device, dtype=torch.float32).expand(
128
+ *flat.shape[:-1], streams, streams
129
+ )
130
+ else:
131
+ mix = sinkhorn(logits[..., 2 * streams:].unflatten(-1, (streams, streams)), self.iterations)
132
+ branch = torch.matmul(pre.to(residual.dtype).unsqueeze(-2), residual).squeeze(-2)
133
+ if self.pre_scale != 1.0:
134
+ branch = branch * self.pre_scale
135
+ return branch, post, mix
136
+
137
+ @staticmethod
138
+ def merge(residual, branch, post, mix):
139
+ """Mix residual streams and add the branch output to each stream."""
140
+ with torch.autocast(device_type=residual.device.type, enabled=False):
141
+ mixed_residual = torch.matmul(mix.to(residual.dtype), residual)
142
+ branch_update = post.to(branch.dtype).unsqueeze(-1) * branch.unsqueeze(-2)
143
+ return mixed_residual + branch_update
144
+
145
+
146
+ def enter(hidden, physical, settings):
147
+ """Expand to residual streams at the first mHC layer, or restore their shape."""
148
+ if physical == settings["start"]:
149
+ return hidden.unsqueeze(-2).expand(
150
+ *hidden.shape[:-1], settings["streams"], hidden.shape[-1]
151
+ ).contiguous()
152
+ return hidden.unflatten(-1, (settings["streams"], -1))
153
+
154
+
155
+ def leave(residual, physical, settings):
156
+ """Reduce streams at the last mHC layer; otherwise carry them forward."""
157
+ return residual.mean(-2) if physical == settings["end"] - 1 else residual.flatten(-2)
158
+
159
+
160
+ class _CacheLayersView:
161
+ """Expose the execution slot when a layer accesses its physical index."""
162
+ def __init__(self, cache, physical, virtual):
163
+ self.cache = cache
164
+ self.physical = physical
165
+ self.virtual = virtual
166
+
167
+ def __getitem__(self, index):
168
+ if index != self.physical:
169
+ raise IndexError("A physical layer may only access its own virtual cache slot")
170
+ return self.cache.layers[self.virtual]
171
+
172
+
173
+ class _VisitCache:
174
+ """Map a physical attention module's layer_idx to one execution slot.
175
+
176
+ Mapping is local to this forward call: shared modules and their layer_idx are
177
+ never mutated. `layers` supports Transformers 5.12; state accessors support
178
+ newer official backbone cache APIs as well.
179
+ """
180
+ def __init__(self, cache, physical, virtual):
181
+ self.cache = cache
182
+ self.virtual = virtual
183
+ self.layers = _CacheLayersView(cache, physical, virtual)
184
+
185
+ def update(self, key_states, value_states, layer_idx, **kwargs):
186
+ return self.cache.update(key_states, value_states, self.virtual, **kwargs)
187
+
188
+ def has_previous_state(self, layer_idx=None, **kwargs):
189
+ return self.cache.has_previous_state(self.virtual, **kwargs)
190
+
191
+ def update_conv_state(self, state, layer_idx, **kwargs):
192
+ return self.cache.update_conv_state(state, self.virtual, **kwargs)
193
+
194
+ def update_recurrent_state(self, state, layer_idx, **kwargs):
195
+ return self.cache.update_recurrent_state(state, self.virtual, **kwargs)
196
+
197
+ def get_conv_state(self, layer_idx, **kwargs):
198
+ return self.cache.get_conv_state(self.virtual, **kwargs)
199
+
200
+ def get_recurrent_state(self, layer_idx, **kwargs):
201
+ return self.cache.get_recurrent_state(self.virtual, **kwargs)
202
+
203
+ def is_recording_past(self, layer_idx):
204
+ return self.cache.is_recording_past(self.virtual)
205
+
206
+
207
+ class SenseNovaLoopTextModel(hf.Qwen3_5TextModel):
208
+ def forward(self, input_ids=None, attention_mask=None, position_ids=None,
209
+ past_key_values=None, inputs_embeds=None, use_cache=None,
210
+ output_hidden_states=None, output_attentions=None, **kwargs):
211
+ """Run Loop visits, optionally returning their states and attention weights.
212
+
213
+ Hidden states include the initial embedding and one state per visit.
214
+ MHC states between its boundaries retain their flattened residual streams.
215
+ Attention entries follow visit order, with None for linear-attention layers.
216
+ """
217
+ if (input_ids is None) == (inputs_embeds is None):
218
+ raise ValueError("Specify exactly one of input_ids or inputs_embeds")
219
+ if inputs_embeds is None:
220
+ inputs_embeds = self.embed_tokens(input_ids)
221
+ use_cache = self.config.use_cache if use_cache is None else use_cache
222
+ if output_hidden_states is None:
223
+ output_hidden_states = getattr(self.config, "output_hidden_states", False)
224
+ if output_attentions is None:
225
+ output_attentions = getattr(self.config, "output_attentions", False)
226
+ if not isinstance(output_hidden_states, bool):
227
+ raise TypeError("output_hidden_states must be a boolean.")
228
+ if output_attentions and self.config._attn_implementation != "eager":
229
+ raise ValueError(
230
+ "Attention weights require attn_implementation='eager' when loading the model."
231
+ )
232
+ if self.gradient_checkpointing and self.training and use_cache:
233
+ raise ValueError("Disable use_cache when training with gradient checkpointing")
234
+ if not use_cache and past_key_values is not None:
235
+ raise ValueError("past_key_values requires use_cache=True")
236
+ if past_key_values is not None and not isinstance(past_key_values, DynamicCache):
237
+ raise TypeError("Loop generation currently supports DynamicCache only")
238
+ if use_cache:
239
+ if past_key_values is None:
240
+ past_key_values = DynamicCache(config=self._loop_cache_config)
241
+ past_key_values._loop_execution_plan = self._loop_plan
242
+ elif getattr(past_key_values, "_loop_execution_plan", None) != self._loop_plan:
243
+ # GenerationMixin may allocate an empty physical-depth cache.
244
+ # A populated foreign cache cannot be reinterpreted safely.
245
+ if past_key_values.get_seq_length() != 0 or past_key_values.has_previous_state():
246
+ raise ValueError("A populated cache from another execution plan cannot be reused")
247
+ past_key_values = DynamicCache(config=self._loop_cache_config)
248
+ past_key_values._loop_execution_plan = self._loop_plan
249
+
250
+ if position_ids is None:
251
+ seen = past_key_values.get_seq_length() if past_key_values is not None else 0
252
+ position_ids = torch.arange(inputs_embeds.shape[1], device=inputs_embeds.device) + seen
253
+ position_ids = position_ids.view(1, 1, -1).expand(4, inputs_embeds.shape[0], -1)
254
+ elif position_ids.ndim == 2:
255
+ position_ids = position_ids.unsqueeze(0).expand(4, -1, -1)
256
+ if position_ids.ndim == 3 and position_ids.shape[0] == 4:
257
+ text_positions, position_ids = position_ids[0], position_ids[1:]
258
+ else:
259
+ text_positions = None
260
+
261
+ if isinstance(attention_mask, dict):
262
+ masks = attention_mask
263
+ else:
264
+ mask_args = dict(
265
+ config=self._loop_cache_config,
266
+ inputs_embeds=inputs_embeds,
267
+ attention_mask=attention_mask,
268
+ past_key_values=past_key_values,
269
+ position_ids=text_positions,
270
+ )
271
+ if hasattr(hf, "create_recurrent_attention_mask"):
272
+ linear_mask = hf.create_recurrent_attention_mask(**mask_args)
273
+ else:
274
+ linear_mask = self._update_linear_attn_mask(attention_mask, past_key_values)
275
+ masks = {
276
+ "full_attention": hf.create_causal_mask(**mask_args),
277
+ "linear_attention": linear_mask,
278
+ }
279
+
280
+ hidden = inputs_embeds
281
+ position_embeddings = self.rotary_emb(hidden, position_ids)
282
+ hidden_states = [hidden] if output_hidden_states else None
283
+ attentions = [] if output_attentions else None
284
+ hooks = []
285
+ capture_token = None
286
+
287
+ def record_attention(_module, _inputs, output):
288
+ if _attention_capture.get() is attentions:
289
+ if output[1] is None:
290
+ raise RuntimeError("The attention backend did not return attention weights.")
291
+ attentions.append(output[1])
292
+
293
+ # Repeated visits share weights, but each visit has its own cache slot.
294
+ try:
295
+ if attentions is not None:
296
+ for layer in self.layers:
297
+ if hasattr(layer, "self_attn"):
298
+ hooks.append(layer.self_attn.register_forward_hook(record_attention))
299
+ capture_token = _attention_capture.set(attentions)
300
+ for execution_index, physical_index in enumerate(self._loop_plan):
301
+ layer_type = self.config.layer_types[physical_index]
302
+ if attentions is not None and layer_type == "linear_attention":
303
+ attentions.append(None)
304
+ cache = (
305
+ _VisitCache(past_key_values, physical_index, execution_index)
306
+ if use_cache else None
307
+ )
308
+ hidden = self.layers[physical_index](
309
+ hidden, position_embeddings=position_embeddings,
310
+ attention_mask=masks[layer_type],
311
+ position_ids=text_positions, past_key_values=cache,
312
+ use_cache=use_cache, **kwargs,
313
+ )
314
+ if hidden_states is not None:
315
+ hidden_states.append(hidden)
316
+ finally:
317
+ if capture_token is not None:
318
+ _attention_capture.reset(capture_token)
319
+ for hook in hooks:
320
+ hook.remove()
321
+
322
+ hidden = self.norm(hidden)
323
+ if hidden_states is not None:
324
+ hidden_states[-1] = hidden
325
+ return hf.Qwen3_5ModelOutputWithPast(
326
+ last_hidden_state=hidden, past_key_values=past_key_values,
327
+ hidden_states=tuple(hidden_states) if hidden_states is not None else None,
328
+ attentions=tuple(attentions) if attentions is not None else None,
329
+ )
330
+
331
+
332
+ class MHCDecoderLayer(hf.Qwen3_5DecoderLayer):
333
+ def forward(self, hidden_states, position_embeddings, attention_mask=None,
334
+ position_ids=None, past_key_values=None, **kwargs):
335
+ residual = enter(hidden_states, self._mhc_physical, self._mhc_settings)
336
+ branch, post, mix = self.mhc_attn(residual)
337
+ branch = self.input_layernorm(branch)
338
+ kind = self.layer_type if hasattr(self, "layer_type") else self.block_type
339
+ if kind == "linear_attention":
340
+ branch = self.linear_attn(hidden_states=branch, cache_params=past_key_values,
341
+ attention_mask=attention_mask, **kwargs)
342
+ else:
343
+ branch, _ = self.self_attn(
344
+ hidden_states=branch, position_embeddings=position_embeddings,
345
+ attention_mask=attention_mask, position_ids=position_ids,
346
+ past_key_values=past_key_values, **kwargs)
347
+ residual = MHCRouter.merge(residual, branch, post, mix)
348
+ branch, post, mix = self.mhc_ffn(residual)
349
+ branch = self.mlp(self.post_attention_layernorm(branch))
350
+ return leave(MHCRouter.merge(residual, branch, post, mix),
351
+ self._mhc_physical, self._mhc_settings)
352
+
353
+
354
+ def _configure_text_model(text, config):
355
+ raw = config.to_dict()
356
+ plan = execution_plan(raw)
357
+ # Change only these new model instances, preserving canonical state_dict
358
+ # names and avoiding a second allocation of the full backbone.
359
+ text.__class__ = SenseNovaLoopTextModel
360
+ text._loop_plan = plan
361
+ text._loop_cache_config = copy.deepcopy(text.config)
362
+ text._loop_cache_config.num_hidden_layers = len(plan)
363
+ text._loop_cache_config.layer_types = [text.config.layer_types[i] for i in plan]
364
+ if raw.get("mhc") is not None:
365
+ settings = raw["mhc"]
366
+ for i in range(settings["start"], settings["end"]):
367
+ layer = text.layers[i]
368
+ layer.__class__ = MHCDecoderLayer
369
+ layer._mhc_physical, layer._mhc_settings = i, dict(settings)
370
+ layer.mhc_attn = MHCRouter(text.config.hidden_size, settings)
371
+ layer.mhc_ffn = MHCRouter(text.config.hidden_size, settings)
372
+
373
+
374
+ class SenseNovaLoopModel(hf.Qwen3_5Model):
375
+ config_class = SenseNovaLoopConfig
376
+ _keep_in_fp32_modules = ["mhc_attn", "mhc_ffn"]
377
+ _no_split_modules = ["Qwen3_5DecoderLayer", "MHCDecoderLayer", "Qwen3_5VisionBlock"]
378
+ _keys_to_ignore_on_load_unexpected = [r"^mtp\."]
379
+
380
+ def __init__(self, config):
381
+ execution_plan(config.to_dict())
382
+ super().__init__(config)
383
+ _configure_text_model(self.language_model, config)
384
+
385
+
386
+ class SenseNovaLoopForConditionalGeneration(hf.Qwen3_5ForConditionalGeneration):
387
+ config_class = SenseNovaLoopConfig
388
+ _keep_in_fp32_modules = ["mhc_attn", "mhc_ffn"]
389
+ _no_split_modules = ["Qwen3_5DecoderLayer", "MHCDecoderLayer", "Qwen3_5VisionBlock"]
390
+ _keys_to_ignore_on_load_unexpected = [r"^mtp\."]
391
+
392
+ def __init__(self, config):
393
+ execution_plan(config.to_dict())
394
+ super().__init__(config)
395
+ _configure_text_model(self.model.language_model, config)
preprocessor_config.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "do_convert_rgb": true,
3
+ "do_normalize": true,
4
+ "do_rescale": true,
5
+ "do_resize": true,
6
+ "image_mean": [
7
+ 0.5,
8
+ 0.5,
9
+ 0.5
10
+ ],
11
+ "image_processor_type": "Qwen2VLImageProcessor",
12
+ "image_std": [
13
+ 0.5,
14
+ 0.5,
15
+ 0.5
16
+ ],
17
+ "merge_size": 2,
18
+ "patch_size": 16,
19
+ "resample": 3,
20
+ "rescale_factor": 0.00392156862745098,
21
+ "size": {
22
+ "longest_edge": 16777216,
23
+ "shortest_edge": 65536
24
+ },
25
+ "temporal_patch_size": 2
26
+ }
processor_config.json ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image_processor": {
3
+ "do_convert_rgb": true,
4
+ "do_normalize": true,
5
+ "do_rescale": true,
6
+ "do_resize": true,
7
+ "image_mean": [
8
+ 0.5,
9
+ 0.5,
10
+ 0.5
11
+ ],
12
+ "image_processor_type": "Qwen2VLImageProcessor",
13
+ "image_std": [
14
+ 0.5,
15
+ 0.5,
16
+ 0.5
17
+ ],
18
+ "merge_size": 2,
19
+ "patch_size": 16,
20
+ "resample": 3,
21
+ "rescale_factor": 0.00392156862745098,
22
+ "size": {
23
+ "longest_edge": 16777216,
24
+ "shortest_edge": 65536
25
+ },
26
+ "temporal_patch_size": 2
27
+ },
28
+ "processor_class": "Qwen3VLProcessor",
29
+ "video_processor": {
30
+ "do_convert_rgb": true,
31
+ "do_normalize": true,
32
+ "do_rescale": true,
33
+ "do_resize": true,
34
+ "do_sample_frames": true,
35
+ "fps": 2,
36
+ "image_mean": [
37
+ 0.5,
38
+ 0.5,
39
+ 0.5
40
+ ],
41
+ "image_std": [
42
+ 0.5,
43
+ 0.5,
44
+ 0.5
45
+ ],
46
+ "max_frames": 768,
47
+ "merge_size": 2,
48
+ "min_frames": 4,
49
+ "patch_size": 16,
50
+ "resample": 3,
51
+ "rescale_factor": 0.00392156862745098,
52
+ "return_metadata": false,
53
+ "size": {
54
+ "longest_edge": 25165824,
55
+ "shortest_edge": 4096
56
+ },
57
+ "temporal_patch_size": 2,
58
+ "video_processor_type": "Qwen3VLVideoProcessor"
59
+ }
60
+ }
quantization/10_reference_checks.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """HF-transformers functional checks for SenseNova-Loop (reference BF16 backbone + FP32 mHC).
3
+
4
+ Checks: mHC router dtype, shared-weight looping, text greedy, thinking, tool-call format,
5
+ image understanding, and cached-vs-uncached decoding consistency (teacher-forced argmax).
6
+ Writes a JSON report; does not modify the model directory.
7
+
8
+ usage: python -I scripts/10_reference_checks.py MODEL_DIR OUT_JSON [IMAGE]
9
+ """
10
+ import json, sys, time
11
+ import torch
12
+ from PIL import Image
13
+ from transformers import AutoModelForImageTextToText, AutoProcessor
14
+
15
+ model_dir, out_path = sys.argv[1], sys.argv[2]
16
+ image_path = sys.argv[3] if len(sys.argv) > 3 else None
17
+ dev = "cuda:0"
18
+ report = {"model_dir": model_dir, "torch": torch.__version__}
19
+
20
+ t0 = time.time()
21
+ proc = AutoProcessor.from_pretrained(model_dir, trust_remote_code=True)
22
+ model = AutoModelForImageTextToText.from_pretrained(
23
+ model_dir, trust_remote_code=True, dtype=torch.bfloat16, device_map={"": dev}).eval()
24
+ report["load_s"] = round(time.time() - t0, 1)
25
+ report["gpu_mem_after_load_GiB"] = round(torch.cuda.memory_allocated() / 2**30, 2)
26
+
27
+ text = model.model.language_model
28
+ report["loop_plan_len"] = len(text._loop_plan)
29
+ routers = {n: p.dtype for n, p in model.named_parameters() if ".mhc_" in n}
30
+ report["mhc_param_count"] = len(routers)
31
+ report["mhc_all_fp32"] = all(d == torch.float32 for d in routers.values())
32
+ report["layer_param_dtypes"] = sorted({str(p.dtype) for n, p in model.named_parameters() if ".mhc_" not in n})
33
+
34
+ # Each physical looped layer must be called twice per forward (shared weights).
35
+ calls = {}
36
+ hooks = [l.register_forward_hook(lambda m, i, o, k=k: calls.__setitem__(k, calls.get(k, 0) + 1))
37
+ for k, l in enumerate(text.layers)]
38
+ tok = proc.tokenizer
39
+
40
+
41
+ def chat(messages, max_new=128, tools=None, thinking=False, images=None):
42
+ prompt = proc.apply_chat_template(messages, tools=tools, tokenize=False, add_generation_prompt=True,
43
+ enable_thinking=thinking)
44
+ inputs = proc(text=[prompt], images=images, return_tensors="pt").to(dev)
45
+ t = time.time()
46
+ with torch.inference_mode():
47
+ out = model.generate(**inputs, max_new_tokens=max_new, do_sample=False, use_cache=True)
48
+ gen = out[0, inputs["input_ids"].shape[1]:]
49
+ return {"prompt_tokens": int(inputs["input_ids"].shape[1]), "gen_tokens": int(gen.shape[0]),
50
+ "seconds": round(time.time() - t, 2), "text": tok.decode(gen, skip_special_tokens=False)}, out, inputs
51
+
52
+
53
+ r, _, _ = chat([{"role": "user", "content": "What is 13 times 7? Reply with the number only."}], 16)
54
+ report["text_mult"] = r
55
+ report["loop_calls_per_layer"] = {str(k): v for k, v in sorted(calls.items())}
56
+ report["looped_layers_called_twice"] = all(calls[k] == (2 if 4 <= k < 28 else 1) * calls[0] for k in calls)
57
+ for h in hooks:
58
+ h.remove()
59
+
60
+ report["text_haiku"] = chat([{"role": "user", "content": "Write a haiku about a tennis ball."}], 64)[0]
61
+ report["thinking"] = chat([{"role": "user", "content": "If a train goes 60 km in 45 minutes, what is its speed in km/h?"}],
62
+ 384, thinking=True)[0]
63
+ tools = [{"type": "function", "function": {
64
+ "name": "get_weather", "description": "Get current weather for a city.",
65
+ "parameters": {"type": "object", "properties": {
66
+ "city": {"type": "string"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}},
67
+ "required": ["city"]}}}]
68
+ r = chat([{"role": "user", "content": "What's the weather in Paris in celsius?"}], 96, tools=tools)[0]
69
+ r["has_tool_call_xml"] = ("<tool_call>" in r["text"] and "<function=get_weather>" in r["text"]
70
+ and "<parameter=city>" in r["text"])
71
+ report["tool_call"] = r
72
+
73
+ if image_path:
74
+ img = Image.open(image_path).convert("RGB")
75
+ msgs = [{"role": "user", "content": [{"type": "image"},
76
+ {"type": "text", "text": "Describe this image in two sentences."}]}]
77
+ report["image"] = chat(msgs, 96, images=[img])[0]
78
+
79
+ # Cached generation consistency: greedy tokens from cached decoding must equal the
80
+ # argmax of a single uncached forward over prompt+generation (teacher forcing).
81
+ r, out, inputs = chat([{"role": "user", "content": "Explain in one paragraph why the sky is blue."}], 64)
82
+ plen = inputs["input_ids"].shape[1]
83
+ with torch.inference_mode():
84
+ logits = model(input_ids=out, use_cache=False).logits[0, plen - 1:-1].float()
85
+ pred = logits.argmax(-1)
86
+ gen = out[0, plen:]
87
+ report["cache_consistency"] = {"n": int(gen.numel()), "match": int((pred == gen).sum()),
88
+ "first_mismatch": int((pred != gen).nonzero()[0]) if (pred != gen).any() else None,
89
+ "finite_logits": bool(torch.isfinite(logits).all())}
90
+ report["peak_gpu_mem_GiB"] = round(torch.cuda.max_memory_allocated() / 2**30, 2)
91
+ json.dump(report, open(out_path, "w"), indent=2, ensure_ascii=False)
92
+ print(json.dumps(report, indent=2, ensure_ascii=False))
quantization/20_quantize.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """ModelOpt NVFP4 PTQ of SenseNova-Loop-9B-Preview through the real looped (2-visit) forward.
3
+
4
+ Recipes:
5
+ mlp - NVFP4 on decoder MLP gate/up/down only (conservative)
6
+ decoder - NVFP4 on all decoder linears (full-attn q/k/v/o, GDN in_proj_qkv/in_proj_z/out_proj, MLP)
7
+ Always higher precision: vision tower, lm_head, embeddings, GDN in_proj_a/in_proj_b (tiny gate projs),
8
+ GDN conv1d/A_log/dt_bias, norms, mHC routers (FP32, not nn.Linear), MTP head (not loaded by HF; copied BF16).
9
+ KV cache: not quantized.
10
+
11
+ usage: python -I scripts/20_quantize.py --recipe decoder --calib calib/calib_v1.jsonl --out work/nvfp4-decoder [--limit N]
12
+ """
13
+ import argparse, copy, json, os, sys, time
14
+ import torch
15
+ from PIL import Image
16
+ from transformers import AutoModelForImageTextToText, AutoProcessor
17
+ import modelopt
18
+ import modelopt.torch.quantization as mtq
19
+ from modelopt.torch.export import export_hf_checkpoint
20
+
21
+ ap = argparse.ArgumentParser()
22
+ ap.add_argument("--model", default="upstream/SenseNova-Loop-9B-Preview")
23
+ ap.add_argument("--recipe", choices=["mlp", "decoder"], required=True)
24
+ ap.add_argument("--calib", default="calib/calib_v1.jsonl")
25
+ ap.add_argument("--root", default=".", help="base dir for calib image paths")
26
+ ap.add_argument("--out", required=True)
27
+ ap.add_argument("--limit", type=int, default=0)
28
+ ap.add_argument("--max-len", type=int, default=2048)
29
+ args = ap.parse_args()
30
+ dev = "cuda:0"
31
+ torch.manual_seed(0)
32
+
33
+ proc = AutoProcessor.from_pretrained(args.model, trust_remote_code=True)
34
+ model = AutoModelForImageTextToText.from_pretrained(
35
+ args.model, trust_remote_code=True, dtype=torch.bfloat16, device_map={"": dev}).eval()
36
+ text = model.model.language_model
37
+
38
+ # --- quantization config -------------------------------------------------------------
39
+ cfg = copy.deepcopy(mtq.NVFP4_DEFAULT_CFG)
40
+ qc = cfg["quant_cfg"] # ModelOpt >=0.48: ordered list, later entries win
41
+ for pat in ["*lm_head*", "*visual*", "*vision*", "*mtp*", "*in_proj_a*", "*in_proj_b*",
42
+ "*mhc_*", "*embed_tokens*", "*conv1d*"]:
43
+ qc.append({"quantizer_name": pat, "enable": False})
44
+ if args.recipe == "mlp":
45
+ for pat in ["*self_attn*", "*linear_attn*"]:
46
+ qc.append({"quantizer_name": pat, "enable": False})
47
+
48
+ # --- calibration data: rendered with the model's own template + processor ----------------
49
+ rows = [json.loads(l) for l in open(args.calib)]
50
+ if args.limit:
51
+ rows = rows[: args.limit]
52
+
53
+
54
+ def render(row):
55
+ msgs, images = [], []
56
+ for m in row["messages"]:
57
+ m = dict(m)
58
+ if isinstance(m.get("content"), list):
59
+ parts = []
60
+ for p in m["content"]:
61
+ if p.get("type") == "image":
62
+ images.append(Image.open(os.path.join(args.root, p["image"])).convert("RGB"))
63
+ parts.append({"type": "image"})
64
+ else:
65
+ parts.append(p)
66
+ m["content"] = parts
67
+ msgs.append(m)
68
+ prompt = proc.apply_chat_template(msgs, tools=row.get("tools"), tokenize=False,
69
+ add_generation_prompt=False,
70
+ enable_thinking=bool(row.get("enable_thinking")))
71
+ batch = proc(text=[prompt], images=images or None, return_tensors="pt")
72
+ if batch["input_ids"].shape[1] > args.max_len and not images:
73
+ batch = {k: v[:, : args.max_len] for k, v in batch.items()}
74
+ return batch
75
+
76
+
77
+ batches = [render(r) for r in rows]
78
+ ntok = sum(int(b["input_ids"].shape[1]) for b in batches)
79
+ print(f"calib: {len(batches)} samples, {ntok} tokens, {sum(r.get('n_images', 0) for r in rows)} images", flush=True)
80
+
81
+ visits = {}
82
+ hooks = [l.register_forward_hook(lambda m, i, o, k=k: visits.__setitem__(k, visits.get(k, 0) + 1))
83
+ for k, l in enumerate(text.layers)]
84
+
85
+
86
+ def forward_loop(m):
87
+ t = time.time()
88
+ for i, b in enumerate(batches):
89
+ b = {k: v.to(dev) for k, v in b.items()}
90
+ with torch.inference_mode():
91
+ m(**b, use_cache=False)
92
+ if i % 32 == 0:
93
+ print(f" calib {i}/{len(batches)} {time.time() - t:.0f}s", flush=True)
94
+
95
+
96
+ t0 = time.time()
97
+ model = mtq.quantize(model, cfg, forward_loop)
98
+ for h in hooks:
99
+ h.remove()
100
+ calib_s = time.time() - t0
101
+ mtq.print_quant_summary(model) if hasattr(mtq, "print_quant_summary") else None
102
+
103
+ # --- bookkeeping: what got quantized ----------------------------------------------------
104
+ quantized, excluded = [], []
105
+ for name, mod in model.named_modules():
106
+ if isinstance(mod, torch.nn.Linear):
107
+ wq = getattr(mod, "weight_quantizer", None)
108
+ (quantized if (wq is not None and wq.is_enabled) else excluded).append((name, mod.weight.numel()))
109
+ total_params = sum(p.numel() for p in model.parameters())
110
+ q_params = sum(n for _, n in quantized)
111
+ looped = [k for k, v in visits.items() if v == 2 * len(batches)]
112
+ stats = {
113
+ "recipe": args.recipe, "modelopt": modelopt.__version__, "torch": torch.__version__,
114
+ "calib_file": args.calib, "calib_samples": len(batches), "calib_tokens": ntok, "calib_seconds": round(calib_s, 1),
115
+ "layers_visited_twice_per_sample": looped,
116
+ "quantized_linear_modules": len(quantized), "excluded_linear_modules": [n for n, _ in excluded],
117
+ "quantized_params": q_params, "total_params_hf_loaded": total_params,
118
+ "fraction_params_quantized": round(q_params / total_params, 4),
119
+ "quant_cfg": qc,
120
+ }
121
+
122
+ # Sanity: logits stay finite through the fake-quant model.
123
+ with torch.inference_mode():
124
+ b = {k: v.to(dev) for k, v in batches[0].items()}
125
+ stats["fakequant_logits_finite"] = bool(torch.isfinite(model(**b, use_cache=False).logits).all())
126
+
127
+ os.makedirs(args.out, exist_ok=True)
128
+ t0 = time.time()
129
+ with torch.inference_mode():
130
+ export_hf_checkpoint(model, export_dir=args.out)
131
+ stats["export_seconds"] = round(time.time() - t0, 1)
132
+ json.dump(stats, open(os.path.join(args.out, "quantization_stats.json"), "w"), indent=2)
133
+ print(json.dumps({k: v for k, v in stats.items() if k != "excluded_linear_modules"}, indent=2))
134
+ print("excluded linear modules:", len(excluded))
quantization/30_verify_export.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Independent structural verification of an exported NVFP4 checkpoint (reads files only).
3
+
4
+ Checks against the upstream checkpoint: every upstream tensor is present (as BF16/FP32 or as an
5
+ NVFP4 packed weight + scales), packed shapes are consistent (uint8 [out, in/2], fp8 e4m3 block scales
6
+ [out, in/16], fp32 global scales), all scales finite/positive, mHC routers FP32, exactly one copy of
7
+ each physical (shared, looped) layer, MTP tensors present, config/quant metadata consistent.
8
+
9
+ usage: python -I scripts/30_verify_export.py EXPORT_DIR UPSTREAM_DIR OUT_JSON
10
+ """
11
+ import json, os, sys, collections
12
+ import torch
13
+ from safetensors import safe_open
14
+
15
+ exp, up, out = sys.argv[1:4]
16
+
17
+
18
+ def index(d):
19
+ idx = os.path.join(d, "model.safetensors.index.json")
20
+ if os.path.exists(idx):
21
+ wm = json.load(open(idx))["weight_map"]
22
+ else:
23
+ wm = {}
24
+ for f in os.listdir(d):
25
+ if f.endswith(".safetensors"):
26
+ with safe_open(os.path.join(d, f), "pt") as fh:
27
+ wm.update({k: f for k in fh.keys()})
28
+ return wm
29
+
30
+
31
+ def header(d, wm):
32
+ meta = {}
33
+ for f in set(wm.values()):
34
+ with safe_open(os.path.join(d, f), "pt") as fh:
35
+ for k in fh.keys():
36
+ sl = fh.get_slice(k)
37
+ meta[k] = (sl.get_dtype(), tuple(sl.get_shape()), f)
38
+ return meta
39
+
40
+
41
+ ewm, uwm = index(exp), index(up)
42
+ E, U = header(exp, ewm), header(up, uwm)
43
+ qcfg = json.load(open(os.path.join(exp, "hf_quant_config.json")))["quantization"]
44
+ cfg = json.load(open(os.path.join(exp, "config.json")))
45
+ rep = {"export_dir": exp, "quant_algo": qcfg.get("quant_algo"), "group_size": qcfg.get("group_size"),
46
+ "kv_cache_quant_algo": qcfg.get("kv_cache_quant_algo"),
47
+ "config_has_quantization_config": "quantization_config" in cfg,
48
+ "n_tensors_export": len(E), "n_tensors_upstream": len(U)}
49
+ problems = []
50
+
51
+ packed = [k[: -len(".weight")] for k, (dt, _, _) in E.items() if k.endswith(".weight") and dt == "U8"]
52
+ rep["nvfp4_linear_modules"] = len(packed)
53
+ q_params = 0
54
+ for m in packed:
55
+ dt, shp, _ = E[m + ".weight"]
56
+ ref = U.get(m + ".weight")
57
+ if ref is None:
58
+ problems.append(f"packed {m} not in upstream"); continue
59
+ o, i = ref[1]
60
+ q_params += o * i
61
+ if shp != (o, i // 2):
62
+ problems.append(f"{m}: packed shape {shp} != {(o, i // 2)}")
63
+ s = E.get(m + ".weight_scale")
64
+ if s is None or s[0] != "F8_E4M3" or s[1] != (o, i // qcfg["group_size"]):
65
+ problems.append(f"{m}: weight_scale {s}")
66
+ for k in ("weight_scale_2", "input_scale"):
67
+ t = E.get(f"{m}.{k}")
68
+ if t is None or t[0] != "F32" or (len(t[1]) and t[1] != (1,)):
69
+ problems.append(f"{m}: {k} {t}")
70
+
71
+ # Load all scales and check finite / positive.
72
+ bad_scales, n_scales = 0, 0
73
+ by_file = collections.defaultdict(list)
74
+ for k in E:
75
+ if k.endswith(("weight_scale", "weight_scale_2", "input_scale")):
76
+ by_file[E[k][2]].append(k)
77
+ for f, keys in by_file.items():
78
+ with safe_open(os.path.join(exp, f), "pt") as fh:
79
+ for k in keys:
80
+ t = fh.get_tensor(k).float()
81
+ n_scales += 1
82
+ if not torch.isfinite(t).all() or (t[t != 0] <= 0).any() or (k.endswith("input_scale") and (t <= 0).any()):
83
+ bad_scales += 1
84
+ problems.append(f"bad scale {k}")
85
+ rep["scale_tensors_checked"] = n_scales
86
+ rep["bad_scale_tensors"] = bad_scales
87
+
88
+ # Upstream coverage: every upstream tensor present in export (either same name or packed).
89
+ missing = [k for k in U if k not in E]
90
+ rep["upstream_tensors_missing_in_export"] = missing[:20]
91
+ rep["n_upstream_missing"] = len(missing)
92
+ extra = [k for k in E if k not in U and not k.endswith(("weight_scale", "weight_scale_2", "input_scale"))]
93
+ rep["unexpected_extra_tensors"] = extra[:20]
94
+
95
+ mhc = {k: E[k][0] for k in E if ".mhc_" in k}
96
+ rep["mhc_tensors"] = len(mhc)
97
+ rep["mhc_all_fp32"] = all(v == "F32" for v in mhc.values()) and len(mhc) == 96
98
+ mtp = [k for k in E if k.startswith("mtp.")]
99
+ rep["mtp_tensors"] = len(mtp)
100
+ rep["mtp_dtypes"] = sorted({E[k][0] for k in mtp})
101
+
102
+ # Shared weights: layer ids present must be exactly 0..31 (no duplicated per-visit copies).
103
+ lids = sorted({int(k.split(".layers.")[1].split(".")[0]) for k in E if "language_model.layers." in k})
104
+ rep["physical_layer_ids"] = f"{lids[0]}..{lids[-1]} ({len(lids)})"
105
+ if lids != list(range(32)):
106
+ problems.append(f"layer ids {lids}")
107
+
108
+ dtypes = collections.Counter()
109
+ nbytes = collections.Counter()
110
+ size = {"U8": 1, "F8_E4M3": 1, "BF16": 2, "F16": 2, "F32": 4}
111
+ import math
112
+ for k, (dt, shp, _) in E.items():
113
+ n = math.prod(shp)
114
+ dtypes[dt] += n
115
+ nbytes[dt] += n * size.get(dt, 0)
116
+ rep["elements_by_dtype"] = dict(dtypes)
117
+ rep["GB_by_dtype"] = {k: round(v / 1e9, 3) for k, v in nbytes.items()}
118
+ rep["total_file_GB"] = round(sum(os.path.getsize(os.path.join(exp, f)) for f in set(ewm.values())) / 1e9, 3)
119
+ total_up = sum(math.prod(s) for (_, s, _) in U.values())
120
+ rep["upstream_params_incl_mtp"] = total_up
121
+ rep["nvfp4_params"] = q_params
122
+ rep["fraction_params_nvfp4"] = round(q_params / total_up, 4)
123
+ rep["problems"] = problems
124
+ rep["ok"] = not problems and not missing and rep["mhc_all_fp32"] and len(mtp) > 0
125
+ json.dump(rep, open(out, "w"), indent=2)
126
+ print(json.dumps(rep, indent=2))
quantization/build_calib.py ADDED
@@ -0,0 +1,329 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python
2
+ """Build calib/calib_v1.jsonl: 256 chat-format calibration examples for NVFP4 (ModelOpt) PTQ.
3
+
4
+ .venv-eval/bin/python calib/build_calib.py # raw files are expected under calib/raw/
5
+ (downloads: see SOURCES; fetched with huggingface_hub.hf_hub_download at the pinned revisions)
6
+
7
+ Each line: {"id", "category", "source", "messages": [...OpenAI-style...], "tools"?: [...],
8
+ "enable_thinking": bool, "approx_text_tokens": int, "n_images": int}
9
+ Images: {"type": "image", "image": "calib/images/<file>.jpg"} (path relative to project root),
10
+ which HF processors' apply_chat_template understand; the chat template also accepts image_url.
11
+ Thinking examples carry the trace in the assistant's `reasoning_content` (the chat template renders
12
+ it as <think>\n...\n</think>\n\n before the answer); all other final assistant turns render with
13
+ the empty <think>\n\n</think>\n\n block, matching enable_thinking=False inference.
14
+ Disjointness from eval/data is enforced by construction (different datasets/splits) AND checked
15
+ by 13-gram overlap against every eval prompt (summary in calib/calib_v1.meta.json).
16
+ """
17
+ from __future__ import annotations
18
+
19
+ import hashlib
20
+ import json
21
+ import random
22
+ import re
23
+ import sys
24
+ from collections import Counter
25
+ from pathlib import Path
26
+
27
+ import pyarrow.parquet as pq
28
+
29
+ ROOT = Path(__file__).resolve().parent.parent
30
+ CAL = ROOT / "calib"
31
+ RAW = CAL / "raw"
32
+ SEED = 20261030
33
+ MAX_TEXT_TOKENS = 1800
34
+ TARGET = {"chat": 77, "code": 51, "math": 51, "tools": 38, "image": 39} # = 256 (30/20/20/15/15 %)
35
+ N_THINK_MATH = 26
36
+
37
+ SOURCES = {
38
+ "chat": dict(repo="OpenAssistant/oasst2", rev="179dd21fc55192153d94adb0e0ce8f69e222bf75",
39
+ file="data/train-00000-of-00001-88ba0162028a73fc.parquet", split="train", license="Apache-2.0"),
40
+ "code": dict(repo="google-research-datasets/mbpp", rev="4bb6404fdc6cacfda99d4ac4205087b89d32030c",
41
+ file="full/train-00000-of-00001.parquet", split="full/train", license="CC-BY-4.0"),
42
+ "math": dict(repo="openai/gsm8k", rev="740312add88f781978c0658806c59bc2815b9866",
43
+ file="main/train-00000-of-00001.parquet", split="main/train", license="MIT"),
44
+ "tools": dict(repo="glaiveai/glaive-function-calling-v2", rev="e7f4b6456019f5d8bcb991ef0dd67d8ff23221ac",
45
+ file="glaive-function-calling-v2.json", split="train", license="Apache-2.0"),
46
+ "image": dict(repo="lmms-lab/textvqa", rev="9c0699cd19768ac5ab97568f6b3cbac4c0062884",
47
+ file="data/train-00000-of-00020.parquet", split="train (shard 0 of 20)",
48
+ license="TextVQA annotations CC-BY-4.0; images OpenImages CC-BY-2.0 (Flickr attribution)"),
49
+ }
50
+
51
+
52
+ def raw(cat):
53
+ s = SOURCES[cat]
54
+ return RAW / s["repo"].replace("/", "__") / s["file"]
55
+
56
+
57
+ _tok = None
58
+
59
+
60
+ def ntok(text):
61
+ global _tok
62
+ if _tok is None:
63
+ from tokenizers import Tokenizer
64
+ _tok = Tokenizer.from_file(str(ROOT / "upstream/SenseNova-Loop-9B-Preview/tokenizer.json"))
65
+ return len(_tok.encode(text, add_special_tokens=False).ids)
66
+
67
+
68
+ def ex_text(ex):
69
+ parts = [json.dumps(ex.get("tools") or "")]
70
+ for m in ex["messages"]:
71
+ c = m.get("content")
72
+ if isinstance(c, list):
73
+ c = " ".join(p.get("text", "") for p in c)
74
+ parts.append(c or "")
75
+ parts.append(m.get("reasoning_content") or "")
76
+ parts.append(json.dumps(m.get("tool_calls") or ""))
77
+ return "\n".join(parts)
78
+
79
+
80
+ # ----------------------------------------------------------------------------- chat (oasst2)
81
+ def build_chat(rng):
82
+ t = pq.read_table(raw("chat"), columns=["message_id", "parent_id", "text", "role", "lang", "deleted",
83
+ "review_result", "rank", "tree_state", "synthetic"]).to_pylist()
84
+ kids = {}
85
+ roots = []
86
+ for m in t:
87
+ if m["deleted"] or m["review_result"] is False or m["synthetic"]:
88
+ continue
89
+ if m["parent_id"] is None:
90
+ if m["role"] == "prompter" and m["tree_state"] == "ready_for_export":
91
+ roots.append(m)
92
+ else:
93
+ kids.setdefault(m["parent_id"], []).append(m)
94
+
95
+ def best(mid, role):
96
+ cs = [c for c in kids.get(mid, []) if c["role"] == role]
97
+ cs.sort(key=lambda c: (c["rank"] if c["rank"] is not None else 99, c["message_id"]))
98
+ return cs[0] if cs else None
99
+ roots.sort(key=lambda m: m["message_id"])
100
+ rng.shuffle(roots)
101
+ want = {"en": 58, "zh": 10, "other": 9}
102
+ got = {"en": [], "zh": [], "other": []}
103
+ for r in roots:
104
+ bucket = r["lang"] if r["lang"] in ("en", "zh") else "other"
105
+ if len(got[bucket]) >= want[bucket]:
106
+ continue
107
+ msgs, cur = [{"role": "user", "content": r["text"]}], r
108
+ turns = rng.choice([1, 1, 2])
109
+ for k in range(turns):
110
+ a = best(cur["message_id"], "assistant")
111
+ if a is None:
112
+ break
113
+ msgs.append({"role": "assistant", "content": a["text"]})
114
+ if k + 1 < turns:
115
+ u = best(a["message_id"], "prompter")
116
+ a2 = best(u["message_id"], "assistant") if u else None
117
+ if not (u and a2):
118
+ break
119
+ msgs.append({"role": "user", "content": u["text"]})
120
+ cur = u
121
+ if msgs[-1]["role"] != "assistant":
122
+ continue
123
+ ex = {"category": "chat", "source": f"oasst2:{r['message_id']}", "lang": r["lang"], "messages": msgs}
124
+ if ntok(ex_text(ex)) > MAX_TEXT_TOKENS:
125
+ continue
126
+ got[bucket].append(ex)
127
+ if all(len(got[b]) >= want[b] for b in want):
128
+ break
129
+ return got["en"] + got["zh"] + got["other"]
130
+
131
+
132
+ # ----------------------------------------------------------------------------- code (MBPP train)
133
+ def build_code(rng):
134
+ rows = pq.read_table(raw("code")).to_pylist()
135
+ rows.sort(key=lambda r: r["task_id"])
136
+ idx = sorted(rng.sample(range(len(rows)), TARGET["code"]))
137
+ out = []
138
+ for i in idx:
139
+ r = rows[i]
140
+ tests = "\n".join(r["test_list"])
141
+ user = f"{r['text']} Your code should pass these tests:\n\n```python\n{tests}\n```"
142
+ asst = f"```python\n{r['code'].strip()}\n```"
143
+ out.append({"category": "code", "source": f"mbpp:task_id={r['task_id']}",
144
+ "messages": [{"role": "user", "content": user}, {"role": "assistant", "content": asst}]})
145
+ return out
146
+
147
+
148
+ # ----------------------------------------------------------------------------- math (GSM8K train)
149
+ def build_math(rng):
150
+ rows = pq.read_table(raw("math")).to_pylist()
151
+ idx = sorted(rng.sample(range(len(rows)), TARGET["math"]))
152
+ out = []
153
+ for j, i in enumerate(idx):
154
+ q, sol = rows[i]["question"], rows[i]["answer"]
155
+ steps, final = sol.split("####")
156
+ steps = re.sub(r"<<[^>]*>>", "", steps).strip()
157
+ final = final.strip()
158
+ think = j < N_THINK_MATH
159
+ if think:
160
+ asst = {"role": "assistant", "reasoning_content": steps,
161
+ "content": f"The final answer is {final}."}
162
+ else:
163
+ asst = {"role": "assistant", "content": f"{steps}\nThe final answer is {final}."}
164
+ out.append({"category": "math", "source": f"gsm8k:train:{i}", "enable_thinking": think,
165
+ "messages": [{"role": "user", "content": q}, asst]})
166
+ return out
167
+
168
+
169
+ # ----------------------------------------------------------------------------- tools (glaive v2)
170
+ def parse_glaive(rec):
171
+ sysm = rec["system"]
172
+ if "following functions" not in sysm:
173
+ return None
174
+ body = sysm.split("Use them if required -", 1)[-1].strip()
175
+ dec, pos, fns = json.JSONDecoder(), 0, []
176
+ while pos < len(body):
177
+ while pos < len(body) and body[pos] in " \n\t":
178
+ pos += 1
179
+ if pos >= len(body):
180
+ break
181
+ try:
182
+ obj, pos = dec.raw_decode(body, pos)
183
+ except json.JSONDecodeError:
184
+ return None
185
+ fns.append({"type": "function", "function": obj})
186
+ if not fns:
187
+ return None
188
+ turns = re.split(r"\n*(USER|ASSISTANT|FUNCTION RESPONSE): ", "\n" + rec["chat"])
189
+ msgs, n_calls = [], 0
190
+ it = iter(turns[1:])
191
+ for role, text in zip(it, it):
192
+ text = text.replace("<|endoftext|>", "").strip()
193
+ if role == "USER":
194
+ msgs.append({"role": "user", "content": text})
195
+ elif role == "FUNCTION RESPONSE":
196
+ msgs.append({"role": "tool", "content": text})
197
+ else:
198
+ if "<functioncall>" in text:
199
+ pre, call = text.split("<functioncall>", 1)
200
+ m = re.match(r'\s*\{\s*"name"\s*:\s*"([^"]+)"\s*,\s*"arguments"\s*:\s*\'(.*)\'\s*\}\s*$', call, re.S)
201
+ if not m:
202
+ return None
203
+ try:
204
+ args = json.loads(m.group(2))
205
+ except json.JSONDecodeError:
206
+ return None
207
+ if not isinstance(args, dict):
208
+ return None
209
+ msgs.append({"role": "assistant", "content": pre.strip(),
210
+ "tool_calls": [{"id": f"call_{n_calls}", "type": "function",
211
+ "function": {"name": m.group(1), "arguments": args}}]})
212
+ n_calls += 1
213
+ else:
214
+ msgs.append({"role": "assistant", "content": text})
215
+ if n_calls == 0 or not msgs or msgs[0]["role"] != "user" or msgs[-1]["role"] != "assistant":
216
+ return None
217
+ if not any(m["role"] == "tool" for m in msgs):
218
+ return None
219
+ return fns, msgs
220
+
221
+
222
+ def build_tools(rng):
223
+ data = json.load(open(raw("tools")))
224
+ order = list(range(len(data)))
225
+ rng.shuffle(order)
226
+ out = []
227
+ for i in order:
228
+ p = parse_glaive(data[i])
229
+ if not p:
230
+ continue
231
+ ex = {"category": "tools", "source": f"glaive-fc-v2:{i}", "tools": p[0], "messages": p[1]}
232
+ if ntok(ex_text(ex)) > MAX_TEXT_TOKENS:
233
+ continue
234
+ out.append(ex)
235
+ if len(out) >= TARGET["tools"]:
236
+ break
237
+ return out
238
+
239
+
240
+ # ----------------------------------------------------------------------------- image (TextVQA train)
241
+ def build_image(rng, eval_image_ids):
242
+ from tasks import vqa_norm # eval/ is on sys.path
243
+ t = pq.read_table(raw("image"), columns=["question_id", "question", "answers", "image_id"]).to_pylist()
244
+ cand = [i for i, r in enumerate(t) if r["image_id"] not in eval_image_ids
245
+ and Counter(vqa_norm(a) for a in r["answers"]).most_common(1)[0][1] >= 3]
246
+ rng.shuffle(cand)
247
+ seen_img, pickd = set(), []
248
+ for i in cand:
249
+ if t[i]["image_id"] in seen_img:
250
+ continue
251
+ seen_img.add(t[i]["image_id"])
252
+ pickd.append(i)
253
+ if len(pickd) >= TARGET["image"]:
254
+ break
255
+ pickd.sort()
256
+ imgs = pq.read_table(raw("image"), columns=["image"]).take(pickd).column("image").to_pylist()
257
+ (CAL / "images").mkdir(exist_ok=True)
258
+ out = []
259
+ for i, im in zip(pickd, imgs):
260
+ r = t[i]
261
+ b = im["bytes"]
262
+ ext = "png" if b[:4] == b"\x89PNG" else "jpg"
263
+ rel = f"calib/images/textvqa_train_{r['question_id']}.{ext}"
264
+ (ROOT / rel).write_bytes(b)
265
+ ans = Counter(a.strip() for a in r["answers"]).most_common(1)[0][0]
266
+ out.append({"category": "image", "source": f"textvqa:train:qid={r['question_id']}", "image_id": r["image_id"],
267
+ "image_sha256": hashlib.sha256(b).hexdigest(), "messages": [
268
+ {"role": "user", "content": [{"type": "image", "image": rel},
269
+ {"type": "text", "text": r["question"].strip().capitalize()
270
+ + "\nAnswer the question using a single word or phrase."}]},
271
+ {"role": "assistant", "content": ans}]})
272
+ return out
273
+
274
+
275
+ # ----------------------------------------------------------------------------- overlap check
276
+ def shingles(text, n=13):
277
+ w = re.findall(r"\w+", text.lower())
278
+ return {" ".join(w[i:i + n]) for i in range(max(0, len(w) - n + 1))}
279
+
280
+
281
+ def main():
282
+ sys.path.insert(0, str(ROOT / "eval"))
283
+ eval_dir = ROOT / "eval/data"
284
+ eval_sh, eval_imgs = set(), set()
285
+ for p in eval_dir.glob("*.jsonl"):
286
+ for l in open(p):
287
+ r = json.loads(l)
288
+ eval_sh |= shingles(json.dumps(r, ensure_ascii=False))
289
+ if "image_id" in r:
290
+ eval_imgs.add(r["image_id"])
291
+ exs = []
292
+ exs += build_chat(random.Random(f"{SEED}-chat"))
293
+ exs += build_code(random.Random(f"{SEED}-code"))
294
+ exs += build_math(random.Random(f"{SEED}-math"))
295
+ exs += build_tools(random.Random(f"{SEED}-tools"))
296
+ exs += build_image(random.Random(f"{SEED}-image"), eval_imgs)
297
+ cnt = Counter(e["category"] for e in exs)
298
+ assert dict(cnt) == TARGET, cnt
299
+ overlaps = []
300
+ for k, e in enumerate(exs):
301
+ e["id"] = f"calib_v1_{k:03d}"
302
+ e.setdefault("enable_thinking", False)
303
+ e["approx_text_tokens"] = ntok(ex_text(e))
304
+ e["n_images"] = sum(1 for m in e["messages"] if isinstance(m["content"], list)
305
+ for p in m["content"] if p.get("type") == "image")
306
+ ov = shingles(ex_text(e)) & eval_sh
307
+ if ov:
308
+ overlaps.append({"id": e["id"], "source": e["source"], "n_shared_13grams": len(ov),
309
+ "example": sorted(ov)[0]})
310
+ order = ["id", "category", "source", "enable_thinking", "approx_text_tokens", "n_images"]
311
+ with open(CAL / "calib_v1.jsonl", "w") as f:
312
+ for e in exs:
313
+ f.write(json.dumps({**{k: e[k] for k in order}, **{k: v for k, v in e.items() if k not in order}},
314
+ ensure_ascii=False) + "\n")
315
+ toks = [e["approx_text_tokens"] for e in exs]
316
+ meta = {"version": "calib_v1", "seed": SEED, "n": len(exs), "counts": dict(cnt),
317
+ "n_thinking_examples": sum(e["enable_thinking"] for e in exs),
318
+ "langs_chat": dict(Counter(e.get("lang") for e in exs if e["category"] == "chat")),
319
+ "text_tokens": {"min": min(toks), "max": max(toks), "mean": sum(toks) / len(toks), "sum": sum(toks)},
320
+ "max_text_tokens_filter": MAX_TEXT_TOKENS, "sources": SOURCES,
321
+ "eval_overlap_13gram": overlaps,
322
+ "sha256": hashlib.sha256((CAL / "calib_v1.jsonl").read_bytes()).hexdigest()}
323
+ (CAL / "calib_v1.meta.json").write_text(json.dumps(meta, indent=2, ensure_ascii=False))
324
+ print(json.dumps({k: meta[k] for k in ("n", "counts", "n_thinking_examples", "langs_chat", "text_tokens")}))
325
+ print("13-gram overlaps with eval:", len(overlaps), overlaps[:3])
326
+
327
+
328
+ if __name__ == "__main__":
329
+ main()
quantization/calib_v1.jsonl.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ 9a4d2112ef71820a5a6d8b015b4996092d4c08c3a2aa1abf7303aa5cd3b7ff83 calib/calib_v1.jsonl
quantization/calib_v1.meta.json ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "calib_v1",
3
+ "seed": 20261030,
4
+ "n": 256,
5
+ "counts": {
6
+ "chat": 77,
7
+ "code": 51,
8
+ "math": 51,
9
+ "tools": 38,
10
+ "image": 39
11
+ },
12
+ "n_thinking_examples": 26,
13
+ "langs_chat": {
14
+ "en": 58,
15
+ "zh": 10,
16
+ "es": 7,
17
+ "ca": 2
18
+ },
19
+ "text_tokens": {
20
+ "min": 25,
21
+ "max": 1551,
22
+ "mean": 233.29296875,
23
+ "sum": 59723
24
+ },
25
+ "max_text_tokens_filter": 1800,
26
+ "sources": {
27
+ "chat": {
28
+ "repo": "OpenAssistant/oasst2",
29
+ "rev": "179dd21fc55192153d94adb0e0ce8f69e222bf75",
30
+ "file": "data/train-00000-of-00001-88ba0162028a73fc.parquet",
31
+ "split": "train",
32
+ "license": "Apache-2.0"
33
+ },
34
+ "code": {
35
+ "repo": "google-research-datasets/mbpp",
36
+ "rev": "4bb6404fdc6cacfda99d4ac4205087b89d32030c",
37
+ "file": "full/train-00000-of-00001.parquet",
38
+ "split": "full/train",
39
+ "license": "CC-BY-4.0"
40
+ },
41
+ "math": {
42
+ "repo": "openai/gsm8k",
43
+ "rev": "740312add88f781978c0658806c59bc2815b9866",
44
+ "file": "main/train-00000-of-00001.parquet",
45
+ "split": "main/train",
46
+ "license": "MIT"
47
+ },
48
+ "tools": {
49
+ "repo": "glaiveai/glaive-function-calling-v2",
50
+ "rev": "e7f4b6456019f5d8bcb991ef0dd67d8ff23221ac",
51
+ "file": "glaive-function-calling-v2.json",
52
+ "split": "train",
53
+ "license": "Apache-2.0"
54
+ },
55
+ "image": {
56
+ "repo": "lmms-lab/textvqa",
57
+ "rev": "9c0699cd19768ac5ab97568f6b3cbac4c0062884",
58
+ "file": "data/train-00000-of-00020.parquet",
59
+ "split": "train (shard 0 of 20)",
60
+ "license": "TextVQA annotations CC-BY-4.0; images OpenImages CC-BY-2.0 (Flickr attribution)"
61
+ }
62
+ },
63
+ "eval_overlap_13gram": [
64
+ {
65
+ "id": "calib_v1_203",
66
+ "source": "glaive-fc-v2:108408",
67
+ "n_shared_13grams": 1,
68
+ "example": "type function function name send_email description send an email parameters type object properties"
69
+ }
70
+ ],
71
+ "sha256": "9a4d2112ef71820a5a6d8b015b4996092d4c08c3a2aa1abf7303aa5cd3b7ff83"
72
+ }
quantization/quantization_stats.json ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "recipe": "decoder",
3
+ "modelopt": "0.48.0rc1",
4
+ "torch": "2.11.0+cu130",
5
+ "calib_file": "calib/calib_v1.jsonl",
6
+ "calib_samples": 256,
7
+ "calib_tokens": 98458,
8
+ "calib_seconds": 1579.2,
9
+ "layers_visited_twice_per_sample": [
10
+ 4,
11
+ 5,
12
+ 6,
13
+ 7,
14
+ 8,
15
+ 9,
16
+ 10,
17
+ 11,
18
+ 12,
19
+ 13,
20
+ 14,
21
+ 15,
22
+ 16,
23
+ 17,
24
+ 18,
25
+ 19,
26
+ 20,
27
+ 21,
28
+ 22,
29
+ 23,
30
+ 24,
31
+ 25,
32
+ 26,
33
+ 27
34
+ ],
35
+ "quantized_linear_modules": 200,
36
+ "excluded_linear_modules": [
37
+ "model.visual.blocks.0.attn.qkv",
38
+ "model.visual.blocks.0.attn.proj",
39
+ "model.visual.blocks.0.mlp.linear_fc1",
40
+ "model.visual.blocks.0.mlp.linear_fc2",
41
+ "model.visual.blocks.1.attn.qkv",
42
+ "model.visual.blocks.1.attn.proj",
43
+ "model.visual.blocks.1.mlp.linear_fc1",
44
+ "model.visual.blocks.1.mlp.linear_fc2",
45
+ "model.visual.blocks.2.attn.qkv",
46
+ "model.visual.blocks.2.attn.proj",
47
+ "model.visual.blocks.2.mlp.linear_fc1",
48
+ "model.visual.blocks.2.mlp.linear_fc2",
49
+ "model.visual.blocks.3.attn.qkv",
50
+ "model.visual.blocks.3.attn.proj",
51
+ "model.visual.blocks.3.mlp.linear_fc1",
52
+ "model.visual.blocks.3.mlp.linear_fc2",
53
+ "model.visual.blocks.4.attn.qkv",
54
+ "model.visual.blocks.4.attn.proj",
55
+ "model.visual.blocks.4.mlp.linear_fc1",
56
+ "model.visual.blocks.4.mlp.linear_fc2",
57
+ "model.visual.blocks.5.attn.qkv",
58
+ "model.visual.blocks.5.attn.proj",
59
+ "model.visual.blocks.5.mlp.linear_fc1",
60
+ "model.visual.blocks.5.mlp.linear_fc2",
61
+ "model.visual.blocks.6.attn.qkv",
62
+ "model.visual.blocks.6.attn.proj",
63
+ "model.visual.blocks.6.mlp.linear_fc1",
64
+ "model.visual.blocks.6.mlp.linear_fc2",
65
+ "model.visual.blocks.7.attn.qkv",
66
+ "model.visual.blocks.7.attn.proj",
67
+ "model.visual.blocks.7.mlp.linear_fc1",
68
+ "model.visual.blocks.7.mlp.linear_fc2",
69
+ "model.visual.blocks.8.attn.qkv",
70
+ "model.visual.blocks.8.attn.proj",
71
+ "model.visual.blocks.8.mlp.linear_fc1",
72
+ "model.visual.blocks.8.mlp.linear_fc2",
73
+ "model.visual.blocks.9.attn.qkv",
74
+ "model.visual.blocks.9.attn.proj",
75
+ "model.visual.blocks.9.mlp.linear_fc1",
76
+ "model.visual.blocks.9.mlp.linear_fc2",
77
+ "model.visual.blocks.10.attn.qkv",
78
+ "model.visual.blocks.10.attn.proj",
79
+ "model.visual.blocks.10.mlp.linear_fc1",
80
+ "model.visual.blocks.10.mlp.linear_fc2",
81
+ "model.visual.blocks.11.attn.qkv",
82
+ "model.visual.blocks.11.attn.proj",
83
+ "model.visual.blocks.11.mlp.linear_fc1",
84
+ "model.visual.blocks.11.mlp.linear_fc2",
85
+ "model.visual.blocks.12.attn.qkv",
86
+ "model.visual.blocks.12.attn.proj",
87
+ "model.visual.blocks.12.mlp.linear_fc1",
88
+ "model.visual.blocks.12.mlp.linear_fc2",
89
+ "model.visual.blocks.13.attn.qkv",
90
+ "model.visual.blocks.13.attn.proj",
91
+ "model.visual.blocks.13.mlp.linear_fc1",
92
+ "model.visual.blocks.13.mlp.linear_fc2",
93
+ "model.visual.blocks.14.attn.qkv",
94
+ "model.visual.blocks.14.attn.proj",
95
+ "model.visual.blocks.14.mlp.linear_fc1",
96
+ "model.visual.blocks.14.mlp.linear_fc2",
97
+ "model.visual.blocks.15.attn.qkv",
98
+ "model.visual.blocks.15.attn.proj",
99
+ "model.visual.blocks.15.mlp.linear_fc1",
100
+ "model.visual.blocks.15.mlp.linear_fc2",
101
+ "model.visual.blocks.16.attn.qkv",
102
+ "model.visual.blocks.16.attn.proj",
103
+ "model.visual.blocks.16.mlp.linear_fc1",
104
+ "model.visual.blocks.16.mlp.linear_fc2",
105
+ "model.visual.blocks.17.attn.qkv",
106
+ "model.visual.blocks.17.attn.proj",
107
+ "model.visual.blocks.17.mlp.linear_fc1",
108
+ "model.visual.blocks.17.mlp.linear_fc2",
109
+ "model.visual.blocks.18.attn.qkv",
110
+ "model.visual.blocks.18.attn.proj",
111
+ "model.visual.blocks.18.mlp.linear_fc1",
112
+ "model.visual.blocks.18.mlp.linear_fc2",
113
+ "model.visual.blocks.19.attn.qkv",
114
+ "model.visual.blocks.19.attn.proj",
115
+ "model.visual.blocks.19.mlp.linear_fc1",
116
+ "model.visual.blocks.19.mlp.linear_fc2",
117
+ "model.visual.blocks.20.attn.qkv",
118
+ "model.visual.blocks.20.attn.proj",
119
+ "model.visual.blocks.20.mlp.linear_fc1",
120
+ "model.visual.blocks.20.mlp.linear_fc2",
121
+ "model.visual.blocks.21.attn.qkv",
122
+ "model.visual.blocks.21.attn.proj",
123
+ "model.visual.blocks.21.mlp.linear_fc1",
124
+ "model.visual.blocks.21.mlp.linear_fc2",
125
+ "model.visual.blocks.22.attn.qkv",
126
+ "model.visual.blocks.22.attn.proj",
127
+ "model.visual.blocks.22.mlp.linear_fc1",
128
+ "model.visual.blocks.22.mlp.linear_fc2",
129
+ "model.visual.blocks.23.attn.qkv",
130
+ "model.visual.blocks.23.attn.proj",
131
+ "model.visual.blocks.23.mlp.linear_fc1",
132
+ "model.visual.blocks.23.mlp.linear_fc2",
133
+ "model.visual.blocks.24.attn.qkv",
134
+ "model.visual.blocks.24.attn.proj",
135
+ "model.visual.blocks.24.mlp.linear_fc1",
136
+ "model.visual.blocks.24.mlp.linear_fc2",
137
+ "model.visual.blocks.25.attn.qkv",
138
+ "model.visual.blocks.25.attn.proj",
139
+ "model.visual.blocks.25.mlp.linear_fc1",
140
+ "model.visual.blocks.25.mlp.linear_fc2",
141
+ "model.visual.blocks.26.attn.qkv",
142
+ "model.visual.blocks.26.attn.proj",
143
+ "model.visual.blocks.26.mlp.linear_fc1",
144
+ "model.visual.blocks.26.mlp.linear_fc2",
145
+ "model.visual.merger.linear_fc1",
146
+ "model.visual.merger.linear_fc2",
147
+ "model.language_model.layers.0.linear_attn.in_proj_b",
148
+ "model.language_model.layers.0.linear_attn.in_proj_a",
149
+ "model.language_model.layers.1.linear_attn.in_proj_b",
150
+ "model.language_model.layers.1.linear_attn.in_proj_a",
151
+ "model.language_model.layers.2.linear_attn.in_proj_b",
152
+ "model.language_model.layers.2.linear_attn.in_proj_a",
153
+ "model.language_model.layers.4.linear_attn.in_proj_b",
154
+ "model.language_model.layers.4.linear_attn.in_proj_a",
155
+ "model.language_model.layers.5.linear_attn.in_proj_b",
156
+ "model.language_model.layers.5.linear_attn.in_proj_a",
157
+ "model.language_model.layers.6.linear_attn.in_proj_b",
158
+ "model.language_model.layers.6.linear_attn.in_proj_a",
159
+ "model.language_model.layers.8.linear_attn.in_proj_b",
160
+ "model.language_model.layers.8.linear_attn.in_proj_a",
161
+ "model.language_model.layers.9.linear_attn.in_proj_b",
162
+ "model.language_model.layers.9.linear_attn.in_proj_a",
163
+ "model.language_model.layers.10.linear_attn.in_proj_b",
164
+ "model.language_model.layers.10.linear_attn.in_proj_a",
165
+ "model.language_model.layers.12.linear_attn.in_proj_b",
166
+ "model.language_model.layers.12.linear_attn.in_proj_a",
167
+ "model.language_model.layers.13.linear_attn.in_proj_b",
168
+ "model.language_model.layers.13.linear_attn.in_proj_a",
169
+ "model.language_model.layers.14.linear_attn.in_proj_b",
170
+ "model.language_model.layers.14.linear_attn.in_proj_a",
171
+ "model.language_model.layers.16.linear_attn.in_proj_b",
172
+ "model.language_model.layers.16.linear_attn.in_proj_a",
173
+ "model.language_model.layers.17.linear_attn.in_proj_b",
174
+ "model.language_model.layers.17.linear_attn.in_proj_a",
175
+ "model.language_model.layers.18.linear_attn.in_proj_b",
176
+ "model.language_model.layers.18.linear_attn.in_proj_a",
177
+ "model.language_model.layers.20.linear_attn.in_proj_b",
178
+ "model.language_model.layers.20.linear_attn.in_proj_a",
179
+ "model.language_model.layers.21.linear_attn.in_proj_b",
180
+ "model.language_model.layers.21.linear_attn.in_proj_a",
181
+ "model.language_model.layers.22.linear_attn.in_proj_b",
182
+ "model.language_model.layers.22.linear_attn.in_proj_a",
183
+ "model.language_model.layers.24.linear_attn.in_proj_b",
184
+ "model.language_model.layers.24.linear_attn.in_proj_a",
185
+ "model.language_model.layers.25.linear_attn.in_proj_b",
186
+ "model.language_model.layers.25.linear_attn.in_proj_a",
187
+ "model.language_model.layers.26.linear_attn.in_proj_b",
188
+ "model.language_model.layers.26.linear_attn.in_proj_a",
189
+ "model.language_model.layers.28.linear_attn.in_proj_b",
190
+ "model.language_model.layers.28.linear_attn.in_proj_a",
191
+ "model.language_model.layers.29.linear_attn.in_proj_b",
192
+ "model.language_model.layers.29.linear_attn.in_proj_a",
193
+ "model.language_model.layers.30.linear_attn.in_proj_b",
194
+ "model.language_model.layers.30.linear_attn.in_proj_a",
195
+ "lm_head"
196
+ ],
197
+ "quantized_params": 6912212992,
198
+ "total_params_hf_loaded": 9412959856,
199
+ "fraction_params_quantized": 0.7343,
200
+ "quant_cfg": [
201
+ {
202
+ "quantizer_name": "*",
203
+ "enable": false
204
+ },
205
+ {
206
+ "quantizer_name": "*weight_quantizer",
207
+ "cfg": {
208
+ "num_bits": [
209
+ 2,
210
+ 1
211
+ ],
212
+ "effective_bits": 4.5,
213
+ "block_sizes": {
214
+ "-1": 16,
215
+ "type": "dynamic",
216
+ "scale_bits": [
217
+ 4,
218
+ 3
219
+ ]
220
+ }
221
+ }
222
+ },
223
+ {
224
+ "quantizer_name": "*input_quantizer",
225
+ "cfg": {
226
+ "num_bits": [
227
+ 2,
228
+ 1
229
+ ],
230
+ "effective_bits": 4.5,
231
+ "block_sizes": {
232
+ "-1": 16,
233
+ "type": "dynamic",
234
+ "scale_bits": [
235
+ 4,
236
+ 3
237
+ ]
238
+ }
239
+ }
240
+ },
241
+ {
242
+ "quantizer_name": "*gdn_state_quantizer",
243
+ "enable": false
244
+ },
245
+ {
246
+ "quantizer_name": "*gdn_w_quantizer",
247
+ "enable": false
248
+ },
249
+ {
250
+ "quantizer_name": "*block_sparse_moe.gate*",
251
+ "enable": false
252
+ },
253
+ {
254
+ "quantizer_name": "*linear_attn.conv1d*",
255
+ "enable": false
256
+ },
257
+ {
258
+ "quantizer_name": "*self_attention.conv1d*",
259
+ "enable": false
260
+ },
261
+ {
262
+ "quantizer_name": "*linear_attn.in_proj_a*",
263
+ "enable": false
264
+ },
265
+ {
266
+ "quantizer_name": "*linear_attn.in_proj_b*",
267
+ "enable": false
268
+ },
269
+ {
270
+ "quantizer_name": "*lm_head*",
271
+ "enable": false
272
+ },
273
+ {
274
+ "quantizer_name": "*mixer.conv1d*",
275
+ "enable": false
276
+ },
277
+ {
278
+ "quantizer_name": "*mlp.gate.*",
279
+ "enable": false
280
+ },
281
+ {
282
+ "quantizer_name": "*mlp.shared_expert_gate.*",
283
+ "enable": false
284
+ },
285
+ {
286
+ "quantizer_name": "*output_layer*",
287
+ "enable": false
288
+ },
289
+ {
290
+ "quantizer_name": "*proj_out.*",
291
+ "enable": false
292
+ },
293
+ {
294
+ "quantizer_name": "*router*",
295
+ "enable": false
296
+ },
297
+ {
298
+ "quantizer_name": "mtp.*",
299
+ "enable": false
300
+ },
301
+ {
302
+ "quantizer_name": "output.*",
303
+ "enable": false
304
+ },
305
+ {
306
+ "quantizer_name": "*embed_vision*",
307
+ "enable": false
308
+ },
309
+ {
310
+ "quantizer_name": "*vision_tower*",
311
+ "enable": false
312
+ },
313
+ {
314
+ "quantizer_name": "*visual*",
315
+ "enable": false
316
+ },
317
+ {
318
+ "quantizer_name": "*vision_model*",
319
+ "enable": false
320
+ },
321
+ {
322
+ "quantizer_name": "*multi_modal_projector*",
323
+ "enable": false
324
+ },
325
+ {
326
+ "quantizer_name": "*",
327
+ "parent_class": "nn.BatchNorm1d",
328
+ "enable": false
329
+ },
330
+ {
331
+ "quantizer_name": "*",
332
+ "parent_class": "nn.BatchNorm2d",
333
+ "enable": false
334
+ },
335
+ {
336
+ "quantizer_name": "*",
337
+ "parent_class": "nn.BatchNorm3d",
338
+ "enable": false
339
+ },
340
+ {
341
+ "quantizer_name": "*",
342
+ "parent_class": "nn.LeakyReLU",
343
+ "enable": false
344
+ },
345
+ {
346
+ "quantizer_name": "*",
347
+ "parent_class": "nn.Embedding",
348
+ "enable": false
349
+ },
350
+ {
351
+ "quantizer_name": "*lm_head*",
352
+ "enable": false
353
+ },
354
+ {
355
+ "quantizer_name": "*visual*",
356
+ "enable": false
357
+ },
358
+ {
359
+ "quantizer_name": "*vision*",
360
+ "enable": false
361
+ },
362
+ {
363
+ "quantizer_name": "*mtp*",
364
+ "enable": false
365
+ },
366
+ {
367
+ "quantizer_name": "*in_proj_a*",
368
+ "enable": false
369
+ },
370
+ {
371
+ "quantizer_name": "*in_proj_b*",
372
+ "enable": false
373
+ },
374
+ {
375
+ "quantizer_name": "*mhc_*",
376
+ "enable": false
377
+ },
378
+ {
379
+ "quantizer_name": "*embed_tokens*",
380
+ "enable": false
381
+ },
382
+ {
383
+ "quantizer_name": "*conv1d*",
384
+ "enable": false
385
+ }
386
+ ],
387
+ "fakequant_logits_finite": true,
388
+ "export_seconds": 140.7
389
+ }
quantization/requirements-quant.lock ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ accelerate==1.15.0
2
+ aiohappyeyeballs==2.7.1
3
+ aiohttp==3.14.5
4
+ aiosignal==1.4.0
5
+ annotated-doc==0.0.5
6
+ annotated-types==0.8.0
7
+ anyio==4.15.1
8
+ attrs==26.1.0
9
+ certifi==2026.10.23
10
+ charset-normalizer==3.5.2
11
+ click==8.5.0
12
+ cloudpickle==3.1.2
13
+ cuda-bindings==13.4.4
14
+ cuda-pathfinder==1.8.4
15
+ cuda-toolkit==13.0.2
16
+ datasets==5.1.0
17
+ deepspeed==0.19.9
18
+ defusedxml==0.7.1
19
+ diffusers==0.42.1
20
+ dill==0.4.1
21
+ einops==0.8.2
22
+ filelock==4.2.4
23
+ frozenlist==1.8.0
24
+ fsspec==2026.7.0
25
+ h11==0.16.0
26
+ hf-xet==1.8.0
27
+ hjson==3.1.0
28
+ httpcore==1.0.9
29
+ httpcore2==2.16.0
30
+ httpx==0.28.1
31
+ httpx2==2.16.0
32
+ huggingface-hub==2.4.0
33
+ idna==3.20
34
+ importlib-metadata==10.1.0
35
+ jinja2==3.1.6
36
+ joblib==1.6.0
37
+ markdown-it-py==4.2.0
38
+ markupsafe==3.0.3
39
+ mdurl==0.1.2
40
+ mpmath==1.3.0
41
+ msgpack==1.2.4
42
+ multidict==7.2.1
43
+ multiprocess==0.70.19
44
+ networkx==3.7
45
+ ninja==1.13.2
46
+ nltk==3.10.4
47
+ numpy==2.5.4
48
+ nvidia-cublas==13.1.0.3
49
+ nvidia-cuda-cccl==13.3.4.3.1
50
+ nvidia-cuda-cupti==13.0.85
51
+ nvidia-cuda-nvrtc==13.0.88
52
+ nvidia-cuda-runtime==13.0.96
53
+ nvidia-cudnn-cu13==9.19.0.56
54
+ nvidia-cufft==12.0.0.61
55
+ nvidia-cufile==1.15.1.6
56
+ nvidia-curand==10.4.0.35
57
+ nvidia-cusolver==12.0.4.66
58
+ nvidia-cusparse==12.6.3.3
59
+ nvidia-cusparselt-cu13==0.8.0
60
+ nvidia-ml-py==13.615.71
61
+ nvidia-modelopt==0.48.0rc1
62
+ nvidia-nccl-cu13==2.28.9
63
+ nvidia-nvjitlink==13.0.88
64
+ nvidia-nvshmem-cu13==3.4.5
65
+ nvidia-nvtx==13.0.85
66
+ omegaconf==2.4.0
67
+ packaging==26.3
68
+ pandas==3.1.0
69
+ peft==0.21.2
70
+ pillow==13.0.0
71
+ propcache==0.5.4
72
+ psutil==7.2.2
73
+ pulp==3.3.2
74
+ py-cpuinfo==9.0.0
75
+ pyarrow==26.0.0
76
+ pydantic==2.14.2
77
+ pydantic-core==2.50.2
78
+ pygments==2.21.0
79
+ python-dateutil==3.0.0
80
+ pyyaml==6.0.3
81
+ regex==2026.9.29
82
+ requests==2.34.2
83
+ rich==15.0.0
84
+ safetensors==0.8.0
85
+ scipy==1.18.1
86
+ sentencepiece==0.2.2
87
+ setuptools==81.0.0
88
+ shellingham==1.5.4
89
+ six==1.17.0
90
+ sympy==1.14.0
91
+ tiktoken==0.14.0
92
+ tokenizers==0.23.3
93
+ torch==2.11.0+cu130
94
+ torchvision==0.26.0+cu130
95
+ tqdm==4.70.1
96
+ transformers==5.18.0
97
+ triton==3.6.0
98
+ truststore==0.10.5
99
+ typer==0.27.3
100
+ typing-extensions==4.16.0
101
+ typing-inspection==0.4.4
102
+ tzdata==2026.6
103
+ urllib3==2.8.0
104
+ wonderwords==3.0.1
105
+ xxhash==4.0.2
106
+ yarl==1.25.1
107
+ zipp==4.1.2