FermionResearch commited on
Commit
2628a82
·
verified ·
1 Parent(s): ee4aab7

Neutrino-8B publish tree (cons3-B25 ship brain 016c6f36)

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. LICENSE +202 -0
  2. MANIFEST.json +335 -0
  3. NOTICE +43 -0
  4. README.md +558 -0
  5. bin/MANIFEST.json +25 -0
  6. bin/README.md +140 -0
  7. bin/fermion-run-linux-x64 +3 -0
  8. bin/fermion-run-linux-x64.sha256 +1 -0
  9. bin/fermion-run-macos-arm64 +3 -0
  10. bin/fermion-run-macos-arm64.sha256 +1 -0
  11. config.json +24 -0
  12. generation_config.json +10 -0
  13. gguf/Modelfile +5 -0
  14. gguf/README.md +223 -0
  15. gguf/SHA256SUMS +23 -0
  16. gguf/receipts/bench_8b.json +112 -0
  17. gguf/receipts/convert_8b.json +44 -0
  18. gguf/receipts/gate_8b_fv5.json +105 -0
  19. gguf/receipts/gateverdict_8b_fv5.json +17 -0
  20. gguf/receipts/gguf_spec_quickstart_k16.json +141 -0
  21. gguf/receipts/gguf_spec_quickstart_k4.json +141 -0
  22. gguf/receipts/ref_8b.json +26 -0
  23. gguf/receipts_ifcs50_retired/bench_8b.json +112 -0
  24. gguf/receipts_ifcs50_retired/convert_8b.json +44 -0
  25. gguf/receipts_ifcs50_retired/gateverdict_8b_fv5.json +17 -0
  26. gguf/receipts_ifcs50_retired/ref_8b.json +26 -0
  27. merges.txt +0 -0
  28. mlx/MANIFEST.json +143 -0
  29. mlx/README.md +133 -0
  30. mlx/fermion_mlx/__init__.py +129 -0
  31. mlx/fermion_mlx/__main__.py +177 -0
  32. mlx/fermion_mlx/arch3.py +183 -0
  33. mlx/fermion_mlx/container.py +143 -0
  34. mlx/fermion_mlx/gates.py +164 -0
  35. mlx/fermion_mlx/mbatch.py +164 -0
  36. mlx/fermion_mlx/model.py +281 -0
  37. mlx/fermion_mlx/spec.py +256 -0
  38. mlx/fermion_mlx/trit_kernels.py +877 -0
  39. mlx/merges.txt +0 -0
  40. mlx/receipts/bench_8b_wbase_m5_16gb_memcapped.json +39 -0
  41. mlx/receipts/pathgate_8b_cons3b25.json +75 -0
  42. mlx/receipts/pathgate_8b_d25.json +75 -0
  43. mlx/receipts/pathgate_8b_product.json +75 -0
  44. mlx/receipts/pathgate_8b_wbase.json +75 -0
  45. mlx/receipts/refgate_8b_cons3b25.json +133 -0
  46. mlx/receipts/refgate_8b_d25.json +133 -0
  47. mlx/receipts/refgate_8b_product.json +133 -0
  48. mlx/receipts/refgate_8b_wbase.json +133 -0
  49. mlx/receipts/specbench_8b_0p6b_m5.json +57 -0
  50. mlx/receipts/specgate_8b_0p6b.json +47 -0
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 2026 Fermion Research
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.
202
+
MANIFEST.json ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "note": "provenance: HF-tree filename -> source + sha256; weights entry is declarative until materialized | upload lane 2026-07-27: __pycache__/*.pyc excluded from the published tree (not-for-shipping)",
3
+ "weights_container": {
4
+ "file": "neutrino-8b_v4.bin",
5
+ "status": "FINAL \u2014 cons3-B25 SHIP BRAIN (consolidation-3 arm B step 25, FOUNDER-AUTHORISED PROMOTE 2026-07-27 ~19:50 IST: GSM8K stated 49.33 / flexible 51.00 / BFCL 65.31 / IFEval-ps 73.17 / MMLU-Redux 67.84 / full-57 72.084 / C4 21.475 / term 0.5067. The Redux no-regression guard (69.5) was breached at 67.84 and the founder amended the bar on the record \u2014 see handoffs/RESEARCH_LEDGER.md. Supersedes ifc-s50 39787a2e\u2026a3be)",
6
+ "bytes": 3875404812,
7
+ "sha256": "016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0",
8
+ "source": "internal lab archive: archive/consolidation_3/export_v4_B25/neutrino-8b_v4.bin (public copy: huggingface.co/fermionresearch/Neutrino-8B)",
9
+ "qat_state_sha256": "09c3f36a72e73223f79e4d7c6fd82295ecc09eb7d81f027afbd239f26f9441b8",
10
+ "coded_transport": {
11
+ "file": "neutrino-8b_v4.tv4z",
12
+ "bytes": 2559836297,
13
+ "sha256": "83ec7a52bd6733e66fb546e89b9c6aa8b3411af7f648e9c0b2bb56ba50aac4df",
14
+ "note": "lossless tv4z transport, 66.05% of raw; decode round-trip md5/sha/cmp PASS"
15
+ },
16
+ "retired_do_not_verify_against": {
17
+ "ifc-s50 container": "39787a2e15dcffa20135c7c1bb1b8ed55c8632263f8457f3395f55d52007a3be",
18
+ "ifc-s50 tv4z": "7d36adaf18c5d779aa96d09a6a6eb080eec39bc8b7dcc8a5eb6680f8fc134c7a",
19
+ "ifc-s50 qat_state": "e62a1199f83285f60352ede2ec9255dbcbc468976e5c8b9595536717260fb6a9",
20
+ "ifc-s50 gguf": "7dda995047dc70540d2d83f2c464dd118fff62eef1aab36ae71ceb3cc972e5d0"
21
+ }
22
+ },
23
+ "files": {
24
+ ".gitattributes": {
25
+ "bytes": 1905,
26
+ "sha256": "501edc5292358aaa11dd13cd5e9b25c43f94df3cc9fff43dd3c0df50efcc40c0",
27
+ "source": "banked lane artifact (LFS globs made explicit per-file 2026-07-27 upload lane)"
28
+ },
29
+ "LICENSE": {
30
+ "bytes": 11347,
31
+ "sha256": "2f978a54ef89f3e4ee2d413c347e4b03500c95f29342277a287acc2ae27ae280",
32
+ "source": "Apache-2.0 text (same file across all release trees)"
33
+ },
34
+ "NOTICE": {
35
+ "bytes": 1557,
36
+ "sha256": "a17bda9d5b1e7d90927a39a2b7c2e40a5cb8afe36ecb9f8c776802cec75b04ca",
37
+ "source": "banked lane artifact"
38
+ },
39
+ "bin/README.md": {
40
+ "bytes": 7211,
41
+ "sha256": "172b910ceb503f28b8bd1977e805d3cd04024192625b31ef1899a30b8e8d6c88",
42
+ "source": "bin/ staging doc: which fermion-run binaries land at upload + sidecar convention"
43
+ },
44
+ "bin/fermion-run-linux-x64": {
45
+ "bytes": 1249584,
46
+ "sha256": "499ecdc569eae2ceaeb4a34cd1f11a91c2c09dfb17aa5847532359c6dc57d93a",
47
+ "source": "BLESSED release binary (Linux x86-64 AVX2 baseline, fully static) \u2014 release-binaries lane 2026-07-25, receipt handoffs/results_release_binaries/receipt_linux_x64.json"
48
+ },
49
+ "bin/fermion-run-linux-x64.sha256": {
50
+ "bytes": 88,
51
+ "sha256": "2350e759fe77faa43803b43ab72f5c3bf2ca12263dddc680af6e453381f2649c",
52
+ "source": "sha256 sidecar (shasum line) for fermion-run-linux-x64"
53
+ },
54
+ "bin/fermion-run-macos-arm64": {
55
+ "bytes": 621480,
56
+ "sha256": "d7b257b5cd4b08cadd04fd867158ac50619aab23164294794dc10f05f1ede53c",
57
+ "source": "BLESSED release binary (macOS arm64 CPU/NEON, static libomp) \u2014 release-binaries lane 2026-07-25, receipt handoffs/results_release_binaries/receipt_macos_arm64.json"
58
+ },
59
+ "bin/fermion-run-macos-arm64.sha256": {
60
+ "bytes": 90,
61
+ "sha256": "d706f6935e715a96df3736035fb22ffa7854a8cb337b575542cd03ecb281ef72",
62
+ "source": "sha256 sidecar (shasum line) for fermion-run-macos-arm64"
63
+ },
64
+ "config.json": {
65
+ "bytes": 505,
66
+ "sha256": "4779738d791ad490794f6b0a7e04c1cb50f2720fbe8b6503c61da547c0861c0a",
67
+ "source": "written for the trtc_v4 loader (hf_ternary registration)"
68
+ },
69
+ "generation_config.json": {
70
+ "bytes": 181,
71
+ "sha256": "2537909d4312b7155b60fe0aee96d342de1a00b7841ed3e7eba4c3835d16d8ae",
72
+ "source": "transformers generation defaults for this SKU"
73
+ },
74
+ "gguf/Modelfile": {
75
+ "bytes": 214,
76
+ "sha256": "47c4c5ac0228e25197eb52e1ae0a964f95ac0d562d9effe9547f01c0cb58a9d1",
77
+ "source": "banked lane artifact"
78
+ },
79
+ "gguf/README.md": {
80
+ "bytes": 10315,
81
+ "sha256": "0124d20fe1e1af892cd809bbcc0a9d390d687684c0747bc6ca8a0b9e12280ce2",
82
+ "source": "banked lane artifact"
83
+ },
84
+ "gguf/SHA256SUMS": {
85
+ "bytes": 1912,
86
+ "sha256": "457e700fc5db35c5618a7c61dc0e4ac601d94f12ed264374b0717c574275bfc4",
87
+ "source": "banked lane artifact"
88
+ },
89
+ "gguf/receipts/bench_8b.json": {
90
+ "bytes": 2663,
91
+ "sha256": "1976449e3aa06f21e8337d0035ce8ee8371d6ff94d84adbafd149dbfa3f9d6fb",
92
+ "source": "banked lane artifact"
93
+ },
94
+ "gguf/receipts/convert_8b.json": {
95
+ "bytes": 953,
96
+ "sha256": "09060b46412b45b856edd239c37b49208f7cebb153620dda1808296ace8308ea",
97
+ "source": "banked lane artifact"
98
+ },
99
+ "gguf/receipts/gate_8b_fv5.json": {
100
+ "bytes": 6989,
101
+ "sha256": "393e6d48d74c27bca09ce82af855a1d5917347595477c4587774d0391b305e34",
102
+ "source": "banked lane artifact"
103
+ },
104
+ "gguf/receipts/gateverdict_8b_fv5.json": {
105
+ "bytes": 457,
106
+ "sha256": "cd79e09be62309d4146da1530211529a9b98cffed0e240c7ef30c7466f8a64c1",
107
+ "source": "banked lane artifact"
108
+ },
109
+ "gguf/receipts/gguf_spec_quickstart_k16.json": {
110
+ "bytes": 9857,
111
+ "sha256": "50079ec10a6b1b1eaf131a2955a2d409d5c1b35926aa12a94e7789ff9bd11962",
112
+ "source": "banked lane artifact"
113
+ },
114
+ "gguf/receipts/gguf_spec_quickstart_k4.json": {
115
+ "bytes": 9830,
116
+ "sha256": "a2ef2374d6affde8ec47560ff146d44da8170c939d55c82d32fea653ae5e3da2",
117
+ "source": "banked lane artifact"
118
+ },
119
+ "gguf/receipts/ref_8b.json": {
120
+ "bytes": 470,
121
+ "sha256": "8916c4936da50ca500f521a19ce4d69668e8fd5603588113359efba5a059851a",
122
+ "source": "banked lane artifact"
123
+ },
124
+ "gguf/receipts_ifcs50_retired/bench_8b.json": {
125
+ "bytes": 2656,
126
+ "sha256": "348dccfc95f896beaacaf7297df7574b364e4d6a431bc54a34f4737fb39acdd8",
127
+ "source": "banked lane artifact"
128
+ },
129
+ "gguf/receipts_ifcs50_retired/convert_8b.json": {
130
+ "bytes": 947,
131
+ "sha256": "111e69bfcf5cea559dfcd500c58b07db366cd1c26cd2c27be49dcf1d645f940a",
132
+ "source": "banked lane artifact"
133
+ },
134
+ "gguf/receipts_ifcs50_retired/gateverdict_8b_fv5.json": {
135
+ "bytes": 457,
136
+ "sha256": "2f45fb6b747cb2ac8e3910bc4e929caad78660485e329eb1828095cb2dff5fa4",
137
+ "source": "banked lane artifact"
138
+ },
139
+ "gguf/receipts_ifcs50_retired/ref_8b.json": {
140
+ "bytes": 470,
141
+ "sha256": "0eb0e2485f3b5aa3c303e33200112cde1fd5441642e440ee1d7e5e5f4821966e",
142
+ "source": "banked lane artifact"
143
+ },
144
+ "merges.txt": {
145
+ "bytes": 1671853,
146
+ "sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
147
+ "source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
148
+ },
149
+ "mlx/MANIFEST.json": {
150
+ "bytes": 6950,
151
+ "sha256": "f87359d53dfd9f433a86495bd87759c1faeaf09b12cf07f9a06d4ec00e7619f5",
152
+ "source": "banked lane artifact"
153
+ },
154
+ "mlx/README.md": {
155
+ "bytes": 6201,
156
+ "sha256": "4d3a3dbc7ab1d32df93a72a1ca1e90b98af9c99c6a3ecd0d1af468d37fa7a278",
157
+ "source": "banked lane artifact"
158
+ },
159
+ "mlx/fermion_mlx/__init__.py": {
160
+ "bytes": 5004,
161
+ "sha256": "9d96d2fe71a8d1af20d78dc2d1cf016d01175462712aed36d493d82d3ffca87e",
162
+ "source": "banked lane artifact"
163
+ },
164
+ "mlx/fermion_mlx/__main__.py": {
165
+ "bytes": 8066,
166
+ "sha256": "aac000379d0c5cab04f5d6bbcb080e20c987cd87c60e8e0d3ad0a87e389937fc",
167
+ "source": "banked lane artifact"
168
+ },
169
+ "mlx/fermion_mlx/arch3.py": {
170
+ "bytes": 7541,
171
+ "sha256": "a831e8a24b2b9a986b393d1c316aac66a3399a082a57176254b2feeae032f8cd",
172
+ "source": "banked lane artifact"
173
+ },
174
+ "mlx/fermion_mlx/container.py": {
175
+ "bytes": 6059,
176
+ "sha256": "c5b352184ba9ddf9a3e424731226cb7dcb9901d3a1516c67d4557953b0101f2f",
177
+ "source": "banked lane artifact"
178
+ },
179
+ "mlx/fermion_mlx/gates.py": {
180
+ "bytes": 7454,
181
+ "sha256": "faf8634b0bd3afec5c4422decdbd8cc04fbb7f3426f70cdd6626fd6b177aa8b4",
182
+ "source": "banked lane artifact"
183
+ },
184
+ "mlx/fermion_mlx/mbatch.py": {
185
+ "bytes": 6492,
186
+ "sha256": "c9ec8d37f1cdc0554cda01cefbc435e67525779c79e0dac7fd760e7919e6fdd2",
187
+ "source": "banked lane artifact"
188
+ },
189
+ "mlx/fermion_mlx/model.py": {
190
+ "bytes": 12748,
191
+ "sha256": "9488f8194ce8be006ce05dfaf6118353de44005ce54f38f73a0908ccac2749da",
192
+ "source": "banked lane artifact"
193
+ },
194
+ "mlx/fermion_mlx/spec.py": {
195
+ "bytes": 11207,
196
+ "sha256": "969e946d5d2eeb63d44afa4c62dd2453d87fa14e83b936bc673482b59ee3b745",
197
+ "source": "banked lane artifact"
198
+ },
199
+ "mlx/fermion_mlx/trit_kernels.py": {
200
+ "bytes": 36881,
201
+ "sha256": "c56db6dd5e3743d19c54411f5f0b2dfef135f7805906a62cc4e40f1ee5e80f27",
202
+ "source": "banked lane artifact"
203
+ },
204
+ "mlx/merges.txt": {
205
+ "bytes": 1671853,
206
+ "sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
207
+ "source": "banked lane artifact"
208
+ },
209
+ "mlx/receipts/bench_8b_wbase_m5_16gb_memcapped.json": {
210
+ "bytes": 599,
211
+ "sha256": "9d3b153ec49dce92846fb9140fc38267ed04a08b5adcda91f2036ce94869b555",
212
+ "source": "banked lane artifact"
213
+ },
214
+ "mlx/receipts/pathgate_8b_cons3b25.json": {
215
+ "bytes": 634,
216
+ "sha256": "ff7f68d926d381a6c1a00f07cbb33ad5b3ac118d7b9dd997de0857cc1af40f71",
217
+ "source": "banked lane artifact"
218
+ },
219
+ "mlx/receipts/pathgate_8b_d25.json": {
220
+ "bytes": 631,
221
+ "sha256": "0de2306ce8c15c80fb776b2d73e6d056302d1d7367440e2c567d4370aaeef81c",
222
+ "source": "banked lane artifact"
223
+ },
224
+ "mlx/receipts/pathgate_8b_product.json": {
225
+ "bytes": 633,
226
+ "sha256": "c13e35c461c6e3a23fb2976d0f26f0b8dce114b12bf976a05cb19f11cc0746ea",
227
+ "source": "banked lane artifact"
228
+ },
229
+ "mlx/receipts/pathgate_8b_wbase.json": {
230
+ "bytes": 612,
231
+ "sha256": "50d6584a9ba2dae87bf502f2e637916a604be72db473fc89977fb8e1aaf3413b",
232
+ "source": "banked lane artifact"
233
+ },
234
+ "mlx/receipts/refgate_8b_cons3b25.json": {
235
+ "bytes": 1916,
236
+ "sha256": "d5a072dd5f796e8fa87269b0a44b3f525de627bb9ee79adeb7fcf973efcb00d7",
237
+ "source": "banked lane artifact (local absolute model path redacted to basename 2026-07-27 upload lane)"
238
+ },
239
+ "mlx/receipts/refgate_8b_d25.json": {
240
+ "bytes": 1918,
241
+ "sha256": "ece57b474473c07bb4064279b73a6f4aa199deb055b39e8166c86cd41a2a71b2",
242
+ "source": "banked lane artifact"
243
+ },
244
+ "mlx/receipts/refgate_8b_product.json": {
245
+ "bytes": 1919,
246
+ "sha256": "5b194b6fb22534c2c9f61cc48be7d5e1ace92b77cd3edb136ed88f94fd0bf2a3",
247
+ "source": "banked lane artifact"
248
+ },
249
+ "mlx/receipts/refgate_8b_wbase.json": {
250
+ "bytes": 1909,
251
+ "sha256": "851da3bb5c093182eb56532711dd9a95123ca44260e53de951b27c8b96eaf7a4",
252
+ "source": "banked lane artifact"
253
+ },
254
+ "mlx/receipts/specbench_8b_0p6b_m5.json": {
255
+ "bytes": 826,
256
+ "sha256": "c36ef09659d8c7690d53c23d065943103a95417890f8defbd01e629d7b5feeca",
257
+ "source": "banked lane artifact"
258
+ },
259
+ "mlx/receipts/specgate_8b_0p6b.json": {
260
+ "bytes": 705,
261
+ "sha256": "64bcb90fe965835bc78887d873d3cfd0677b76c6d72cda04a84e295efee7c10c",
262
+ "source": "banked lane artifact"
263
+ },
264
+ "mlx/requirements.txt": {
265
+ "bytes": 191,
266
+ "sha256": "1cf4d1fa581aaa5deae0568f88388c51166bbd69e7a89080b5a53488a7ef39b6",
267
+ "source": "banked lane artifact"
268
+ },
269
+ "mlx/tokenizer.json": {
270
+ "bytes": 11422654,
271
+ "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
272
+ "source": "banked lane artifact"
273
+ },
274
+ "mlx/tokenizer_config.json": {
275
+ "bytes": 9732,
276
+ "sha256": "d5d09f07b48c3086c508b30d1c9114bd1189145b74e982a265350c923acd8101",
277
+ "source": "banked lane artifact"
278
+ },
279
+ "mlx/vocab.json": {
280
+ "bytes": 2776833,
281
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
282
+ "source": "banked lane artifact"
283
+ },
284
+ "receipts/acceptance_receipt_d25.json": {
285
+ "bytes": 9820,
286
+ "sha256": "037e3c009d6287ecd0f3dfcd38065236b832e63d7c0d64f5124fbde46a1816c3",
287
+ "source": "banked lane artifact"
288
+ },
289
+ "receipts/acceptance_receipt_s50.json": {
290
+ "bytes": 9772,
291
+ "sha256": "b46a60823b86f8f1f1f0d119492ae19b6a2ac667c5a98733721a6118eeed4da5",
292
+ "source": "banked lane artifact"
293
+ },
294
+ "receipts/dspec_cert_cons3_b25.json": {
295
+ "bytes": 6061,
296
+ "sha256": "086607c20ada9bc1ec251e217329e6ec5146f3826d6f4feab5e918e1b346240f",
297
+ "source": "banked lane artifact"
298
+ },
299
+ "receipts/dspec_cert_ifc_s50.json": {
300
+ "bytes": 700,
301
+ "sha256": "d5f8b115759e4abac8e9856a6c771ddb68c5eeadf9fc0af8f69062a349032bf8",
302
+ "source": "banked lane artifact"
303
+ },
304
+ "receipts/dspec_cert_ifc_s50.json.log": {
305
+ "bytes": 2130,
306
+ "sha256": "91fab18237b4b6fa465f9f90cb4a83c97918cc92d0def8f08b50fff45d7d475e",
307
+ "source": "banked lane artifact"
308
+ },
309
+ "receipts/export_receipts_d25.json": {
310
+ "bytes": 5890,
311
+ "sha256": "0a77b6c649bcbda96a52730f7daee2d8e47ee3b8c3c0485ee9b2db9d6cc0faf0",
312
+ "source": "banked lane artifact"
313
+ },
314
+ "receipts/export_receipts_s50.json": {
315
+ "bytes": 5959,
316
+ "sha256": "909f92d30e37f99ea78e2990904749213e1c4bbeaa01a0250f79d5a2b8f9b201",
317
+ "source": "banked lane artifact"
318
+ },
319
+ "tokenizer.json": {
320
+ "bytes": 11422654,
321
+ "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
322
+ "source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
323
+ },
324
+ "tokenizer_config.json": {
325
+ "bytes": 9732,
326
+ "sha256": "d5d09f07b48c3086c508b30d1c9114bd1189145b74e982a265350c923acd8101",
327
+ "source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
328
+ },
329
+ "vocab.json": {
330
+ "bytes": 2776833,
331
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
332
+ "source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
333
+ }
334
+ }
335
+ }
NOTICE ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Neutrino — Fermion Research
2
+ Copyright 2026 Fermion Research
3
+
4
+ This product includes software and model artifacts developed by
5
+ Fermion Research, licensed under the Apache License, Version 2.0
6
+ (see LICENSE).
7
+
8
+ --------------------------------------------------------------------
9
+ THIRD-PARTY NOTICES
10
+ --------------------------------------------------------------------
11
+
12
+ Qwen3 (Alibaba Cloud / Qwen Team)
13
+ Copyright (c) Alibaba Cloud
14
+ Licensed under the Apache License, Version 2.0
15
+ https://huggingface.co/Qwen/Qwen3-8B
16
+
17
+ This model is a derivative of Qwen/Qwen3-8B. In particular, the following
18
+ files in this repository are redistributed VERBATIM from the upstream
19
+ Qwen3 release and are covered by Alibaba Cloud's copyright and the
20
+ Apache License 2.0:
21
+
22
+ tokenizer.json
23
+ tokenizer_config.json
24
+ vocab.json
25
+ merges.txt
26
+
27
+ The model architecture is stock Qwen3 geometry. The ternary
28
+ quantization-aware training, the TRTC v4 container format, the
29
+ post-training, the runtimes and the evaluation receipts are Fermion
30
+ Research's own work.
31
+
32
+ You may obtain a copy of the Apache License 2.0 at:
33
+ http://www.apache.org/licenses/LICENSE-2.0
34
+
35
+ --------------------------------------------------------------------
36
+
37
+ llama.cpp (The ggml authors) — MIT License
38
+
39
+ The GGUF pack in gguf/ is produced for, and loaded by, Fermion
40
+ Research's fork of ggml-org/llama.cpp. That fork is distributed
41
+ separately under upstream's MIT license, NOT under this repository's
42
+ Apache-2.0 grant. Any patch or source in gguf/ that derives from
43
+ llama.cpp is MIT-licensed; see the fork's own LICENSE.
README.md ADDED
@@ -0,0 +1,558 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ base_model: Qwen/Qwen3-8B
4
+ pipeline_tag: text-generation
5
+ library_name: transformers
6
+ tags:
7
+ - ternary
8
+ - sub-2-bit
9
+ - quantization
10
+ - trtc
11
+ ---
12
+
13
+ > [!WARNING]
14
+ > **[MMLU-adapted, disclosed] This model was deliberately trained on the
15
+ > public MMLU test, validation, dev, and auxiliary-train splits** (all
16
+ > 115,700 public `cais/mmlu` rows) as part of its shipping recipe, and we
17
+ > say so up front. **Its MMLU-family scores (72.1-class) are not comparable
18
+ > to clean models and must not be quoted as benchmark results without this
19
+ > label.** Clean-lineage calibration anchors (internal checkpoints, not
20
+ > products): full-57 63.43 / 63.99, blind MMLU-CF 52.74 / 53.61. On the
21
+ > blind, contamination-free meter the adaptation bought approximately
22
+ > nothing (adapted brain W-7500: 54.32; sibling arm W-7000: 54.16; clean
23
+ > branch: 53.61) — which is the point, and the method + receipts are
24
+ > published in the eval-receipts repo.
25
+
26
+ # Neutrino-8B
27
+
28
+ An 8B chat model whose every transformer linear is stored **five-valued
29
+ (sub-2 bits per weight)** in a single 3.88 GB (3.61 GiB) container, and
30
+ which loads as a native `transformers` model in one `from_pretrained` call.
31
+
32
+ This is the ONE published 8B SKU, and it is the full release stack:
33
+ the MMLU-adapted brain (W step 7500, disclosed above) → the combined
34
+ behavior stage → the IF-1 instruction stack, snapshot-50 → the
35
+ verifiable-reward tool-calling stage, dose-25, locked 2026-07-26 after
36
+ passing all release guards plus the tool-calling primary and stretch
37
+ targets (VERDICT PASS, `results_bfcl_round2/r2_summary.json`). There is
38
+ no separate "clean" variant to download; the clean lineage exists only
39
+ as internal calibration anchors in the published tables. Cross-model
40
+ rows we publish are same-harness measurements, and every 72.1-class
41
+ knowledge cell carries the adaptation label wherever it appears.
42
+
43
+ - Container: `neutrino-8b_v4.bin` — TRTC v4, arch-3 (Qwen3 topology),
44
+ 36 layers, hidden 4096, vocab 151936.
45
+ **Product bake landed (cons3-B25, the ship brain)**: baked 2026-07-27
46
+ from the consolidation-3 arm-B dose-25 QAT state — the ifc-s50 brain
47
+ plus 25 steps of a three-axis consolidation diet (stated-format math KD,
48
+ live-checker instruction following, gold function-calling). It buys the
49
+ GSM8K stated-format column and trades MMLU-Redux; the Redux
50
+ no-regression guard was breached and deliberately amended by the
51
+ founder on the record (`results_consolidation_3/RESULTS.md`).
52
+ 3,875,404,812 bytes, sha256
53
+ `016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0`.
54
+ <!-- Retirement record (receipts culture; internal archive locations are
55
+ not published). Superseded: earlier product bake — baked 2026-07-25
56
+ (banked 23:31Z) from the IF-stack snap50 QAT state (Manan lock 2026-07-25
57
+ 23:51 IST), 3,875,404,812 bytes, sha256
58
+ fe85f93214376354f118a83235eeff6bc5d40e112474afacbd3e0310768b08d5, under
59
+ archive/if_stack_b800/export_v4/. Earlier still, same culture: interim
60
+ development container (stage-B step-800 bake, 3,875,404,812 bytes, sha256
61
+ be44129e579a1c39d713652a0a4f3dd046e61dab83524d023d2f238ef181b814), which
62
+ carried the pre-launch working slug at source. -->
63
+ - Inside: 252 packed trit linears + int8 embedding lanes (untied). Nothing
64
+ in the decode path is fp16/fp32 weights; scales are snapped to the
65
+ runtime grid at export.
66
+ - Base: [Qwen/Qwen3-8B](https://huggingface.co/Qwen/Qwen3-8B) (Apache-2.0),
67
+ ternary QAT + staged post-training by [Fermion Research](https://huggingface.co/fermionresearch).
68
+ Tokenizer: Qwen3-8B (shipped in this repo).
69
+
70
+ ## Architecture
71
+
72
+ Geometry as read from the shipped container's header (bit-checked during
73
+ the GGUF conversion, `gguf/receipts/convert_8b.json`; byte budget from the
74
+ provenance-gated container walk, `results_model_anatomy/ANATOMY.json`):
75
+
76
+ | field | value |
77
+ |---|---|
78
+ | Parameters | 8,190,735,360 (6,945,767,424 ternary projection + 1,244,659,712 int8 embedding + 308,224 fp32 norm) |
79
+ | Decoder layers | 36 |
80
+ | Hidden width | 4,096 |
81
+ | Feed-forward width | 12,288, gated (SwiGLU) |
82
+ | Attention | grouped-query 4:1 — 32 query heads, 8 KV heads, head_dim 128 |
83
+ | Rotary embedding | full head width (rotary_dims 128), theta 1,000,000 |
84
+ | Normalization | RMSNorm, eps 1e-6, plus per-head Q/K RMSNorm in attention |
85
+ | Context length | 40,960 tokens (`max_pos`) |
86
+ | Vocabulary | 151,936 |
87
+ | Embeddings | untied — separate int8 input-embedding and lm_head records |
88
+ | KV cache | 144 KiB/token fp16 (288 KiB fp32): 0.60 GB @ 4k, 4.83 GB @ 32k |
89
+
90
+ Byte budget of the 3,875,404,812-byte container, by tensor class:
91
+
92
+ | lane | bytes | share |
93
+ |---|---:|---:|
94
+ | ternary weight lane (q/k/v/o/gate/up/down × 36 layers = 252 linears) | 2,604,662,784 | 67.2% |
95
+ | token embeddings, int8 (two untied 622 MB records) | 1,244,659,712 | 32.1% |
96
+ | per-row metadata (dims, scales, row sums) | 24,849,360 | 0.6% |
97
+ | norm vectors, fp32 (145 tensors) | 1,232,896 | 0.03% |
98
+ | container header | 60 | — |
99
+
100
+ Every layer costs exactly 72,351,744 weight-lane bytes (21.7% attention /
101
+ 78.3% MLP). State occupancy across the 6.95B ternary weights: 62.63% zero /
102
+ 18.68% plus / 18.69% minus (per-layer arrays in ANATOMY.json).
103
+
104
+ ## Formats and artifacts
105
+
106
+ | artifact | bytes | sha256 |
107
+ |---|---:|---|
108
+ | `neutrino-8b_v4.bin` (TRTC v4 container, the file every runtime executes) | 3,875,404,812 | `016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0` |
109
+ | `neutrino-8b_v4.tv4z` (lossless coded transport, 66.05% of raw) | 2,559,836,297 | `83ec7a52bd6733e66fb546e89b9c6aa8b3411af7f648e9c0b2bb56ba50aac4df` |
110
+ | `gguf/neutrino-8b-fv5.gguf` (llama.cpp-fork pack, bit-checked vs the container) | 4,093,015,136 | `7dda995047dc70540d2d83f2c464dd118fff62eef1aab36ae71ceb3cc972e5d0` |
111
+
112
+ The tv4z transport decodes back to the container byte-exactly (md5+sha
113
+ round-trip identity, `results_tv4z_8b_ship/RESULTS.md`; 17 s encode / 6 s
114
+ decode on an M5). Three distribution surfaces read these artifacts:
115
+
116
+ 1. **pip engine** (`pip install fermion`) — the one-command door; pulls the
117
+ container and the platform-matching `bin/` runtime from this repo.
118
+ Measured: 24.94 tok/s CPU-only on an Apple M5, 9 threads.
119
+ 2. **GGUF pack + our llama.cpp fork** (`gguf/`) — ecosystem door for
120
+ llama.cpp / ollama / LM Studio, CPU + CUDA. Measured: 30.7 tok/s on an
121
+ NVIDIA L4 at full offload, 4.68 GiB VRAM @ 4k context (fits 8 GB cards).
122
+ 3. **MLX pack** (`mlx/`) — Python-native Apple-silicon runtime with custom
123
+ Metal kernels. Measured: 25.0 tok/s median on a 16 GB M5 under a 6 GiB
124
+ memory cap.
125
+
126
+ ## Quickstart
127
+
128
+ ```bash
129
+ pip install fermion
130
+ printf 'In one sentence, why is the sky blue?\n/exit\n' | \
131
+ fermion chat --model fermionresearch/Neutrino-8B --max-new 48
132
+ ```
133
+
134
+ Or pure `transformers`:
135
+
136
+ ```python
137
+ import fermion # registers the trtc_v4 model type
138
+ from transformers import AutoModelForCausalLM, AutoTokenizer
139
+
140
+ model = AutoModelForCausalLM.from_pretrained("fermionresearch/Neutrino-8B")
141
+ tokenizer = AutoTokenizer.from_pretrained("fermionresearch/Neutrino-8B")
142
+ inputs = tokenizer.apply_chat_template([{"role": "user", "content": "hi"}],
143
+ add_generation_prompt=True,
144
+ enable_thinking=False,
145
+ return_tensors="pt", return_dict=True)
146
+ out = model.generate(**inputs, max_new_tokens=256) # generation_config: greedy
147
+ n = inputs["input_ids"].shape[1]
148
+ print(tokenizer.decode(out[0][n:], skip_special_tokens=True))
149
+ ```
150
+
151
+ `import fermion` must come first: it is what registers the `trtc_v4` model
152
+ type, and without it `AutoTokenizer`/`AutoModelForCausalLM` cannot read this
153
+ repo's `config.json`. `return_dict=True` is not optional either on
154
+ transformers 4.56 and newer, where `apply_chat_template` returns a mapping
155
+ rather than a bare tensor.
156
+
157
+ `generation_config.json` in this repo is deliberately **greedy with no
158
+ repetition penalty and no top-k**: that is the setting the published MMLU,
159
+ GSM8K, IFEval and BFCL numbers were measured at, so a bare `generate()`, an
160
+ `lm-eval` run and `fermion generate` all reproduce each other. The
161
+ conversational and long-form settings are one argument away — see
162
+ [Recommended settings](#recommended-settings-by-use-case).
163
+
164
+ GGUF surface (build our fork once, then standard llama.cpp tooling —
165
+ full instructions in `gguf/README.md`):
166
+
167
+ ```bash
168
+ git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
169
+ git checkout d67c0b4107112e4790774c3a8169e2e3eb24643b && git am fv5.patch
170
+ cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build -j --target llama-cli
171
+ ./build/bin/llama-cli -m neutrino-8b-fv5.gguf -p "Why is the sky blue?" -n 256 --temp 0
172
+ ```
173
+
174
+ MLX surface (Apple silicon — full instructions in `mlx/README.md`):
175
+
176
+ ```bash
177
+ pip install -r mlx/requirements.txt
178
+ python -m fermion_mlx --model neutrino-8b_v4.bin --mode chat \
179
+ --tokenizer . --prompt "Why is the sky blue?"
180
+ ```
181
+
182
+ The pip path is the **reference torch path**: correctness-gated (bit-packed
183
+ weights at rest, 0 greedy mismatches vs the native runtime over 768 tokens;
184
+ receipt in the eval-receipts repo), honest about speed — it is NOT the fast
185
+ path. The fast path ships **in this repo** under `bin/` (see below).
186
+
187
+ Verified end-to-end in one fresh CPU container (Modal, cpu=8, 24 GiB, no
188
+ GPU): wheel install -> container sha gate -> piped `chat` produced a
189
+ real answer; receipt: `receipts/acceptance_cpu_receipt_20260724.json`
190
+ (run under the pre-rename working slug; same code path, wheel renamed
191
+ `fermion` and repo id renamed `Neutrino-8B` 2026-07-24 per the release
192
+ runbook). Peak resident memory for load+chat was under 8 GiB (bit-packed
193
+ weights). Acceptance rerun against the final product container is a
194
+ publish gate (RELEASE_RUNBOOK §3).
195
+
196
+ ## Recommended settings, by use case
197
+
198
+ Swept on the predecessor ifc-s50 container (`39787a2e…`); the sampling sweep was NOT re-run for `016c6f36…` across four prompt slices — chat,
199
+ factual, structured/tool-shaped, long-form — at four temperature x
200
+ repetition-penalty cells (`handoffs/results_recommended_configs`). `—` means
201
+ leave it alone; a number there would be one we did not measure.
202
+
203
+ | use case | temperature | top-p | top-k | repetition penalty | penalty window | max new tokens | stop |
204
+ |---|---|---|---|---|---|---|---|
205
+ | **conversation / assistant** | **0.01** on the C binary · **0** on torch | 1.0 | not exposed | 1.05 | 256 | **512** | EOS `151645` |
206
+ | **tool calling / structured output** | **0** (greedy) | — | — | **1.0 (off)** | — | 256 | EOS `151645` |
207
+ | **long-form prose** | **0.7** | 0.95 | not exposed | 1.05 | 256 | **1024** | EOS `151645` |
208
+ | **deterministic / scriptable / benchmarks** | **0** (greedy) | — | — | **1.0 (off)** | — | 256 | EOS `151645` |
209
+
210
+ - **Structured output is an 8B capability.** On tool-shaped prompts this
211
+ model emitted whole-reply, fence-free, parseable JSON **8/8 in every cell
212
+ tried, including at temperature 0.7** — the format is not sensitive to the
213
+ sampler here. Turn the repetition penalty **off** for it anyway: JSON
214
+ requires repeating `"`, `:` and `{`, and a penalty over that punctuation is
215
+ the one place it can actively break the format. The 0.6B-Chat scored 0/8 on
216
+ the same prompts at every setting; route schema-shaped work here.
217
+ - **Why 512 and 1024 token budgets.** These are measured, not taste. At a
218
+ 150-token cap the chat slice terminated 0/2 in every cell and every
219
+ long-form generation hit the cap. The 8B is verbose; give it room.
220
+ - **The repetition penalty is insurance, not a fix.** Loop rate was **0.000
221
+ in all four cells**, with and without it. It is in the conversation row to
222
+ match what the behaviour receipts were graded at, and it is off everywhere
223
+ reproducibility matters.
224
+ - **`top_p 0.95` on the long-form row is the one unmeasured number here** —
225
+ we swept top-p 1.0 only. It is the conventional pairing for temperature
226
+ 0.7, flagged as convention rather than measurement.
227
+ - **Top-k is not exposed on any runtime we ship**, and this repo's
228
+ `generation_config.json` sets none. Top-p is the only shortlist knob.
229
+ - **`--stop-id 151645` is mandatory on the C binary.** Without it the binary
230
+ runs in fixed-length "race mode", ignores EOS and emits exactly the token
231
+ count you asked for.
232
+ - **Speculative decoding and the exactness claim.** Token-identical output is
233
+ certified under **greedy** decoding (`--temperature 0`). With sampling,
234
+ drafted output is drawn from the same distribution as undrafted output —
235
+ the pip path routes through the transformers rejection-sampling correction
236
+ — but individual tokens will differ. The research CUDA engine and the MLX
237
+ `--spec` mode use argmax-match acceptance and are greedy-only.
238
+ - Exactly one repetition penalty ever applies on the torch path: the
239
+ `fermion` CLI pins `repetition_penalty=1.0` in its `generate()` call before
240
+ installing its own windowed processor, so HF's full-context processor is
241
+ never built alongside it. If you assemble your own call, do the same — two
242
+ penalties of 1.05 compose to a measured 1.1025.
243
+
244
+ ## `bin/` — prebuilt native runtimes (the fast path)
245
+
246
+ This repo bundles the prebuilt, closed-source `fermion-run` binaries next
247
+ to the weights (the GGUF-ecosystem pattern; HF permits arbitrary binaries):
248
+
249
+ | Binary | Platform | Backend |
250
+ |---|---|---|
251
+ | `bin/fermion-run-macos-arm64` | macOS arm64 (M1+) | CPU — NEON dotprod, zero dylib deps (Metal runtime is a week-one follow-up) |
252
+ | `bin/fermion-run-linux-x64` | Linux x86-64 | CPU — AVX2 baseline, fully static ELF |
253
+ | `bin/fermion-run-linux-cuda` | Linux x86-64 + NVIDIA | CUDA — **coming soon** (not yet staged; today's CUDA fallback is the pip torch path) |
254
+
255
+ Every binary ships with a `<name>.sha256` sidecar; verify before running:
256
+
257
+ ```bash
258
+ shasum -a 256 -c bin/fermion-run-macos-arm64.sha256
259
+ ```
260
+
261
+ `pip install fermion` remains the one door: the CLI downloads the model and
262
+ the platform-matching binary from THIS repo and shells out to it for the
263
+ fast path (the torch reference path stays as the fallback). Until the
264
+ auto-binary CLI lands, the documented one-command binary path is in
265
+ `bin/README.md`.
266
+
267
+ Shipped-binary speed, with venue: **24.94 tok/s** CPU-only on an M5
268
+ MacBook (the macOS arm64 binary, 9 threads, 32-token greedy, banked
269
+ 2026-07-25) and **25.0 tok/s median** via the MLX pack on a 16 GB M5
270
+ under a 6 GiB memory cap (`mlx/receipts/`). These are shipped-binary
271
+ numbers; our research-harness results are reported separately and never
272
+ conflated with them.
273
+
274
+ Binaries are closed-source and covered by the EULA line below; speed
275
+ receipts (venue+version+date on every number) live in the eval-receipts
276
+ repo. `bin/MANIFEST.json` in this tree is the staging manifest: it names
277
+ exactly which binaries land at upload time and their provenance.
278
+
279
+ ## Benchmarks
280
+
281
+ Everything here is **non-thinking**: all our numbers, and every
282
+ same-harness cell we publish for another model, are measured with
283
+ thinking disabled. Release battery: the shipped brain (IF-stack
284
+ snapshot-50 + verifiable-reward tool-calling dose-25, "d25"), graded
285
+ 2026-07-26 on our house harness; all five release guards (termination,
286
+ IFEval floor, Redux, knowledge-delta, C4 ppl) plus the tool-calling
287
+ primary and stretch targets passed before the container was baked
288
+ (VERDICT PASS, `results_bfcl_round2/r2_summary.json`). A receipt exists
289
+ for every cell (venue, version, date) in the eval-receipts repo.
290
+
291
+ Release battery of the ship brain, protocol on every row:
292
+
293
+ | Meter | Score | Protocol |
294
+ |---|---:|---|
295
+ | MMLU full-57 | 72.1 **[MMLU-adapted, disclosed — see banner]** | 5-shot likelihood, 14,042 items |
296
+ | MMLU-Redux | 67.84 | generative, thinking off |
297
+ | IFEval, prompt-strict | 73.17 | generative, thinking off |
298
+ | IFEval, instruction-strict | 80.22 | same run, per-instruction grading |
299
+ | IFEval, prompt-loose | 76.31 | same run, loose extraction |
300
+ | BFCL v3 | 65.31 | macro over 13 subsets, bfcl-eval 2025.10.27.1, thinking off |
301
+ | GSM8K, flexible-extract | 51.00 | 0-shot generative, greedy, 256-token cap |
302
+ | GSM8K, strict/stated format | 49.33 | same run, answer must appear in the stated format |
303
+
304
+ Same-harness reference points for the strongest same-byte-class open
305
+ model (Ternary-Bonsai-8B, measured by us on identical protocols):
306
+ MMLU 65.75 · MMLU-Redux 71.47 · IFEval prompt-strict 83.65 · BFCL v3
307
+ 71.45 · GSM8K flexible 35.00 · **GSM8K stated-format 39.67**. It leads us
308
+ on instruction following, tool calling and MMLU-Redux; we lead on MMLU
309
+ full-57, on GSM8K flexible extraction (51.00 vs 35.00) and — new with this
310
+ brain — on **GSM8K stated-format extraction, 49.33 vs 39.67**, the one
311
+ column the category leader previously held. (Its MMLU cell is
312
+ clean-lineage; ours carries the adaptation banner.)
313
+
314
+ ### Reproducing the battery
315
+
316
+ The model registers as a native `transformers` model (`import fermion`),
317
+ so the public harnesses run directly. MMLU / GSM8K were graded with
318
+ lm-eval:
319
+
320
+ ```python
321
+ import fermion, lm_eval
322
+ from lm_eval.models.huggingface import HFLM
323
+ from transformers import AutoModelForCausalLM, AutoTokenizer
324
+
325
+ model = AutoModelForCausalLM.from_pretrained("fermionresearch/Neutrino-8B")
326
+ tok = AutoTokenizer.from_pretrained("fermionresearch/Neutrino-8B")
327
+ lm = HFLM(pretrained=model, tokenizer=tok)
328
+ lm_eval.simple_evaluate(model=lm, tasks=["mmlu"], num_fewshot=5)
329
+ lm_eval.simple_evaluate(model=lm, tasks=["gsm8k"], num_fewshot=0) # flexible-extract
330
+ ```
331
+
332
+ IFEval, MMLU-Redux, and BFCL v3 were graded with evalscope 1.4.2 against
333
+ a vLLM OpenAI-compatible endpoint serving this model, with
334
+ `chat_template_kwargs: {"enable_thinking": false}` and
335
+ `bfcl-eval==2025.10.27.1` pinned:
336
+
337
+ ```bash
338
+ pip install evalscope==1.4.2 bfcl-eval==2025.10.27.1
339
+ evalscope eval --model Neutrino-8B --api-url http://127.0.0.1:8000/v1 \
340
+ --datasets ifeval mmlu_redux bfcl_v3
341
+ ```
342
+
343
+ The exact per-leg configs (context lengths 8k/16k, generation configs,
344
+ the BFCL 13-subset list) and every raw report JSON are in the
345
+ eval-receipts repo.
346
+
347
+ | Meter | Neutrino-8B | Same-harness comparison | Receipt |
348
+ |---|---:|---|---|
349
+ | MMLU full-57 (5-shot likelihood, n=14,042) | **72.1** **[MMLU-adapted, disclosed — see banner; not comparable to clean models]** | Ternary-Bonsai-8B 65.75 · Gemma-4-E4B 70.57 (non-thinking) — both measured by us on the identical harness | eval-receipts repo, d25 grade (`full57_25/`) |
350
+ | MMLU-CF (blind, contamination-free, val-10K) | 54.32 — the W-7500 brain this stack is trained from; the shipped snapshot's own CF cell is not yet measured (behavior stages moved full-57 by ≤0.2 pp, ns) | Ternary-Bonsai-8B 54.31 — dead even. Clean-lineage anchors: 52.74 / 53.61 | eval-receipts repo |
351
+ | TruthfulQA mc1 / mc2 (0-shot likelihood) | 34.52 / 53.57 — clean-lineage anchor checkpoint (internal, not this SKU) | Ternary-Bonsai-8B 32.19 / 51.12 — both wins significant (paired McNemar, p=0.0448 / p=0.0079) | eval-receipts repo |
352
+ | GSM8K flexible-extract (0-shot generative, greedy, 256-token cap, fixed 300-item subset) | **51.00** (shipped brain, cons3-B25) | Ternary-Bonsai-8B 35.0 on the byte-identical meter | eval-receipts repo |
353
+ | GSM8K stated-format (same run, answer must land in the prescribed format; 1024-token cap) | **49.33** (shipped brain, cons3-B25; was 29.33 on ifc-s50) | Ternary-Bonsai-8B **39.67** on the byte-identical meter — this column is ours as of this brain | eval-receipts repo |
354
+ | MMLU full-57 — clean-lineage calibration anchors (internal checkpoints, NOT products) | 63.43 (stage-B step-800) / 63.99 (decay-6.5K research branch) | — | eval-receipts repo |
355
+
356
+ Don't-regress guards, held at release (guards, not headlines): GSM8K
357
+ generative termination 0.51 — above the bf16 teacher's 0.42 anchor on the
358
+ same raw-completion meter, but now BELOW the snapshot-50 stage base (0.63)
359
+ and the pre-stack base (0.593); the consolidation stage traded termination
360
+ rate for stated-format accuracy and we state that plainly — and C4
361
+ perplexity 21.48 (ifc-s50 21.46, snapshot-50 21.47, base 21.40 — flat
362
+ across every behavior stage). The
363
+ raw-completion protocol suppresses EOS across model families, ours and
364
+ fp16 alike; chat-templated serving terminates normally.
365
+
366
+ The trained-on-public-MMLU disclosure travels with every 72.1-class number,
367
+ here and in every table this model appears in. The clean-lineage rows are
368
+ calibration anchors for the contamination-fingerprint method (gap =
369
+ original MMLU minus blind MMLU-CF; clean band 10.4–10.7 pp, adapted band
370
+ 15.9–17.8 pp), published in full in the eval-receipts repo.
371
+
372
+ Tool calling is a trained, measured capability of this brain, stated
373
+ here as our own absolute numbers (no comparison rows on this axis):
374
+ two stacked verifiable-reward stages built this brain. The
375
+ tool-calling stage lifted BFCL v3 13-subset macro 42.15 → 62.60
376
+ (bfcl-eval 2025.10.27.1, our harness, thinking off); the
377
+ instruction-following stage on top took IFEval prompt-strict 67.51 →
378
+ 72.75 while BFCL rose again to 63.67 and MMLU-Redux to 70.65 — a
379
+ validated upgrade on every measured axis, no guard traded (receipts:
380
+ `results_bfcl_round2/r2_summary.json` + `results_ifeval_bump/` A1 walk).
381
+ A third, **consolidation** stage then took IFEval prompt-strict to
382
+ **73.17**, BFCL to **65.31** and GSM8K stated-format from 29.33 to
383
+ **49.33** — and, unlike the first two, this one **did** trade a guard:
384
+ MMLU-Redux fell to **67.84** and GSM8K flexible extraction to **51.00**.
385
+ That trade was made deliberately and is disclosed here rather than
386
+ buried (receipts: `results_consolidation_3/RESULTS.md`).
387
+ Honest read: instruction-following and tool-calling absolutes are still
388
+ below where we want them against the class, and instruction-following
389
+ upgrades continue through week one; the numbers live — like everything
390
+ else — in the eval-receipts repo.
391
+
392
+ <!-- [SHIP-BRAIN RE-PIN 016c6f36… — publish gates] Card re-pinned from
393
+ the ifc-s50 bake to the cons3-B25 ship brain (FOUNDER-AUTHORISED PROMOTE
394
+ 2026-07-27 ~19:50 IST over a deliberate MMLU-Redux guard amendment:
395
+ GSM8K stated 49.33 / flexible 51.00 / BFCL 65.31 / IFEval-ps 73.17 /
396
+ redux 67.84 / full57 72.084 / c4 21.475 / term 0.5067, receipts
397
+ results_consolidation_3/RESULTS.md). CARRY-OVER GATES re-run against THIS
398
+ container 2026-07-27: (1) pip acceptance rerun — NOT YET RUN, still
399
+ outstanding; (2) MLX path+ref gate — DONE, both PASS; (3) GGUF re-cut +
400
+ token-identity gate — DONE; (4) dspec cert — DONE, PASS 27,648 tokens
401
+ 0 mismatches.
402
+ Each is a fan-out leg of the ship-brain bake; swap its receipt in and
403
+ regenerate MANIFEST as it lands. -->
404
+
405
+ ## Weights: get them and verify them
406
+
407
+ The container ships in this repository. Fetch it and check the digest —
408
+ `MANIFEST.json` carries the same sha256, and `fermion info` verifies it for
409
+ you automatically:
410
+
411
+ ```bash
412
+ hf download fermionresearch/Neutrino-8B neutrino-8b_v4.bin --local-dir .
413
+ shasum -a 256 neutrino-8b_v4.bin # must print 016c6f362...b4155fa0
414
+ ```
415
+
416
+ <!-- Retirement record (receipts culture; internal archive locations are not
417
+ published). Superseded containers, by sha256:
418
+ fe85f9321...768b08d5 — earlier product bake, retired 2026-07-26.
419
+ be44129e5...81b814 — interim stage-B step-800 development container,
420
+ retired earlier; carried the pre-launch working slug.
421
+ Their acceptance/export receipts are held out of this published tree, in
422
+ packaging/retired_receipts_not_published/, because this repo ships only
423
+ 016c6f362...b4155fa0 and provenance for a brain we do not ship is misleading.
424
+ 39787a2e1...2007a3be — ifc-s50 ship brain, retired 2026-07-27 by the
425
+ consolidation-3 arm-B-step-25 promote.
426
+ -->
427
+
428
+ Product bake provenance: exported on a Modal CPU container from the
429
+ archived consolidation-3 arm-B dose-25 QAT state — the ship-brain
430
+ lineage is IF-stack snapshot-50 → round-2 dose-25 verifiable-reward
431
+ tool-calling (step-25 sha256 `752d01d5…3abbc`) → IF-checker stage dose-50,
432
+ VALIDATED-BUMP (step-50 sha256 `e62a1199…0fb6a9`) → **consolidation-3
433
+ arm B dose-25** (step-25 sha256 `09c3f36a…41b8`,
434
+ 32,768,963,587 bytes, banked 2026-07-27 11:40Z —
435
+ `results_consolidation_3/RESULTS.md`) with the banked
436
+ exporter (`export_trit_c_model.py`, pinned command + flags recorded in
437
+ the export receipt; quantizer parameters are not published); full receipt at S3
438
+ `archive/bfcl_round2/export_v4/export_receipts.json` (the in-tree
439
+ `receipts/` acceptance + export copies are still the RETIRED s50 bake's —
440
+ swap them, plus the fresh acceptance receipt, into `receipts/` and
441
+ regenerate this tree's MANIFEST after the §3 acceptance rerun).
442
+ <!-- RETIRED 2026-07-26 s50 provenance (kept per receipts culture): IF-stack
443
+ snap50 QAT state sha256
444
+ f31195a8e2fcdeff0c42b9e3968a8259d44e647e93f601d77d74b5980bffa2a2,
445
+ 32,768,963,587 bytes, durably archived at
446
+ archive/if_stack_b800/snap50/last_state.pt; bake receipt
447
+ archive/if_stack_b800/export_v4/export_receipts.json; exporter flags
448
+ identical. Earlier retirement (interim): QAT state sha256
449
+ a1ffcf409c0330345e240397093f61654ad013ed8935a62a0a19164d99daa1f9,
450
+ 32,768,963,587 bytes; exporter flags identical. -->
451
+
452
+ ## Runtime matrix
453
+
454
+ Single-stream decode, one artifact across every row (venue + date on each
455
+ number in the eval-receipts repo):
456
+
457
+ | Platform | Surface | Rate | Memory |
458
+ |---|---|---:|---|
459
+ | H100 80 GB | research stack, drafted (0.6B draft, certified exact) | 763 tok/s (counting; 402-500 tok/s on other prompt classes) | — |
460
+ | H100 80 GB | research stack, plain greedy | 396 tok/s | — |
461
+ | NVIDIA L4 | GGUF pack via our CUDA fork, full offload | 30.7 tok/s | 4.68 GiB @ 4k ctx |
462
+ | Apple M5, 16 GB | MLX pack (median, 3 runs) | 25.0 tok/s | 3.67 GiB peak, 6 GiB cap |
463
+ | Apple M5 | `bin/` native binary, CPU only, 9 threads | 24.94 tok/s | < 8 GiB resident |
464
+
465
+ ## Speculative decoding (certified exact)
466
+
467
+ This repo's 8B pairs with [Neutrino-0.6B-Chat](https://huggingface.co/fermionresearch/Neutrino-0.6B-Chat)
468
+ as a **draft model** for speculative decoding: the 0.6B proposes tokens,
469
+ the 8B verifies them, and the output is **token-identical to plain 8B
470
+ greedy decode** — speculation changes throughput, never the answer. Both
471
+ models are the same TRTC v4 container format and run on the same
472
+ `fermion-run` binaries, so the pairing needs no extra conversion.
473
+
474
+ | Meter | Value | Note |
475
+ |---|---:|---|
476
+ | Certification (exact-match tokens) | **27,648 spec tokens, 0 mismatches** (receipt `receipts/dspec_cert_cons3_b25.json`, re-run against THIS container 2026-07-27; 54/54 arm x prompt cells identical) | draft+verify vs plain greedy, N tokens, 0 mismatches required |
477
+ | Counting-style prompts | **×1.92 (763 tok/s vs 397 plain)** | measured speedup vs plain 8B greedy |
478
+ | Factual / short-answer prompts | **×1.26 (500 tok/s)** | " |
479
+ | Code | ×1.10 (437 tok/s) | " |
480
+ | Prose continuation | ×1.06 (422 tok/s) | " |
481
+ | Conversational explanation | ×1.01 (402 tok/s) | " |
482
+ | Venue | H100, 3-round median, dynamic-k controller, 2026-07-27 | same-moment, same-machine protocol (see VERIFY) |
483
+
484
+ Mechanics (measured on this brain, 2026-07-27 cert run): on counting
485
+ prompts the 8B still accepts 100% of drafted tokens; on factual prompts
486
+ acceptance is 86.9% and on chat-factual 90.3%; it falls to 81.4% on prose
487
+ and 50.0% on technical explanation. Acceptance decays with draft length
488
+ everywhere except counting, so the dynamic-k controller shortens its
489
+ drafts there and the gain thins. **This brain drafts less well than
490
+ ifc-s50 did** on everything but counting — the speedup table above is
491
+ lower across four of five rows, and that is a throughput regression we
492
+ state rather than carry over the old numbers.
493
+
494
+ Receipts: `receipts/dspec_cert_cons3_b25.json` (banked, ship brain).
495
+ Honest framing: speculative decoding is a **throughput** feature with a
496
+ correctness certificate; it does not change quality, and the speedup is
497
+ prompt-dependent — the five rows above are the honest spread, not a
498
+ single headline number.
499
+
500
+ ## Limitations
501
+
502
+ - Chat-tuned for short helpful answers. Tool calling is trained and
503
+ measured (BFCL v3 13-subset macro 65.31, our harness — see Benchmarks)
504
+ but carries no guarantees at this size; instruction-following and
505
+ tool-calling absolutes are below where we want them against the class,
506
+ and instruction-following upgrades continue through week one.
507
+ - This is a non-thinking model: it ships and is evaluated with thinking
508
+ disabled (`enable_thinking=False`).
509
+ - MMLU-family numbers carry the [MMLU-adapted, disclosed] label by
510
+ construction — never quote this model's 72.1-class cells without it.
511
+ The blind MMLU-CF cell and the clean-lineage anchors are the honest
512
+ cross-model meters.
513
+ - Against the strongest same-byte-class open model on identical
514
+ protocols, instruction following and tool calling trail: their 83.65
515
+ IFEval prompt-strict and 71.45 BFCL v3 vs our 73.17 and 65.31. Their
516
+ MMLU-Redux 71.47 also leads our 67.84.
517
+ - GSM8K stated-format and flexible extraction now sit close together
518
+ (49.33 vs 51.00, same run): the format gap that used to be 33 points is
519
+ largely closed. The cost, disclosed above, is that flexible extraction
520
+ itself fell from 62.67, so the model lands the prescribed format far
521
+ more reliably but solves slightly fewer problems overall.
522
+ - The GGUF pack loads only through our llama.cpp fork (`gguf/`) until
523
+ upstreaming lands; stock llama.cpp, ollama, and LM Studio binaries do
524
+ not know the FV5/FV5B tensor types.
525
+ - The pip package's torch path prioritizes bit-exactness over throughput;
526
+ use the `bin/` runtimes in this repo for speed.
527
+ - The family's chat 0.6B SKU is not bit-identical across its three
528
+ shipping runtimes (near-tie argmax flips at small logit margins; each
529
+ runtime deterministic on its own) — see the Neutrino-0.6B-Chat card.
530
+ - Knowledge cutoff and multilingual behavior inherit from the Qwen3-8B base
531
+ plus our disclosed post-training mix; see the data disclosure in
532
+ eval-receipts.
533
+
534
+ ## Model lineage and methodology
535
+
536
+ The full methodology — the ternary QAT recipe, the staged post-training
537
+ program (combined behavior stage → IF-1 instruction stack → two
538
+ verifiable-reward stages), the contamination-fingerprint method behind
539
+ the MMLU banner, and the per-cell receipts (venue, version, date) — is
540
+ published in the eval-receipts repo. The engineering story of the format
541
+ and the engine is on the research pages at fermionresearch.com/research/.
542
+ Lineage in one line: base model (see frontmatter) → ternary QAT →
543
+ MMLU-adapted W-7500 brain (disclosed) → combined behavior stage → IF-1
544
+ stack snapshot-50 → verifiable-reward tool-calling dose-25 → IF-checker
545
+ dose-50, baked 2026-07-26 as the ship container pinned above.
546
+
547
+ ## License and attribution
548
+
549
+ Weights: Apache-2.0. Derivative of Qwen/Qwen3-8B (Apache-2.0, Alibaba
550
+ Cloud) — see `LICENSE`. Post-training data sources are disclosed in the
551
+ eval-receipts repo (UltraChat, MetaMath, the public `cais/mmlu` splits per
552
+ the banner, and the other accepted-source datasets).
553
+
554
+ `bin/` binaries: prebuilt, closed-source, free to use with these weights;
555
+ no redistribution outside this repo; no reverse engineering. [EULA short
556
+ text — final wording lands with the binaries at upload; RELEASE_RUNBOOK §6.]
557
+
558
+ Contact: [PENDING-DNS: mailbox on fermionresearch.com — fill when mail is configured]
bin/MANIFEST.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "note": "bin/ staging manifest: blessed fermion-run release binaries (release-binaries lane, 2026-07-25; receipts in handoffs/results_release_binaries/). Entries mirror the .sha256 sidecars. Functional gates ran against the INTERIM dev container (bake sha256 be44129e...); rerun the one-command smoke after the final W-pipeline product bake lands (RELEASE_RUNBOOK par.4) \u2014 the binaries are container-agnostic (any TRTC v4 arch-3 file) so no rebuild is expected.",
3
+ "binaries": {
4
+ "fermion-run-macos-arm64": {
5
+ "status": "RE-BLESSED 2026-07-27 (thread-safety rebuild, results_native_threadfix): macOS arm64 CPU runtime (NEON dotprod + persistent thread pool; libomp statically linked, only libSystem dep). Supersedes the 2026-07-25 build b963de1a..., which overflowed the per-thread activation-quant buffer above 16 threads. Gated by an OUTPUT-STABILITY thread sweep (packaging/acceptance/thread_sweep.py): byte-identical generated ids at 1/4/8/15/16/17/20/23/max threads on the 0.6B and the 8B, and identical to the superseded binary's output at every thread count it survived.",
6
+ "bytes": 621480,
7
+ "sha256": "d7b257b5cd4b08cadd04fd867158ac50619aab23164294794dc10f05f1ede53c",
8
+ "sidecar": "fermion-run-macos-arm64.sha256",
9
+ "build_receipt": "handoffs/results_release_binaries/receipt_macos_arm64.json"
10
+ },
11
+ "fermion-run-linux-x64": {
12
+ "status": "RE-BLESSED 2026-07-27 (thread-safety rebuild, results_native_threadfix): Linux x86-64 CPU runtime, AVX2 baseline (-march=x86-64-v3), fully static ELF, same source and flags as the 2026-07-25 build. Supersedes 4a09b5cb..., which SIGSEGVed on the 8B and returned silently WRONG TOKENS at exit code 0 on the 0.6B above 16 threads. Gated by the same output-stability thread sweep, run on a 24-core Linux venue.",
13
+ "bytes": 1249584,
14
+ "sha256": "499ecdc569eae2ceaeb4a34cd1f11a91c2c09dfb17aa5847532359c6dc57d93a",
15
+ "sidecar": "fermion-run-linux-x64.sha256",
16
+ "build_receipt": "handoffs/results_release_binaries/receipt_linux_x64.json"
17
+ },
18
+ "fermion-run-linux-cuda": {
19
+ "status": "COMING-SOON (NOT STAGED, no fake artifact): no arch-3 (Qwen3) CUDA runtime exists in kernels/gpu_e2e/ (arch-1/arch-2 walkers only) and the CUDA lane was Python+torch-extension harnesses, never a standalone binary. Blocker + week-one lane recommendation: handoffs/results_release_binaries/CUDA_BLOCKER.md. Working fallback documented in bin/README.md: pip fermion torch path with --device cuda.",
20
+ "bytes": null,
21
+ "sha256": null,
22
+ "sidecar": null
23
+ }
24
+ }
25
+ }
bin/README.md ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # bin/ — prebuilt `fermion-run` native runtimes
2
+
3
+ STATUS: **BLESSED BINARIES STAGED 2026-07-25** (release-binaries lane;
4
+ receipts in `handoffs/results_release_binaries/` in the research tree).
5
+ Binaries are prebuilt and closed-source (kernel source does not ship);
6
+ every binary has a `.sha256` sidecar. EULA line lives on the model card.
7
+ Functional gates ran on the interim 8B v4 cut; the runtime is
8
+ container-agnostic (any TRTC v4 arch-3), and the RELEASE_RUNBOOK §3
9
+ acceptance rerun against the final product container (`016c6f36…`, the
10
+ cons3-B25 ship brain — supersedes ifc-s50 `39787a2e…` and d25
11
+ `06af07b5…`) is a publish gate and is STILL OUTSTANDING.
12
+
13
+ ## What is here
14
+
15
+ | File | Platform / backend | State |
16
+ |---|---|---|
17
+ | `fermion-run-macos-arm64` | macOS arm64 (M1+), CPU — NEON dotprod + persistent thread pool, OpenMP statically linked, zero dylib deps beyond libSystem | BLESSED (functional-gated on the 8B v4 container) |
18
+ | `fermion-run-linux-x64` | Linux x86-64, CPU — AVX2 baseline (`-march=x86-64-v3`), fully static ELF, runs on any 2015+ x86-64 distro | BLESSED (functional-gated on the 8B v4 container) |
19
+ | `fermion-run-linux-cuda` | Linux x86-64, NVIDIA CUDA | **COMING SOON** — not staged; see fallback below |
20
+
21
+ Honesty notes, so nobody is surprised:
22
+
23
+ - The macOS binary is the **CPU** runtime. The Metal-optimized runtime for
24
+ this model's container format is a week-one follow-up (the existing Metal
25
+ dev builds target a different container lineage and cannot load this
26
+ model); when it lands it replaces this entry with its own receipts.
27
+ - The linux-x64 binary is built for the AVX2 **compatibility baseline**, not
28
+ the AVX-512-VNNI fast path the banked x86 speed receipts were measured on.
29
+ It is correct (bit-exact GEMV unit gate at build time) and self-contained;
30
+ a VNNI-optimized variant is a week-one candidate.
31
+ - CUDA fallback that works today: `pip install fermion` then
32
+ `fermion chat --model fermionresearch/Neutrino-8B --device cuda`
33
+ (reference torch path — correctness-first, not the speed path).
34
+
35
+ ## CLI (what the binaries actually take)
36
+
37
+ The binaries take **pre-tokenized ids**, not prompt strings — tokenization
38
+ lives in the `fermion` CLI (`pip install fermion`, the one door), which
39
+ downloads the model + the platform-matching binary from this repo and shells
40
+ out for the fast path. Manual use:
41
+
42
+ ```
43
+ ./fermion-run-<platform> <container.bin> "<id0,id1,...>" <n_new_tokens> <n_threads> \
44
+ [logits_out.bin] [--temp T] [--top-p P] [--rep-pen R] [--seed S] \
45
+ [--stop-id I0[,I1...]] [--min-p F] [--presence-pen F] [--freq-pen F] \
46
+ [--dry MULT [--dry-base B --dry-allow N]] [--pen-window N] \
47
+ [--eos-exempt-minp] [--eos-boost B:N] [--ppl]
48
+ ```
49
+
50
+ The container, the id list, the token count and the **thread count are
51
+ positionals, in that order**. There is no `--chat` and no `--threads`.
52
+
53
+ > [!IMPORTANT]
54
+ > **Sampler flags require `--temp > 0` to engage.** At `--temp 0`
55
+ > (pure greedy) the binary takes a fast argmax path and silently
56
+ > ignores `--rep-pen`, `--top-p`, and all other penalty flags. If
57
+ > you want penalties with near-deterministic output, use
58
+ > `--temp 0.01 --top-p 1.0 --seed 42`. (Found 2026-07-26; a
59
+ > runtime patch making this explicit is a week-one item.)
60
+ >
61
+ > **`--pen-window` is inert on the legacy sampled path.** With `--temp > 0`
62
+ > and none of `--min-p` / `--presence-pen` / `--freq-pen` / `--dry` present,
63
+ > the repetition-penalty window is clamped to the last **64** tokens
64
+ > regardless of what `--pen-window` says (`trit_run.c`: `rwin = g_ext ?
65
+ > max(g_penwin, 64) : 64`). Adding any one of those four flags switches on
66
+ > the modern penalty stack and makes `--pen-window` literal. Every graded
67
+ > C receipt was taken on the legacy path, so the banked C numbers are
68
+ > **64-window** numbers even where the command line reads
69
+ > `--pen-window 256`; the torch and MLX paths do apply 256. Documented
70
+ > 2026-07-27 (results_pip_native + results_0p6b_binaries); making the flag
71
+ > literal on the legacy path is a week-one runtime patch.
72
+
73
+ Defaults: greedy decode; without `--stop-id` exactly `n_new_tokens` are
74
+ generated (EOS ignored); with `--stop-id` generation stops at the stop token
75
+ (coherence mode). Output: line 1 = generated ids (comma-separated), line 2 =
76
+ `tok/s: ...`. `n_new_tokens 0` = prefill-only logits dump.
77
+
78
+ Per-platform one-liners (chat-format ids for a user prompt, then generate up
79
+ to 64 tokens, stop at `<|im_end|>` = 151645):
80
+
81
+ ```bash
82
+ # ids for 'In one sentence, why is the sky blue?' via this repo's tokenizer:
83
+ IDS=$(python3 -c "
84
+ from tokenizers import Tokenizer
85
+ t = Tokenizer.from_file('tokenizer.json')
86
+ p = '<|im_start|>user\nIn one sentence, why is the sky blue?<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n'
87
+ print(','.join(map(str, t.encode(p).ids)))")
88
+
89
+ # macOS arm64:
90
+ ./bin/fermion-run-macos-arm64 neutrino-8b_v4.bin "$IDS" 64 8 --stop-id 151645
91
+ # Linux x86-64:
92
+ ./bin/fermion-run-linux-x64 neutrino-8b_v4.bin "$IDS" 64 8 --stop-id 151645
93
+ ```
94
+
95
+ macOS quarantine note: after download run
96
+ `xattr -d com.apple.quarantine bin/fermion-run-macos-arm64` once.
97
+
98
+ ## sha256 sidecar convention
99
+
100
+ Every binary `X` ships with `X.sha256` (standard `shasum` line):
101
+
102
+ ```bash
103
+ # producer (at staging time, from this directory):
104
+ shasum -a 256 fermion-run-macos-arm64 > fermion-run-macos-arm64.sha256
105
+ # consumer:
106
+ shasum -a 256 -c fermion-run-macos-arm64.sha256
107
+ ```
108
+
109
+ `MANIFEST.json` here mirrors the sidecars (file -> bytes + sha256 + build
110
+ provenance) and is regenerated by `packaging/receipts/gen_manifests.py`
111
+ whenever binaries are added or replaced.
112
+
113
+ ## Upload checklist (mirrors RELEASE_RUNBOOK §6)
114
+
115
+ 1. Binaries + sidecars staged here (done 2026-07-25, receipts in the lane).
116
+ 2. Regenerate the tree MANIFEST (`packaging/receipts/gen_manifests.py`).
117
+ 3. After `hf upload`, re-download each binary and `shasum -a 256 -c` its
118
+ sidecar (the §6 re-download hash-verify rule applies to binaries too).
119
+ 4. One-command smoke on a machine matching each platform before flipping
120
+ the repo public (macOS leg locally is fine — the C runtime is not the
121
+ 8B torch load, but respect the RSS budget; linux leg on Modal).
122
+
123
+ ## thread count: the runtime is safe above 16 threads again
124
+
125
+ > [!IMPORTANT]
126
+ > **Rebuilt 2026-07-27 (thread-safety fix).** The 2026-07-25 binaries
127
+ > corrupted memory above **16 threads**: the per-thread activation-quant
128
+ > scratch buffer inside the runtime was allocated for 16 workers while the
129
+ > OpenMP path indexed it by thread id with no clamp. On Linux x86-64 that was
130
+ > a SIGSEGV on the 8B and — worse — **exit code 0 with silently different,
131
+ > wrong tokens** on the 0.6B, on any box with 18 or more logical cores (the
132
+ > `fermion` CLI asked for `cpu_count - 1`). macOS arm64 never reached it
133
+ > because Apple silicon tops out at 16 logical cores.
134
+ >
135
+ > These binaries size that buffer from the live thread count, clamp the
136
+ > requested count at a hard ceiling, and are gated by an output-stability
137
+ > thread sweep (`packaging/acceptance/thread_sweep.py`) that asserts
138
+ > **byte-identical generated ids** at 1/4/8/15/16/17/20/23/max threads rather
139
+ > than merely a zero exit code. The `fermion` package independently caps the
140
+ > count it will request at 16 (`fermion.native.THREAD_CAP`).
bin/fermion-run-linux-x64 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:499ecdc569eae2ceaeb4a34cd1f11a91c2c09dfb17aa5847532359c6dc57d93a
3
+ size 1249584
bin/fermion-run-linux-x64.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ 499ecdc569eae2ceaeb4a34cd1f11a91c2c09dfb17aa5847532359c6dc57d93a fermion-run-linux-x64
bin/fermion-run-macos-arm64 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d7b257b5cd4b08cadd04fd867158ac50619aab23164294794dc10f05f1ede53c
3
+ size 621480
bin/fermion-run-macos-arm64.sha256 ADDED
@@ -0,0 +1 @@
 
 
1
+ d7b257b5cd4b08cadd04fd867158ac50619aab23164294794dc10f05f1ede53c fermion-run-macos-arm64
config.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transformers_version": "5.14.1",
3
+ "architectures": null,
4
+ "output_hidden_states": false,
5
+ "return_dict": true,
6
+ "dtype": "auto",
7
+ "chunk_size_feed_forward": 0,
8
+ "is_encoder_decoder": false,
9
+ "id2label": {
10
+ "0": "LABEL_0",
11
+ "1": "LABEL_1"
12
+ },
13
+ "label2id": {
14
+ "LABEL_0": 0,
15
+ "LABEL_1": 1
16
+ },
17
+ "problem_type": null,
18
+ "_name_or_path": "",
19
+ "container": "neutrino-8b_v4.bin",
20
+ "row_chunk": 512,
21
+ "int8_row_chunk": 4096,
22
+ "model_type": "trtc_v4",
23
+ "output_attentions": false
24
+ }
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "do_sample": false,
4
+ "eos_token_id": [
5
+ 151645,
6
+ 151643
7
+ ],
8
+ "pad_token_id": 151643,
9
+ "transformers_version": "4.53.2"
10
+ }
gguf/Modelfile ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Neutrino-8B — ollama Modelfile (requires ollama built against the
2
+ # fermion-fv5 llama.cpp fork; stock ollama cannot load the FV5 type)
3
+ FROM ./neutrino-8b-fv5.gguf
4
+ PARAMETER temperature 0.7
5
+ PARAMETER top_p 0.95
gguf/README.md ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Neutrino-8B — GGUF pack (llama.cpp fork `fermion-fv5`)
2
+
3
+ Run [Neutrino-8B](../README.md) through the **llama.cpp toolchain**
4
+ (llama-cli, llama-server, llama-bench, and anything built on them,
5
+ including ollama built from source against the fork).
6
+
7
+ **Read this first — which door do you want?**
8
+
9
+ - **This GGUF pack is the compatibility door.** It exists so the
10
+ llama.cpp/ollama/LM Studio world can load and run our format with the
11
+ ecosystem's own tooling. It runs on **CPU and CUDA** (full GPU offload
12
+ works — see below), is correctness-certified on both backends, and is
13
+ *not* tuned for peak speed.
14
+ - **The native binaries are the fast door.** The `fermion-run` CPU
15
+ runtimes in this repo's `bin/` decode the same container at native
16
+ speed (see `bin/README.md`). If you want speed, use those; if you want
17
+ llama.cpp-ecosystem integration, use this pack.
18
+
19
+ **Backend support, stated precisely:**
20
+
21
+ | backend | status |
22
+ |---|---|
23
+ | CPU (x86-64 AVX2, Apple arm64) | shipped, certified |
24
+ | **CUDA, full offload (`-ngl 99`)** | **shipped, certified** — measured below |
25
+ | Metal | **not implemented** — no FV5 kernel; build Metal-off and run on CPU |
26
+ | Vulkan / ROCm / SYCL | not implemented |
27
+
28
+ > [!NOTE]
29
+ > The digests in `SHA256SUMS` certify the **release product cut**:
30
+ > converted and bit-checked (token-identical gate PASS, 2026-07-27 —
31
+ > 8/8 free-running streams identical, 0/1024 teacher-forced mismatches,
32
+ > 0 near-tie flips) from the release container `neutrino-8b_v4.bin`
33
+ > (sha256 `016c6f36…55fa0`, the cons3-B25 ship brain — see the parent
34
+ > card). Receipts in `receipts/`. Earlier cuts that certified this
35
+ > pipeline (ifc-s50, d25, s50, W-baseline) are retired and commented in
36
+ > `SHA256SUMS`; the ifc-s50 gate receipts are kept in
37
+ > `receipts_ifcs50_retired/` as historical record.
38
+
39
+ ## What's in the file
40
+
41
+ `neutrino-8b-fv5.gguf` stores the model in two Fermion GGML types added by
42
+ the fork (they do **not** exist in stock llama.cpp):
43
+
44
+ | type | used for | layout per 256-element block | bits/weight |
45
+ |---|---|---|---|
46
+ | `FV5` (43) | all 252 transformer linears | f32 `s_lo`, f32 `s_hi`, 3×32B bit-planes (`bp`,`bn`,`br`) | 3.25 |
47
+ | `FV5B` (44) | int8 embedding + lm_head rows (untied) | f32 row scale, 256×int8 | 8.125 |
48
+
49
+ Weights are `w = (bp − bn) · (br ? s_hi : s_lo)` — the exact five-value
50
+ {0, ±s_lo, ±s_hi} function of the TRTC v4 container. The converter copies
51
+ the container's bit-planes and f32 per-row dual scales verbatim (no
52
+ re-quantization anywhere), and bit-compares every tensor's dequantization
53
+ against the container's f32 expansion before writing. Norms stay f32.
54
+ Activations are never quantized at runtime (`vec_dot_type = F32`), so the
55
+ only numerical difference vs the fp32 expansion is float summation order.
56
+
57
+ ## Files
58
+
59
+ See `SHA256SUMS` in this folder for the authoritative digests of:
60
+
61
+ - `neutrino-8b-fv5.gguf` — the model (FV5/FV5B, ~4.2 GiB)
62
+ - `fv5.patch` / `fermion-fv5.bundle` — the fork as a single patch/bundle
63
+ on top of upstream `ggml-org/llama.cpp` @ `d67c0b41`, branch
64
+ `fermion-fv5`, commit `e07b3ec1`, docs in `FORK_NOTES.md`
65
+
66
+ ## Quickstart (build the fork, run the model)
67
+
68
+ ```bash
69
+ git clone https://github.com/fermionresearch/llama.cpp && cd llama.cpp
70
+ git checkout fermion-fv5
71
+ # or, from upstream + our patch:
72
+ # git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
73
+ # git checkout d67c0b4107112e4790774c3a8169e2e3eb24643b && git am fv5.patch
74
+
75
+ # CPU
76
+ cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF
77
+ cmake --build build -j --target llama-completion llama-bench
78
+
79
+ ./build/bin/llama-completion -m neutrino-8b-fv5.gguf \
80
+ -p "Explain why the sky is blue." -n 256 --temp 0 -no-cnv
81
+ ```
82
+
83
+ ### CUDA (full offload)
84
+
85
+ ```bash
86
+ cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF \
87
+ -DGGML_CUDA=ON -DGGML_CUDA_NO_VMM=ON -DCMAKE_CUDA_ARCHITECTURES=75\;89
88
+ cmake --build build -j --target llama-completion llama-bench
89
+
90
+ ./build/bin/llama-completion -m neutrino-8b-fv5.gguf -ngl 99 \
91
+ -p "Explain why the sky is blue." -n 256 --temp 0 -no-cnv
92
+ ```
93
+
94
+ `-DGGML_CUDA_NO_VMM=ON` is **required** when building on a box that has the
95
+ CUDA toolkit but no driver (GPU-less builder/CI): without it the link fails
96
+ on `cuGetErrorString` from the driver API. Drop it if you build on a machine
97
+ with a real driver.
98
+
99
+ **Measured, NVIDIA L4 (driver 580.95.05, 23 GB), full offload:**
100
+
101
+ | metric | value |
102
+ |---|---|
103
+ | tg128 (decode) | **30.68 ± 0.05 tok/s** |
104
+ | pp512 (prefill) | **477.1 ± 4.9 tok/s** |
105
+ | VRAM peak @ 4096 ctx | **4680 MiB (4.68 GiB)** |
106
+
107
+ An 8B model doing 30.7 tok/s inside 4.68 GiB of VRAM is the point of the
108
+ format: this fits on a card that cannot hold an 8B at fp16 at all.
109
+
110
+ > **`llama-server` is not in the verified target set.** The gated build ran
111
+ > `-DLLAMA_BUILD_SERVER=OFF`, so we have not exercised the server against an
112
+ > FV5 pack. It should work, but we will not print a command we never ran.
113
+ > `llama-cli` likewise was not the gated target — `llama-completion` was.
114
+
115
+ ### ollama (built against the fork)
116
+
117
+ Stock ollama binaries do not know the FV5 type. Build ollama from source
118
+ with its vendored llama.cpp replaced by the fork checkout, then:
119
+
120
+ ```
121
+ # Modelfile -- the conversation row of the parent card's recommended
122
+ # settings. llama.cpp defaults would otherwise give you top-k 40 and a
123
+ # 64-token penalty window, neither of which we measured.
124
+ FROM ./neutrino-8b-fv5.gguf
125
+ PARAMETER temperature 0
126
+ PARAMETER top_p 1.0
127
+ PARAMETER top_k 0
128
+ PARAMETER repeat_penalty 1.05
129
+ PARAMETER repeat_last_n 256
130
+ PARAMETER num_predict 512
131
+ PARAMETER stop "<|im_end|>"
132
+ ```
133
+
134
+ For tool calling and JSON, drop the penalty (`PARAMETER repeat_penalty 1.0`):
135
+ structured output measured 8/8 valid at every setting tried, and a penalty
136
+ over `"`, `:` and `{` is the one thing that can break the format. For
137
+ long-form prose, `temperature 0.7`, `top_p 0.95`, `num_predict 1024`.
138
+
139
+ ```bash
140
+ ollama create neutrino-8b -f Modelfile
141
+ ollama run neutrino-8b "Explain why the sky is blue."
142
+ ```
143
+
144
+ LM Studio: point it at a llama.cpp server built from the fork
145
+ (`llama-server -m neutrino-8b-fv5.gguf`) and connect via the
146
+ OpenAI-compatible endpoint; the bundled engine cannot load FV5 directly.
147
+
148
+ ## Speculative decoding quickstart (llama.cpp native, EXPERIMENTAL)
149
+
150
+ The fork inherits mainline llama.cpp's built-in speculative decoding, so
151
+ the 0.6B fv5 pack can draft for this 8B out of the box. **Honesty note,
152
+ read first**: the acceptance rule here is llama.cpp's own sampling-based
153
+ speculation — **not** our bit-gated certification. This is "llama.cpp's
154
+ native speculation with our models"; speeds vary by text; our
155
+ certified-exact speculation lives in the native runtime (and the MLX pack's
156
+ exactness-gated `--spec` mode).
157
+
158
+ ```bash
159
+ cmake --build build -j --target llama-speculative
160
+ ./build/bin/llama-speculative -m neutrino-8b-fv5.gguf \
161
+ -md neutrino-0.6b-base-fv5.gguf \
162
+ -p "The three laws of thermodynamics state that" \
163
+ -n 128 --temp 0 -t 16 -td 16 --spec-draft-n-max 4
164
+ ```
165
+
166
+ Note the flag surface: this tree removed `--draft-max` (use
167
+ `--spec-draft-n-max`; the old flag errors out with a pointer).
168
+
169
+ Measured on a Modal CPU container (16 threads, x86-64-v3 portable build,
170
+ 128-token greedy, 8B-wbase target + 0.6B-base draft;
171
+ `receipts/gguf_spec_*.json`): **CPU speculative decode is currently a
172
+ honest null for this pack — it is slower than plain decode at every draft
173
+ depth we tried** (draft depth 16: 0.14–0.29× baseline; depth 4: 0.44–0.66× with
174
+ llama.cpp-rule acceptance 30–57% — facts 5.3→3.5, code 5.4→3.1, prose
175
+ 5.4→2.4 tok/s on that host). Why: the FV5 ggml type has a fast `vec_dot` but no
176
+ batched-matmul (sgemm) path yet, so verifying M draft positions costs
177
+ almost M× a single token (~121 ms/position vs ~151 ms single-token in the
178
+ perf prints) — the amortization that makes speculation win never happens,
179
+ and per-token draft acceptance (llama.cpp's rule) of the base 0.6B for the
180
+ 8B is text-dependent on top. An FV5 sgemm path is the wired follow-up that
181
+ would flip this; until then, plain `llama-completion`/`llama-cli` is the
182
+ right way to run this pack on CPU.
183
+
184
+ ## Correctness & speed
185
+
186
+ - **CPU gate:** greedy streams (8 prompts × 128 tokens) from the fork's CPU
187
+ backend, checked token-by-token against the container's fp32 expansion
188
+ reference. Verdict and near-tie argmax notes: `receipts/` in this folder.
189
+ - **CUDA gate:** greedy streams (8 prompts × 256 tokens, `-ngl 99`,
190
+ `NVIDIA_TF32_OVERRIDE=0`) checked against the certified CPU path.
191
+ **7 of 8 prompts free-run token-identical; 2047 of 2048 teacher-forced
192
+ steps agree.** The single flip is a documented near-tie — step 0 of one
193
+ prompt, logits 15.631217 vs 15.630566, margin **6.5e-4**, inside the
194
+ preregistered 1e-3 bar — i.e. fp32 summation-order noise, not a kernel
195
+ defect. Zero unexplained flips. We report this rather than rounding it to
196
+ "identical"; the raw receipt is the authority.
197
+ - **Speed:** CUDA numbers are in the table above. For CPU tok/s see
198
+ `receipts/bench_*.json`. The native runtime is still the fast door;
199
+ upstreaming and a SIMD-fused NEON `vec_dot` are wired follow-ups. Note the
200
+ speculative-decoding null documented above — do not assume it helps here.
201
+
202
+ ### Platform scope of the native binaries (`../bin/`)
203
+
204
+ Two prebuilt runtimes ship: **macOS arm64** (NEON, static libomp) and
205
+ **Linux x86-64**. They are not equivalent. The Linux binary is built to a
206
+ portable **AVX2 baseline** so it runs on older hosts, which means it leaves
207
+ AVX-512/VNNI performance on the table. **Apple-silicon throughput figures
208
+ quoted elsewhere in this repo are Apple-silicon figures and do not transfer
209
+ to the Linux x86-64 binary.** Both binaries verify against their `.sha256`
210
+ sidecars in `../bin/`; on macOS and Linux you may need `chmod +x` after
211
+ download, as the executable bit does not always survive transport.
212
+
213
+ ## License
214
+
215
+ **This folder is mixed-licence. Read the split before you reuse anything.**
216
+
217
+ - The **model pack** `neutrino-8b-fv5.gguf` is Apache-2.0, same as the
218
+ parent model card (`../LICENSE`, and `../NOTICE` for the Qwen3
219
+ attribution).
220
+ - The **`fv5.patch` / `fermion-fv5.bundle` source** is a derivative of
221
+ ggml-org/llama.cpp and is therefore **MIT**, upstream's licence — *not*
222
+ Apache-2.0. It is not covered by the parent card's grant. See the fork
223
+ repository's own `LICENSE`.
gguf/SHA256SUMS ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Neutrino-8B GGUF pack — authoritative digests (lane results_gguf_fork)
2
+ # NOTE: this certifies the cons3-B25 SHIP-BRAIN cut (source container
3
+ # neutrino-8b_v4.bin, sha256 016c6f36…55fa0), gate_fv5 PASS
4
+ # token-identical 2026-07-27: verdict PASS, 8/8 free-running streams
5
+ # identical, 0/1024 teacher-forced mismatches, 0 near-tie flips
6
+ # (receipts/gateverdict_8b_fv5.json). Lane artifact name
7
+ # neutrino-8b-cons3b25-fv5.gguf (S3 archive/labarchive/gguf_fork/gguf/ +
8
+ # Modal volume ternary-gguf-fork-20260725); published here under the
9
+ # plain name — byte-identical, digest below is the file's sha either
10
+ # way. Verify with `shasum -a 256 -c`.
11
+ 1c13a34360b2531d28821fb6aee41708f99a040215a2522af37213c6c8c87211 neutrino-8b-fv5.gguf
12
+ 81bf7be5eef18eb98ab4823596374c8dd6615151cf5615425973cb65942c0f73 fv5.patch
13
+ # source container: 016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0 neutrino-8b_v4.bin
14
+ # f32 control (not published): 089466ea7a8378b658a4ed0e24788bf67f6a81879d93dfa87a3e6863626d0532 neutrino-8b-cons3b25-f32.gguf
15
+ # fork: ggml-org/llama.cpp @ d67c0b41 + fv5.patch = branch fermion-fv5, commit e07b3ec1 (tree f3067d90)
16
+ # fermion-fv5.bundle sha256: 4fad510f32c9df84efda6a63dcb6de7d33f01590947111fdd337d17d2baf87e0
17
+ # RETIRED cuts — do NOT verify against (pipeline certification lineage):
18
+ # 7dda995047dc70540d2d83f2c464dd118fff62eef1aab36ae71ceb3cc972e5d0 neutrino-8b-ifc50-fv5.gguf (07-26 PM; retired 2026-07-27 by the cons3-B25 promote)
19
+ # ae26b97fb17cdb843a34260b748f43dddc4ca3bb66321a2441e52ba2d5f5d687 neutrino-8b-d25-fv5.gguf (07-26 PM)
20
+ # 210195d72d7858698a94e6aca8365c61648322112d4d3ee31ae1f1d36f730d29 neutrino-8b-s50-fv5.gguf (07-26 AM)
21
+ # c4e82e295ad827918e205f46e6c6c874979321e6aac9f0d296b8175168e12861 neutrino-8b-wbase-fv5.gguf (07-25)
22
+ # The ifc-s50 gate receipts are preserved beside this file in
23
+ # receipts_ifcs50_retired/ (historical record; not the shipping gate).
gguf/receipts/bench_8b.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "bench": [
4
+ {
5
+ "build_commit": "e07b3ec",
6
+ "build_number": 2,
7
+ "cpu_info": "unknown",
8
+ "gpu_info": "",
9
+ "backends": "CPU",
10
+ "model_filename": "/vol/gguf/neutrino-8b-cons3b25-fv5.gguf",
11
+ "model_type": "qwen3 8B FV5 - 3.25 bpw Fermion five-value ternary",
12
+ "model_size": 4087058432,
13
+ "model_n_params": 8190735360,
14
+ "n_batch": 2048,
15
+ "n_ubatch": 512,
16
+ "n_threads": 16,
17
+ "cpu_mask": "0x0",
18
+ "cpu_strict": false,
19
+ "poll": 50,
20
+ "type_k": "f16",
21
+ "type_v": "f16",
22
+ "n_gpu_layers": -1,
23
+ "n_cpu_moe": 0,
24
+ "split_mode": "layer",
25
+ "main_gpu": 0,
26
+ "no_kv_offload": false,
27
+ "flash_attn": -1,
28
+ "devices": "auto",
29
+ "tensor_split": "0.00",
30
+ "tensor_buft_overrides": "none",
31
+ "load_mode": "mmap",
32
+ "embeddings": false,
33
+ "no_op_offload": 0,
34
+ "no_host": false,
35
+ "fit_target": 0,
36
+ "fit_min_ctx": 0,
37
+ "n_prompt": 512,
38
+ "n_gen": 0,
39
+ "n_depth": 0,
40
+ "test_time": "2026-07-27T15:39:39Z",
41
+ "avg_ns": 79088419701,
42
+ "stddev_ns": 90694156,
43
+ "avg_ts": 6.473771,
44
+ "stddev_ts": 0.007424,
45
+ "samples_ns": [
46
+ 79024289865,
47
+ 79152549538
48
+ ],
49
+ "samples_ts": [
50
+ 6.47902,
51
+ 6.46852
52
+ ]
53
+ },
54
+ {
55
+ "build_commit": "e07b3ec",
56
+ "build_number": 2,
57
+ "cpu_info": "unknown",
58
+ "gpu_info": "",
59
+ "backends": "CPU",
60
+ "model_filename": "/vol/gguf/neutrino-8b-cons3b25-fv5.gguf",
61
+ "model_type": "qwen3 8B FV5 - 3.25 bpw Fermion five-value ternary",
62
+ "model_size": 4087058432,
63
+ "model_n_params": 8190735360,
64
+ "n_batch": 2048,
65
+ "n_ubatch": 512,
66
+ "n_threads": 16,
67
+ "cpu_mask": "0x0",
68
+ "cpu_strict": false,
69
+ "poll": 50,
70
+ "type_k": "f16",
71
+ "type_v": "f16",
72
+ "n_gpu_layers": -1,
73
+ "n_cpu_moe": 0,
74
+ "split_mode": "layer",
75
+ "main_gpu": 0,
76
+ "no_kv_offload": false,
77
+ "flash_attn": -1,
78
+ "devices": "auto",
79
+ "tensor_split": "0.00",
80
+ "tensor_buft_overrides": "none",
81
+ "load_mode": "mmap",
82
+ "embeddings": false,
83
+ "no_op_offload": 0,
84
+ "no_host": false,
85
+ "fit_target": 0,
86
+ "fit_min_ctx": 0,
87
+ "n_prompt": 0,
88
+ "n_gen": 128,
89
+ "n_depth": 0,
90
+ "test_time": "2026-07-27T15:43:36Z",
91
+ "avg_ns": 25262016201,
92
+ "stddev_ns": 350847779,
93
+ "avg_ts": 5.067384,
94
+ "stddev_ts": 0.070378,
95
+ "samples_ns": [
96
+ 25510103045,
97
+ 25013929357
98
+ ],
99
+ "samples_ts": [
100
+ 5.01762,
101
+ 5.11715
102
+ ]
103
+ }
104
+ ],
105
+ "smoke_rc": 0,
106
+ "smoke_text": "Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France",
107
+ "threads": 16,
108
+ "cpu": "unknown",
109
+ "nproc": 32,
110
+ "wall_s": 304.1,
111
+ "ts": "2026-07-27T15:44:42Z"
112
+ }
gguf/receipts/convert_8b.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "CONVERT_PASS",
3
+ "container": {
4
+ "name": "neutrino-8b_v4.bin",
5
+ "sha256": "016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0"
6
+ },
7
+ "f32_gguf": {
8
+ "name": "neutrino-8b-cons3b25-f32.gguf",
9
+ "bytes": 32768897920
10
+ },
11
+ "out": {
12
+ "name": "neutrino-8b-cons3b25-fv5.gguf",
13
+ "bytes": 4093015136,
14
+ "sha256": "1c13a34360b2531d28821fb6aee41708f99a040215a2522af37213c6c8c87211"
15
+ },
16
+ "tensors": {
17
+ "fv5": 252,
18
+ "fv5b": 2,
19
+ "f32": 145,
20
+ "total": 399,
21
+ "bitwise_checked": 399
22
+ },
23
+ "tied_output_omitted": false,
24
+ "quantized_params": 8190427136,
25
+ "effective_bpw_quantized": 3.9979,
26
+ "header": {
27
+ "magic": 1129599572,
28
+ "version": 4,
29
+ "arch": 3,
30
+ "n_layers": 36,
31
+ "hidden": 4096,
32
+ "n_heads": 32,
33
+ "n_kv_heads": 8,
34
+ "head_dim": 128,
35
+ "rotary_dims": 128,
36
+ "intermediate": 12288,
37
+ "vocab": 151936,
38
+ "max_pos": 40960,
39
+ "rms_norm_eps": 9.999999974752427e-07,
40
+ "rope_theta": 1000000.0,
41
+ "eok": 2
42
+ },
43
+ "wall_seconds": 174.6
44
+ }
gguf/receipts/gate_8b_fv5.json ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "/vol/gguf/neutrino-8b-cons3b25-fv5.gguf",
3
+ "steps": 128,
4
+ "n_threads": 16,
5
+ "load_seconds": 0.46,
6
+ "kv_type": "f32",
7
+ "prompts": [
8
+ {
9
+ "prompt_ids": [1,791,1283,315,220,17,42,99],
10
+ "free_ids": [198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42,198,1,791,1283,315,220,17,42],
11
+ "free_min_top2_margin": 0.128316,
12
+ "prompt_seconds": 1.369,
13
+ "gen_seconds": 24.789,
14
+ "free_match": true,
15
+ "first_divergence": -1,
16
+ "forced_mismatch_count": 0,
17
+ "forced_mismatches": []
18
+ },
19
+ {
20
+ "prompt_ids": [1,9707,374,264,1296,13,28,7],
21
+ "free_ids": [16,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15],
22
+ "free_min_top2_margin": 0.083496,
23
+ "prompt_seconds": 1.316,
24
+ "gen_seconds": 28.711,
25
+ "free_match": true,
26
+ "first_divergence": -1,
27
+ "forced_mismatch_count": 0,
28
+ "forced_mismatches": []
29
+ },
30
+ {
31
+ "prompt_ids": [1,2610,525,498,1091,11,16,3],
32
+ "free_ids": [323,498,525,537,11,17,3,323,498,525,537,11,18,3,323,498,525,537,11,19,3,323,498,525,537,11,20,3,323,498,525,537,11,21,3,323,498,525,537,11,22,3,323,498,525,537,11,23,3,323,498,525,537,11,24,3,323,498,525,537,11,16,15,3,323,498,525,537,11,16,16,3,323,498,525,537,11,16,17,3,323,498,525,537,11,16,18,3,323,498,525,537,11,16,19,3,323,498,525,537,11,16,20,3,323,498,525,537,11,16,21,3,323,498,525,537,11,16,22,3,323,498,525,537,11,16,23,3],
33
+ "free_min_top2_margin": 0.070037,
34
+ "prompt_seconds": 1.516,
35
+ "gen_seconds": 31.581,
36
+ "free_match": true,
37
+ "first_divergence": -1,
38
+ "forced_mismatch_count": 0,
39
+ "forced_mismatches": []
40
+ },
41
+ {
42
+ "prompt_ids": [785,6722,315,9625,374],
43
+ "free_ids": [12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13,576,6722,315,9625,374,12095,13],
44
+ "free_min_top2_margin": 0.439365,
45
+ "prompt_seconds": 0.952,
46
+ "gen_seconds": 31.652,
47
+ "free_match": true,
48
+ "first_divergence": -1,
49
+ "forced_mismatch_count": 0,
50
+ "forced_mismatches": []
51
+ },
52
+ {
53
+ "prompt_ids": [641,264,28727,33161,11,264,2613,21730,11105],
54
+ "free_ids": [264,11580,448,264,4911,3343,13,576,11580,594,7329,374,9761,448,264,5827,315,31340,11,1817,9334,3432,264,3108,3084,315,220,16,4982,13,576,21730,13166,429,279,11580,702,264,2790,315,220,16,15,15,15,31340,13,4354,11,279,21730,1083,13686,429,1045,31340,525,537,9434,504,279,21730,594,2309,13,44763,11,279,31340,429,525,537,9434,525,1846,429,525,537,23942,311,894,1008,9334,13,758,1008,4244,11,264,9334,374,537,9434,421,432,374,537,8433,311,894,1008,9334,553,264,3108,13,2585,1657,31340,525,9434,504,279,21730,594,2309,1939,1249,11625,419,3491,11,582,1184,311,8253,1246,1657],
55
+ "free_min_top2_margin": 0.053864,
56
+ "prompt_seconds": 1.460,
57
+ "gen_seconds": 25.266,
58
+ "free_match": true,
59
+ "first_divergence": -1,
60
+ "forced_mismatch_count": 0,
61
+ "forced_mismatches": []
62
+ },
63
+ {
64
+ "prompt_ids": [750,75698,1445,1648],
65
+ "free_ids": [198,262,421,308,621,220,15,510,286,470,220,15,198,262,421,308,621,220,16,510,286,470,220,16,198,262,770,510,286,470,75698,1445,12,16,8,488,75698,1445,12,17,692,77,284,526,5384,445,6269,279,1372,315,3793,25,40964,75326,39345,1445,340,13874,19324,1986,2038,18653,264,729,1565,75326,39345,1445,46186,429,47417,279,55129,79683,1372,1667,50262,13,576,729,374,1221,2598,448,279,1196,594,1946,369,279,1372,315,3793,13,576,2038,374,1632,1737,36684,323,5646,1465,11589,369,8318,11127,13,576,729,374,11050,323,4278,369,678,6785,25780,13,576,2038,374,1083,4135,311,1349,323,3535],
66
+ "free_min_top2_margin": 0.067368,
67
+ "prompt_seconds": 0.689,
68
+ "gen_seconds": 25.640,
69
+ "free_match": true,
70
+ "first_divergence": -1,
71
+ "forced_mismatch_count": 0,
72
+ "forced_mismatches": []
73
+ },
74
+ {
75
+ "prompt_ids": [785,2326,6872,315,29845,78911,1584,429],
76
+ "free_ids": [4802,374,1585,2771,11,429,279,5106,315,8628,8317,374,504,1550,9315,311,3347,9315,11,323,429,279,2086,2329,315,29845,78911,374,429,47502,12703,13,220,576,1156,2329,315,29845,78911,374,279,2329,315,28611,315,4802,13,220,576,2086,2329,315,29845,78911,374,279,2329,315,7703,47502,13,220,576,4843,2329,315,29845,78911,374,429,279,47502,315,264,1849,646,2581,18472,11,1172,5263,11,323,429,279,47502,315,264,1849,304,28387,55311,374,518,1181,7192,13,220,576,1156,2329,315,29845,78911,374,279,2329,315,28611,315,4802,13,220,576,2086,2329,315,29845,78911,374,279,2329,315,7703,47502,13,220,576],
77
+ "free_min_top2_margin": 0.021589,
78
+ "prompt_seconds": 1.308,
79
+ "gen_seconds": 25.809,
80
+ "free_match": true,
81
+ "first_divergence": -1,
82
+ "forced_mismatch_count": 0,
83
+ "forced_mismatches": []
84
+ },
85
+ {
86
+ "prompt_ids": [12522,5193,264,882,11,304,264,11340,14126,553,279,9396,11],
87
+ "free_ids": [1052,12163,264,3908,3743,6941,444,10524,13,2932,572,3881,369,1059,40228,323,2948,369,279,17951,13,7209,6556,11,1340,1035,15156,705,1573,38393,11,11435,311,279,11321,311,8844,311,279,16876,323,2666,279,9278,1948,1059,44613,13,6252,6930,7702,572,264,2613,272,994,1380,279,3015,572,19300,323,279,511,988,6436,15186,832,304,279,39020,13,1084,572,1052,429,1340,1035,1477,279,1429,6233,58849,11,1075,13673,511,988,6436,11,9472,315,33638,6660,11,323,1496,264,2421,1560,3435,429,1035,1136,98243,3123,979,1340,2684,2238,3265,382,3966,1899,11,1393,23966,279,272,994,11,444,10524,13686,264,43697,2770,396],
88
+ "free_min_top2_margin": 0.015932,
89
+ "prompt_seconds": 2.088,
90
+ "gen_seconds": 25.561,
91
+ "free_match": true,
92
+ "first_divergence": -1,
93
+ "forced_mismatch_count": 0,
94
+ "forced_mismatches": []
95
+ }
96
+ ],
97
+ "totals": {
98
+ "prompt_tokens": 63,
99
+ "gen_tokens": 1024,
100
+ "prompt_tok_per_s": 5.89,
101
+ "gen_tok_per_s": 4.68,
102
+ "forced_mismatch_total": 0,
103
+ "all_free_match": true
104
+ }
105
+ }
gguf/receipts/gateverdict_8b_fv5.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "variant": "fv5",
4
+ "verdict": "PASS",
5
+ "all_free_match": true,
6
+ "forced_mismatch_total": 0,
7
+ "gen_tok_per_s": 4.68,
8
+ "prompt_tok_per_s": 5.89,
9
+ "flips": [],
10
+ "unexplained": 0,
11
+ "gguf_sha256": "1c13a34360b2531d28821fb6aee41708f99a040215a2522af37213c6c8c87211",
12
+ "ref_trajectory_sha256": "96041dbdd766f1536303f5a8bd7577bd60ea8412008f525f2e8f9d3499980638",
13
+ "threads": 16,
14
+ "cpu": "unknown",
15
+ "wall_s": 463.4,
16
+ "ts": "2026-07-27T15:39:37Z"
17
+ }
gguf/receipts/gguf_spec_quickstart_k16.json ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "receipt": "llama.cpp NATIVE speculative decoding (its own sampling-based acceptance, NOT our bit-gated certification) with fermion-fv5 fork + banked fv5 GGUFs: 8B-wbase target, 0.6B-base draft",
3
+ "fork_head": "e07b3ec135c1f90c64584e0ea2dc983e47094ab9",
4
+ "draft_flag_used": [
5
+ "--spec-draft-n-max",
6
+ "16"
7
+ ],
8
+ "target": "/gguf_ro/gguf/neutrino-8b-wbase-fv5.gguf",
9
+ "draft": "/gguf_ro/gguf/neutrino-0.6b-base-fv5.gguf",
10
+ "threads": 16,
11
+ "cpu": "unknown",
12
+ "nproc": 32,
13
+ "results": {
14
+ "facts": {
15
+ "prompt": "The three laws of thermodynamics state that",
16
+ "n_tok": 128,
17
+ "baseline": {
18
+ "prompt_ms": 997.53,
19
+ "prompt_n": 8,
20
+ "prompt_tok_s": 8.02,
21
+ "eval_ms": 19152.78,
22
+ "eval_n": 127,
23
+ "eval_tok_s": 6.63,
24
+ "rc": 0
25
+ },
26
+ "baseline_text_head": "energy is conserved, that the entropy of an isolated system can never decrease, and that the entropy of an isolated system can never increase. The first law is ",
27
+ "spec": {
28
+ "prompt_ms": 58409.31,
29
+ "prompt_n": 484,
30
+ "prompt_tok_s": 8.29,
31
+ "encoded_ms_tok_s": {
32
+ "tokens": 8,
33
+ "seconds": 1.082,
34
+ "tok_s": 7.392
35
+ },
36
+ "decoded_ms_tok_s": {
37
+ "tokens": 134,
38
+ "seconds": 68.869,
39
+ "tok_s": 1.946
40
+ },
41
+ "n_draft": 16,
42
+ "n_predict": 134,
43
+ "n_drafted": 448,
44
+ "n_accept": 105,
45
+ "accept_pct": 23.438,
46
+ "rc": 0,
47
+ "draft_flag": [
48
+ "--spec-draft-n-max",
49
+ "16"
50
+ ]
51
+ },
52
+ "spec_tail": "The three laws of thermodynamics state that energy is conserved, that the entropy of an isolated system can never decrease, and that the entropy of an isolated system can never increase. The first law is the conservation of energy, which states that energy cannot be created or destroyed, only transformed. The second law states that the entropy of an isolated system can never decrease. The third law states that the entropy of an isolated system can never increase. The second law is also known as the law of increasing entropy. The third law is also known as the law of zero entropy. The second law is also known as the law of increasing entropy. The third law is also known as the law of zero entropy. The second\n\n\n\n\n0.00.623.260 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.02.775.851 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n1.13.679.397 I encoded 8 tokens in 1.082 seconds, speed: 7.392 t/s\n1.13.679.399 I decoded 134 tokens in 68.869 seconds, speed: 1.946 t/s\n1.13.679.399 I \n1.13.679.400 I n_draft = 16\n1.13.679.400 I n_predict = 134\n1.13.679.408 I n_drafted = 448\n1.13.679.408 I n_accept = 105\n1.13.679.409 I accept = 23.438%\n1.13.679.409 I \n1.13.679.409 I draft:\n\n1.13.679.424 I \n1.13.679.424 I target:\n\n1.13.679.431 I common_perf_print: sampling time = 29.13 ms\n1.13.679.431 I common_perf_print: samplers time = 7.43 ms / 134 tokens\n1.13.679.433 I common_perf_print: load time = 1559.85 ms\n1.13.679.434 I common_perf_print: prompt eval time = 58409.31 ms / 484 tokens ( 120.68 ms per token, 8.29 tokens per second)\n1.13.679.435 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n1.13.679.435 I common_perf_print: total time = 71586.90 ms / 485 tokens\n1.13.679.436 I common_perf_print: unaccounted time = 13148.46 ms / 18.4 % (total - sampling - prompt eval - eval) / (total)\n1.13.679.436 I common_perf_print: graphs reused = 27",
53
+ "speedup": 0.294
54
+ },
55
+ "prose": {
56
+ "prompt": "Once upon a time, in a quiet village by the sea,",
57
+ "n_tok": 128,
58
+ "baseline": {
59
+ "prompt_ms": 1553.17,
60
+ "prompt_n": 13,
61
+ "prompt_tok_s": 8.37,
62
+ "eval_ms": 19023.4,
63
+ "eval_n": 127,
64
+ "eval_tok_s": 6.68,
65
+ "rc": 0
66
+ },
67
+ "baseline_text_head": "there lived a young girl named Lila. She was known for her kindness and curiosity. One day, while exploring the forest near her home, she stumbled upon a hidden",
68
+ "spec": {
69
+ "prompt_ms": 123089.45,
70
+ "prompt_n": 999,
71
+ "prompt_tok_s": 8.12,
72
+ "encoded_ms_tok_s": {
73
+ "tokens": 13,
74
+ "seconds": 1.699,
75
+ "tok_s": 7.653
76
+ },
77
+ "decoded_ms_tok_s": {
78
+ "tokens": 131,
79
+ "seconds": 143.874,
80
+ "tok_s": 0.911
81
+ },
82
+ "n_draft": 16,
83
+ "n_predict": 131,
84
+ "n_drafted": 928,
85
+ "n_accept": 72,
86
+ "accept_pct": 7.759,
87
+ "rc": 0,
88
+ "draft_flag": [
89
+ "--spec-draft-n-max",
90
+ "16"
91
+ ]
92
+ },
93
+ "spec_tail": "Once upon a time, in a quiet village by the sea, there lived a young girl named Lila. She was known for her kindness and curiosity. One day, while exploring the forest near her home, she stumbled upon a hidden cave. Inside, she found a glowing orb that seemed to pulse with a soft light. Intrigued, she picked it up and noticed that it was not just a regular rock. It was a crystal that emitted a faint light. As she held it closer, she noticed that the light was not just a natural glow but something more. It was as if the crystal was alive, responding to her touch. \n\nLila was fascinated and decided to take the crystal home. She placed\n\n\n\n\n0.00.585.445 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.02.549.802 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n2.29.032.790 I encoded 13 tokens in 1.699 seconds, speed: 7.653 t/s\n2.29.032.794 I decoded 131 tokens in 143.874 seconds, speed: 0.911 t/s\n2.29.032.794 I \n2.29.032.802 I n_draft = 16\n2.29.032.802 I n_predict = 131\n2.29.032.803 I n_drafted = 928\n2.29.032.803 I n_accept = 72\n2.29.032.804 I accept = 7.759%\n2.29.032.804 I \n2.29.032.804 I draft:\n\n2.29.032.822 I \n2.29.032.822 I target:\n\n2.29.032.829 I common_perf_print: sampling time = 32.72 ms\n2.29.032.830 I common_perf_print: samplers time = 7.62 ms / 131 tokens\n2.29.032.832 I common_perf_print: load time = 1519.90 ms\n2.29.032.833 I common_perf_print: prompt eval time = 123089.45 ms / 999 tokens ( 123.21 ms per token, 8.12 tokens per second)\n2.29.032.834 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n2.29.032.834 I common_perf_print: total time = 147009.52 ms / 1000 tokens\n2.29.032.837 I common_perf_print: unaccounted time = 23887.34 ms / 16.2 % (total - sampling - prompt eval - eval) / (total)\n2.29.032.838 I common_perf_print: graphs reused = 57",
94
+ "speedup": 0.136
95
+ },
96
+ "code": {
97
+ "prompt": "def fibonacci(n):",
98
+ "n_tok": 128,
99
+ "baseline": {
100
+ "prompt_ms": 511.22,
101
+ "prompt_n": 4,
102
+ "prompt_tok_s": 7.82,
103
+ "eval_ms": 19258.57,
104
+ "eval_n": 127,
105
+ "eval_tok_s": 6.59,
106
+ "rc": 0
107
+ },
108
+ "baseline_text_head": "if n < 0:\n return 0\n if n == 0:\n return 0\n if n == 1:\n return 1\n return fibonacci(n-1) + fibonacci(n-2)\n\ndef fibonacci(n):\n # b",
109
+ "spec": {
110
+ "prompt_ms": 74903.85,
111
+ "prompt_n": 616,
112
+ "prompt_tok_s": 8.22,
113
+ "encoded_ms_tok_s": {
114
+ "tokens": 4,
115
+ "seconds": 0.598,
116
+ "tok_s": 6.693
117
+ },
118
+ "decoded_ms_tok_s": {
119
+ "tokens": 132,
120
+ "seconds": 88.867,
121
+ "tok_s": 1.485
122
+ },
123
+ "n_draft": 16,
124
+ "n_predict": 132,
125
+ "n_drafted": 576,
126
+ "n_accept": 95,
127
+ "accept_pct": 16.493,
128
+ "rc": 0,
129
+ "draft_flag": [
130
+ "--spec-draft-n-max",
131
+ "16"
132
+ ]
133
+ },
134
+ "spec_tail": " # base case\n if n == 0:\n return 0\n if n == 1:\n return 1\n # recursive case\n return fibonacci(n-1) + fibonacci(n-2)\n\n# test cases\nprint(fibonacci(0)) # 0\nprint(fibonacci(1)) # 1\nprint(fib\n\n\n\n\n0.00.572.699 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.02.535.311 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n1.32.870.731 I encoded 4 tokens in 0.598 seconds, speed: 6.693 t/s\n1.32.870.732 I decoded 132 tokens in 88.867 seconds, speed: 1.485 t/s\n1.32.870.733 I \n1.32.870.733 I n_draft = 16\n1.32.870.733 I n_predict = 132\n1.32.870.734 I n_drafted = 576\n1.32.870.734 I n_accept = 95\n1.32.870.734 I accept = 16.493%\n1.32.870.734 I \n1.32.870.734 I draft:\n\n1.32.870.747 I \n1.32.870.754 I target:\n\n1.32.870.798 I common_perf_print: sampling time = 30.78 ms\n1.32.870.801 I common_perf_print: samplers time = 6.95 ms / 132 tokens\n1.32.870.807 I common_perf_print: load time = 1545.72 ms\n1.32.870.809 I common_perf_print: prompt eval time = 74903.85 ms / 616 tokens ( 121.60 ms per token, 8.22 tokens per second)\n1.32.870.810 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n1.32.870.811 I common_perf_print: total time = 90831.76 ms / 617 tokens\n1.32.870.811 I common_perf_print: unaccounted time = 15897.13 ms / 17.5 % (total - sampling - prompt eval - eval) / (total)\n1.32.870.812 I common_perf_print: graphs reused = 35",
135
+ "speedup": 0.225
136
+ }
137
+ },
138
+ "wall_s": 449.6,
139
+ "ts": "2026-07-25T16:44:01Z",
140
+ "_annotation_20260726": "HONESTY NOTE (successor session): this quickstart was measured against the RETIRED W-baseline cut (neutrino-8b-wbase-fv5.gguf). The published pack is the s50 product cut (210195d7...). llama.cpp-native speculation was an honest NULL either way (0.14-0.66x); numbers kept for reference only, not re-run (README already labels the section EXPERIMENTAL)."
141
+ }
gguf/receipts/gguf_spec_quickstart_k4.json ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "receipt": "llama.cpp NATIVE speculative decoding (its own sampling-based acceptance, NOT our bit-gated certification) with fermion-fv5 fork + banked fv5 GGUFs: 8B-wbase target, 0.6B-base draft",
3
+ "fork_head": "e07b3ec135c1f90c64584e0ea2dc983e47094ab9",
4
+ "draft_flag_used": [
5
+ "--spec-draft-n-max",
6
+ "4"
7
+ ],
8
+ "target": "/gguf_ro/gguf/neutrino-8b-wbase-fv5.gguf",
9
+ "draft": "/gguf_ro/gguf/neutrino-0.6b-base-fv5.gguf",
10
+ "threads": 16,
11
+ "cpu": "unknown",
12
+ "nproc": 32,
13
+ "results": {
14
+ "facts": {
15
+ "prompt": "The three laws of thermodynamics state that",
16
+ "n_tok": 128,
17
+ "baseline": {
18
+ "prompt_ms": 1245.64,
19
+ "prompt_n": 8,
20
+ "prompt_tok_s": 6.42,
21
+ "eval_ms": 23778.72,
22
+ "eval_n": 127,
23
+ "eval_tok_s": 5.34,
24
+ "rc": 0
25
+ },
26
+ "baseline_text_head": "energy is conserved, that the entropy of an isolated system can never decrease, and that the entropy of an isolated system can never increase. The first law is ",
27
+ "spec": {
28
+ "prompt_ms": 32972.91,
29
+ "prompt_n": 208,
30
+ "prompt_tok_s": 6.31,
31
+ "encoded_ms_tok_s": {
32
+ "tokens": 8,
33
+ "seconds": 1.358,
34
+ "tok_s": 5.892
35
+ },
36
+ "decoded_ms_tok_s": {
37
+ "tokens": 132,
38
+ "seconds": 37.378,
39
+ "tok_s": 3.531
40
+ },
41
+ "n_draft": 4,
42
+ "n_predict": 132,
43
+ "n_drafted": 160,
44
+ "n_accept": 91,
45
+ "accept_pct": 56.875,
46
+ "rc": 0,
47
+ "draft_flag": [
48
+ "--spec-draft-n-max",
49
+ "4"
50
+ ]
51
+ },
52
+ "spec_tail": "The three laws of thermodynamics state that energy is conserved, that the entropy of an isolated system can never decrease, and that the entropy of an isolated system can never increase. The first law is the conservation of energy, which states that energy cannot be created or destroyed, only transformed. The second law states that the entropy of an isolated system can never decrease. The third law states that the entropy of an isolated system can never increase. The second law is also known as the law of increasing entropy. The third law is also known as the law of zero entropy. The second law is also known as the law of increasing entropy. The third law is also known as the law of zero entropy.\n\n\n\n\n0.00.771.178 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.03.414.343 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.43.601.940 I encoded 8 tokens in 1.358 seconds, speed: 5.892 t/s\n0.43.601.942 I decoded 132 tokens in 37.378 seconds, speed: 3.531 t/s\n0.43.601.942 I \n0.43.601.943 I n_draft = 4\n0.43.601.943 I n_predict = 132\n0.43.601.943 I n_drafted = 160\n0.43.601.943 I n_accept = 91\n0.43.601.954 I accept = 56.875%\n0.43.601.954 I \n0.43.601.954 I draft:\n\n0.43.601.972 I \n0.43.601.972 I target:\n\n0.43.601.979 I common_perf_print: sampling time = 53.66 ms\n0.43.601.980 I common_perf_print: samplers time = 10.54 ms / 132 tokens\n0.43.601.983 I common_perf_print: load time = 2121.14 ms\n0.43.601.984 I common_perf_print: prompt eval time = 32972.91 ms / 208 tokens ( 158.52 ms per token, 6.31 tokens per second)\n0.43.601.985 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n0.43.601.985 I common_perf_print: total time = 40811.34 ms / 209 tokens\n0.43.601.986 I common_perf_print: unaccounted time = 7784.78 ms / 19.1 % (total - sampling - prompt eval - eval) / (total)\n0.43.601.987 I common_perf_print: graphs reused = 39",
53
+ "speedup": 0.661
54
+ },
55
+ "prose": {
56
+ "prompt": "Once upon a time, in a quiet village by the sea,",
57
+ "n_tok": 128,
58
+ "baseline": {
59
+ "prompt_ms": 1974.15,
60
+ "prompt_n": 13,
61
+ "prompt_tok_s": 6.59,
62
+ "eval_ms": 23545.97,
63
+ "eval_n": 127,
64
+ "eval_tok_s": 5.39,
65
+ "rc": 0
66
+ },
67
+ "baseline_text_head": "there lived a young girl named Lila. She was known for her kindness and curiosity. One day, while exploring the forest near her home, she stumbled upon a hidden",
68
+ "spec": {
69
+ "prompt_ms": 49207.44,
70
+ "prompt_n": 308,
71
+ "prompt_tok_s": 6.26,
72
+ "encoded_ms_tok_s": {
73
+ "tokens": 13,
74
+ "seconds": 2.134,
75
+ "tok_s": 6.093
76
+ },
77
+ "decoded_ms_tok_s": {
78
+ "tokens": 131,
79
+ "seconds": 55.128,
80
+ "tok_s": 2.376
81
+ },
82
+ "n_draft": 4,
83
+ "n_predict": 131,
84
+ "n_drafted": 236,
85
+ "n_accept": 71,
86
+ "accept_pct": 30.085,
87
+ "rc": 0,
88
+ "draft_flag": [
89
+ "--spec-draft-n-max",
90
+ "4"
91
+ ]
92
+ },
93
+ "spec_tail": "Once upon a time, in a quiet village by the sea, there lived a young girl named Lila. She was known for her kindness and curiosity. One day, while exploring the forest near her home, she stumbled upon a hidden cave. Inside, she found a glowing orb that seemed to pulse with a soft light. Intrigued, she picked it up and noticed that it was not just a regular rock. It was a crystal that emitted a faint light. As she held it closer, she noticed that the light was not just a natural glow but something more. It was as if the crystal was alive, responding to her touch. \n\nLila was fascinated and decided to take the crystal home. She placed\n\n\n\n\n0.00.762.994 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.03.368.212 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n1.01.817.657 I encoded 13 tokens in 2.134 seconds, speed: 6.093 t/s\n1.01.817.697 I decoded 131 tokens in 55.128 seconds, speed: 2.376 t/s\n1.01.817.697 I \n1.01.817.698 I n_draft = 4\n1.01.817.698 I n_predict = 131\n1.01.817.698 I n_drafted = 236\n1.01.817.698 I n_accept = 71\n1.01.817.699 I accept = 30.085%\n1.01.817.699 I \n1.01.817.699 I draft:\n\n1.01.817.715 I \n1.01.817.715 I target:\n\n1.01.817.723 I common_perf_print: sampling time = 54.68 ms\n1.01.817.723 I common_perf_print: samplers time = 10.39 ms / 131 tokens\n1.01.817.725 I common_perf_print: load time = 2083.05 ms\n1.01.817.726 I common_perf_print: prompt eval time = 49207.44 ms / 308 tokens ( 159.76 ms per token, 6.26 tokens per second)\n1.01.817.727 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n1.01.817.728 I common_perf_print: total time = 59095.53 ms / 309 tokens\n1.01.817.729 I common_perf_print: unaccounted time = 9833.40 ms / 16.6 % (total - sampling - prompt eval - eval) / (total)\n1.01.817.729 I common_perf_print: graphs reused = 58",
94
+ "speedup": 0.441
95
+ },
96
+ "code": {
97
+ "prompt": "def fibonacci(n):",
98
+ "n_tok": 128,
99
+ "baseline": {
100
+ "prompt_ms": 639.29,
101
+ "prompt_n": 4,
102
+ "prompt_tok_s": 6.26,
103
+ "eval_ms": 23422.95,
104
+ "eval_n": 127,
105
+ "eval_tok_s": 5.42,
106
+ "rc": 0
107
+ },
108
+ "baseline_text_head": "if n < 0:\n return 0\n if n == 0:\n return 0\n if n == 1:\n return 1\n return fibonacci(n-1) + fibonacci(n-2)\n\ndef fibonacci(n):\n # b",
109
+ "spec": {
110
+ "prompt_ms": 36454.87,
111
+ "prompt_n": 229,
112
+ "prompt_tok_s": 6.28,
113
+ "encoded_ms_tok_s": {
114
+ "tokens": 4,
115
+ "seconds": 0.728,
116
+ "tok_s": 5.494
117
+ },
118
+ "decoded_ms_tok_s": {
119
+ "tokens": 130,
120
+ "seconds": 42.11,
121
+ "tok_s": 3.087
122
+ },
123
+ "n_draft": 4,
124
+ "n_predict": 130,
125
+ "n_drafted": 180,
126
+ "n_accept": 84,
127
+ "accept_pct": 46.667,
128
+ "rc": 0,
129
+ "draft_flag": [
130
+ "--spec-draft-n-max",
131
+ "4"
132
+ ]
133
+ },
134
+ "spec_tail": " # base case\n if n == 0:\n return 0\n if n == 1:\n return 1\n # recursive case\n return fibonacci(n-1) + fibonacci(n-2)\n\n# test cases\nprint(fibonacci(0)) # 0\nprint(fibonacci(1)) # 1\nprint\n\n\n\n\n0.00.784.120 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.03.369.496 W load: control-looking token: 128247 '</s>' was not control-type; this is probably a bug in the model. its type will be overridden\n0.47.355.247 I encoded 4 tokens in 0.728 seconds, speed: 5.494 t/s\n0.47.355.249 I decoded 130 tokens in 42.110 seconds, speed: 3.087 t/s\n0.47.355.249 I \n0.47.355.250 I n_draft = 4\n0.47.355.250 I n_predict = 130\n0.47.355.250 I n_drafted = 180\n0.47.355.250 I n_accept = 84\n0.47.355.251 I accept = 46.667%\n0.47.355.251 I \n0.47.355.251 I draft:\n\n0.47.355.269 I \n0.47.355.269 I target:\n\n0.47.355.276 I common_perf_print: sampling time = 51.98 ms\n0.47.355.277 I common_perf_print: samplers time = 10.86 ms / 130 tokens\n0.47.355.279 I common_perf_print: load time = 2064.72 ms\n0.47.355.280 I common_perf_print: prompt eval time = 36454.87 ms / 229 tokens ( 159.19 ms per token, 6.28 tokens per second)\n0.47.355.281 I common_perf_print: eval time = 0.00 ms / 1 runs ( 0.00 ms per token, inf tokens per second)\n0.47.355.282 I common_perf_print: total time = 44627.08 ms / 230 tokens\n0.47.355.283 I common_perf_print: unaccounted time = 8120.24 ms / 18.2 % (total - sampling - prompt eval - eval) / (total)\n0.47.355.283 I common_perf_print: graphs reused = 44",
135
+ "speedup": 0.57
136
+ }
137
+ },
138
+ "wall_s": 324.4,
139
+ "ts": "2026-07-25T16:50:05Z",
140
+ "_annotation_20260726": "HONESTY NOTE (successor session): this quickstart was measured against the RETIRED W-baseline cut (neutrino-8b-wbase-fv5.gguf). The published pack is the s50 product cut (210195d7...). llama.cpp-native speculation was an honest NULL either way (0.14-0.66x); numbers kept for reference only, not re-run (README already labels the section EXPERIMENTAL)."
141
+ }
gguf/receipts/ref_8b.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "dtype": "float32",
4
+ "steps": 128,
5
+ "trajectory_sha256": "96041dbdd766f1536303f5a8bd7577bd60ea8412008f525f2e8f9d3499980638",
6
+ "continuity_vs_banked_fp16": [
7
+ {
8
+ "prompt": 0,
9
+ "fp16_match": false,
10
+ "first_divergence": 0
11
+ },
12
+ {
13
+ "prompt": 1,
14
+ "fp16_match": false,
15
+ "first_divergence": 1
16
+ },
17
+ {
18
+ "prompt": 2,
19
+ "fp16_match": false,
20
+ "first_divergence": 1
21
+ }
22
+ ],
23
+ "wall_s": 1701.4,
24
+ "cpu": "unknown",
25
+ "ts": "2026-07-27T15:26:19Z"
26
+ }
gguf/receipts_ifcs50_retired/bench_8b.json ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "bench": [
4
+ {
5
+ "build_commit": "e07b3ec",
6
+ "build_number": 2,
7
+ "cpu_info": "unknown",
8
+ "gpu_info": "",
9
+ "backends": "CPU",
10
+ "model_filename": "/vol/gguf/neutrino-8b-ifc50-fv5.gguf",
11
+ "model_type": "qwen3 8B FV5 - 3.25 bpw Fermion five-value ternary",
12
+ "model_size": 4087058432,
13
+ "model_n_params": 8190735360,
14
+ "n_batch": 2048,
15
+ "n_ubatch": 512,
16
+ "n_threads": 16,
17
+ "cpu_mask": "0x0",
18
+ "cpu_strict": false,
19
+ "poll": 50,
20
+ "type_k": "f16",
21
+ "type_v": "f16",
22
+ "n_gpu_layers": -1,
23
+ "n_cpu_moe": 0,
24
+ "split_mode": "layer",
25
+ "main_gpu": 0,
26
+ "no_kv_offload": false,
27
+ "flash_attn": -1,
28
+ "devices": "auto",
29
+ "tensor_split": "0.00",
30
+ "tensor_buft_overrides": "none",
31
+ "load_mode": "mmap",
32
+ "embeddings": false,
33
+ "no_op_offload": 0,
34
+ "no_host": false,
35
+ "fit_target": 0,
36
+ "fit_min_ctx": 0,
37
+ "n_prompt": 512,
38
+ "n_gen": 0,
39
+ "n_depth": 0,
40
+ "test_time": "2026-07-26T18:21:45Z",
41
+ "avg_ns": 79011453606,
42
+ "stddev_ns": 10228708,
43
+ "avg_ts": 6.480073,
44
+ "stddev_ts": 0.000839,
45
+ "samples_ns": [
46
+ 79004220817,
47
+ 79018686395
48
+ ],
49
+ "samples_ts": [
50
+ 6.48067,
51
+ 6.47948
52
+ ]
53
+ },
54
+ {
55
+ "build_commit": "e07b3ec",
56
+ "build_number": 2,
57
+ "cpu_info": "unknown",
58
+ "gpu_info": "",
59
+ "backends": "CPU",
60
+ "model_filename": "/vol/gguf/neutrino-8b-ifc50-fv5.gguf",
61
+ "model_type": "qwen3 8B FV5 - 3.25 bpw Fermion five-value ternary",
62
+ "model_size": 4087058432,
63
+ "model_n_params": 8190735360,
64
+ "n_batch": 2048,
65
+ "n_ubatch": 512,
66
+ "n_threads": 16,
67
+ "cpu_mask": "0x0",
68
+ "cpu_strict": false,
69
+ "poll": 50,
70
+ "type_k": "f16",
71
+ "type_v": "f16",
72
+ "n_gpu_layers": -1,
73
+ "n_cpu_moe": 0,
74
+ "split_mode": "layer",
75
+ "main_gpu": 0,
76
+ "no_kv_offload": false,
77
+ "flash_attn": -1,
78
+ "devices": "auto",
79
+ "tensor_split": "0.00",
80
+ "tensor_buft_overrides": "none",
81
+ "load_mode": "mmap",
82
+ "embeddings": false,
83
+ "no_op_offload": 0,
84
+ "no_host": false,
85
+ "fit_target": 0,
86
+ "fit_min_ctx": 0,
87
+ "n_prompt": 0,
88
+ "n_gen": 128,
89
+ "n_depth": 0,
90
+ "test_time": "2026-07-26T18:25:42Z",
91
+ "avg_ns": 25045562535,
92
+ "stddev_ns": 103809881,
93
+ "avg_ts": 5.11073,
94
+ "stddev_ts": 0.021183,
95
+ "samples_ns": [
96
+ 25118967206,
97
+ 24972157864
98
+ ],
99
+ "samples_ts": [
100
+ 5.09575,
101
+ 5.12571
102
+ ]
103
+ }
104
+ ],
105
+ "smoke_rc": 0,
106
+ "smoke_text": "Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France is Paris. The capital of France",
107
+ "threads": 16,
108
+ "cpu": "unknown",
109
+ "nproc": 32,
110
+ "wall_s": 301.3,
111
+ "ts": "2026-07-26T18:26:46Z"
112
+ }
gguf/receipts_ifcs50_retired/convert_8b.json ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "status": "CONVERT_PASS",
3
+ "container": {
4
+ "name": "neutrino-8b_v4.bin",
5
+ "sha256": "39787a2e15dcffa20135c7c1bb1b8ed55c8632263f8457f3395f55d52007a3be"
6
+ },
7
+ "f32_gguf": {
8
+ "name": "neutrino-8b-ifc50-f32.gguf",
9
+ "bytes": 32768897920
10
+ },
11
+ "out": {
12
+ "name": "neutrino-8b-ifc50-fv5.gguf",
13
+ "bytes": 4093015136,
14
+ "sha256": "7dda995047dc70540d2d83f2c464dd118fff62eef1aab36ae71ceb3cc972e5d0"
15
+ },
16
+ "tensors": {
17
+ "fv5": 252,
18
+ "fv5b": 2,
19
+ "f32": 145,
20
+ "total": 399,
21
+ "bitwise_checked": 399
22
+ },
23
+ "tied_output_omitted": false,
24
+ "quantized_params": 8190427136,
25
+ "effective_bpw_quantized": 3.9979,
26
+ "header": {
27
+ "magic": 1129599572,
28
+ "version": 4,
29
+ "arch": 3,
30
+ "n_layers": 36,
31
+ "hidden": 4096,
32
+ "n_heads": 32,
33
+ "n_kv_heads": 8,
34
+ "head_dim": 128,
35
+ "rotary_dims": 128,
36
+ "intermediate": 12288,
37
+ "vocab": 151936,
38
+ "max_pos": 40960,
39
+ "rms_norm_eps": 9.999999974752427e-07,
40
+ "rope_theta": 1000000.0,
41
+ "eok": 2
42
+ },
43
+ "wall_seconds": 177.3
44
+ }
gguf/receipts_ifcs50_retired/gateverdict_8b_fv5.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "variant": "fv5",
4
+ "verdict": "PASS",
5
+ "all_free_match": true,
6
+ "forced_mismatch_total": 0,
7
+ "gen_tok_per_s": 5.16,
8
+ "prompt_tok_per_s": 6.08,
9
+ "flips": [],
10
+ "unexplained": 0,
11
+ "gguf_sha256": "7dda995047dc70540d2d83f2c464dd118fff62eef1aab36ae71ceb3cc972e5d0",
12
+ "ref_trajectory_sha256": "3568b5a38cc72ec8b4cb9b61eba729b60a538647fad3ae455151b7c7a0620597",
13
+ "threads": 16,
14
+ "cpu": "unknown",
15
+ "wall_s": 422.5,
16
+ "ts": "2026-07-26T18:21:43Z"
17
+ }
gguf/receipts_ifcs50_retired/ref_8b.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "8b",
3
+ "dtype": "float32",
4
+ "steps": 128,
5
+ "trajectory_sha256": "3568b5a38cc72ec8b4cb9b61eba729b60a538647fad3ae455151b7c7a0620597",
6
+ "continuity_vs_banked_fp16": [
7
+ {
8
+ "prompt": 0,
9
+ "fp16_match": false,
10
+ "first_divergence": 0
11
+ },
12
+ {
13
+ "prompt": 1,
14
+ "fp16_match": false,
15
+ "first_divergence": 0
16
+ },
17
+ {
18
+ "prompt": 2,
19
+ "fp16_match": false,
20
+ "first_divergence": 0
21
+ }
22
+ ],
23
+ "wall_s": 1756.0,
24
+ "cpu": "unknown",
25
+ "ts": "2026-07-26T18:08:52Z"
26
+ }
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
mlx/MANIFEST.json ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "note": "MLX pack provenance: file -> bytes/sha256/source. Weights entry is declarative (not duplicated in this subfolder).",
3
+ "generated_utc": "2026-07-25T16:52:04Z",
4
+ "weights_container": {
5
+ "file": "neutrino-8b_v4.bin",
6
+ "status": "GATED ON THE RELEASE CONTAINER (cons3-B25 product bake, FOUNDER-AUTHORISED PROMOTE 2026-07-27): pathgate + refgate both PASS 2026-07-27 (fused-vs-mxops 32-token greedy IDENTICAL; TF rel max 0.000%, argmax 9/9, greedy 24/24, 0 flips) \u2014 receipts pathgate_8b_cons3b25.json / refgate_8b_cons3b25.json.",
7
+ "gated_on_file": "neutrino-8b_v4.bin",
8
+ "gated_on_bytes": 3875404812,
9
+ "gated_on_sha256": "016c6f362ad237a8cf4043601efbc1e766574f76ea58691e78fdef51b4155fa0",
10
+ "gated_on_source": "internal lab archive: archive/consolidation_3/export_v4_B25/neutrino-8b_v4.bin",
11
+ "retired_gates": [
12
+ {
13
+ "note": "RETIRED 2026-07-27 \u2014 d25 product bake; the pack was NEVER re-pinned to ifc-s50 even though its gates ran (receipts pathgate_8b_ifc50.json / refgate_8b_ifc50.json stayed in the lane dir and were never copied into the pack). Both are superseded by cons3-B25.",
14
+ "gated_on_sha256": "06af07b5f17c832186c95fce6252691ec84dbbc9320a4d4ce9ce989547c06e9b"
15
+ },
16
+ {
17
+ "note": "RETIRED 2026-07-27 \u2014 ifc-s50 ship brain, gated 2026-07-26 but never published in this manifest; superseded by the cons3-B25 promote.",
18
+ "gated_on_sha256": "39787a2e15dcffa20135c7c1bb1b8ed55c8632263f8457f3395f55d52007a3be"
19
+ },
20
+ {
21
+ "note": "RETIRED 2026-07-26 ~10:45Z \u2014 s50 gate, superseded by d25 (round-2 winner).",
22
+ "gated_on_sha256": "fe85f93214376354f118a83235eeff6bc5d40e112474afacbd3e0310768b08d5"
23
+ },
24
+ {
25
+ "note": "RETIRED 2026-07-26 \u2014 original correctness gate ran on the W-baseline container (identical bytes count, different brain).",
26
+ "gated_on_file": "wbase_v4.bin",
27
+ "gated_on_sha256": "5af0c75b14fbac020324768455ce660a92110f4f71f507635fd160dd0b03f0a1",
28
+ "gated_on_source": "internal lab archive: archive/labarchive/dspec8b/artifacts/wbase_v4.bin"
29
+ }
30
+ ]
31
+ },
32
+ "kernel_provenance": {
33
+ "vendored": "fermion_mlx/trit_kernels.py",
34
+ "repo_file": "kernels/metal/trit_kernels.py",
35
+ "sha256": "c56db6dd5e3743d19c54411f5f0b2dfef135f7805906a62cc4e40f1ee5e80f27",
36
+ "relation": "byte-identical"
37
+ },
38
+ "files": {
39
+ "README.md": {
40
+ "bytes": 5949,
41
+ "sha256": "1ab11a6f6e1a915db38da647ecbc66b524fed9e474b80067c60daf3d89dfaafe",
42
+ "source": "pack file"
43
+ },
44
+ "fermion_mlx/__init__.py": {
45
+ "bytes": 2306,
46
+ "sha256": "55d5e0a1efd92802c28f03ab526a01d4c26493fa5e523c495d2076ff56e4985a",
47
+ "source": "NEW: load()/generate()/chat() API"
48
+ },
49
+ "fermion_mlx/__main__.py": {
50
+ "bytes": 7347,
51
+ "sha256": "87190c0c23c712bfe5b31bc54d2dadd69a971c5d8ff8e66f05ba1fd2dbc1352f",
52
+ "source": "NEW: CLI (gen/chat/gate/cachegate/pathgate/bench) + spec/specgate/specbench modes (spec_paths)"
53
+ },
54
+ "fermion_mlx/arch3.py": {
55
+ "bytes": 7541,
56
+ "sha256": "a831e8a24b2b9a986b393d1c316aac66a3399a082a57176254b2feeae032f8cd",
57
+ "source": "NEW (mlx_packs lane 2026-07-25): fused per-head QK-RMSNorm+rope+KV-append kernel + s8 v3 x-staged int8 unembed"
58
+ },
59
+ "fermion_mlx/container.py": {
60
+ "bytes": 6059,
61
+ "sha256": "c5b352184ba9ddf9a3e424731226cb7dcb9901d3a1516c67d4557953b0101f2f",
62
+ "source": "NEW: TRTC v4 arch-3 reader (layout per scripts/expand_trtc_v4_to_hf.py + kernels/gpu_e2e/runtime8b.py)"
63
+ },
64
+ "fermion_mlx/gates.py": {
65
+ "bytes": 7454,
66
+ "sha256": "faf8634b0bd3afec5c4422decdbd8cc04fbb7f3426f70cdd6626fd6b177aa8b4",
67
+ "source": "NEW: house V1 refgate / cachegate / pathgate"
68
+ },
69
+ "fermion_mlx/mbatch.py": {
70
+ "bytes": 6492,
71
+ "sha256": "c9ec8d37f1cdc0554cda01cefbc435e67525779c79e0dac7fd760e7919e6fdd2",
72
+ "source": "NEW (spec_paths 2026-07-25): M-token batched trit/s8 GEMMs for speculative verify (one weight read per k+1 positions)"
73
+ },
74
+ "fermion_mlx/model.py": {
75
+ "bytes": 12748,
76
+ "sha256": "9488f8194ce8be006ce05dfaf6118353de44005ce54f38f73a0908ccac2749da",
77
+ "source": "NEW: MetalTritQwen3 walker (fused primary path + mx.fast-ops reference twin)"
78
+ },
79
+ "fermion_mlx/spec.py": {
80
+ "bytes": 11207,
81
+ "sha256": "969e946d5d2eeb63d44afa4c62dd2453d87fa14e83b936bc673482b59ee3b745",
82
+ "source": "NEW (spec_paths 2026-07-25): EXPERIMENTAL greedy-exact speculative decoding (0.6B drafts, 8B verifies), exactness-gated"
83
+ },
84
+ "fermion_mlx/trit_kernels.py": {
85
+ "bytes": 36881,
86
+ "sha256": "c56db6dd5e3743d19c54411f5f0b2dfef135f7805906a62cc4e40f1ee5e80f27",
87
+ "source": "VENDORED byte-identical from kernels/metal/trit_kernels.py (repo sha c56db6dd5e3743d19c54411f5f0b2dfef135f7805906a62cc4e40f1ee5e80f27) \u2014 the banked five-value Metal GEMV stack"
88
+ },
89
+ "merges.txt": {
90
+ "bytes": 1671853,
91
+ "sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
92
+ "source": "donor Qwen/Qwen3-8B (same files as the parent tree root)"
93
+ },
94
+ "receipts/bench_8b_wbase_m5_16gb_memcapped.json": {
95
+ "bytes": 599,
96
+ "sha256": "9d3b153ec49dce92846fb9140fc38267ed04a08b5adcda91f2036ce94869b555",
97
+ "source": "gate/bench receipt (mlx_packs lane, 2026-07-25)"
98
+ },
99
+ "receipts/pathgate_8b_wbase.json": {
100
+ "bytes": 612,
101
+ "sha256": "50d6584a9ba2dae87bf502f2e637916a604be72db473fc89977fb8e1aaf3413b",
102
+ "source": "gate/bench receipt (mlx_packs lane, 2026-07-25)"
103
+ },
104
+ "receipts/refgate_8b_wbase.json": {
105
+ "bytes": 1909,
106
+ "sha256": "851da3bb5c093182eb56532711dd9a95123ca44260e53de951b27c8b96eaf7a4",
107
+ "source": "gate/bench receipt (mlx_packs lane, 2026-07-25)"
108
+ },
109
+ "receipts/specbench_8b_0p6b_m5.json": {
110
+ "bytes": 826,
111
+ "sha256": "c36ef09659d8c7690d53c23d065943103a95417890f8defbd01e629d7b5feeca",
112
+ "source": "spec gate/bench receipt (spec_paths lane, 2026-07-25)"
113
+ },
114
+ "receipts/specgate_8b_0p6b.json": {
115
+ "bytes": 705,
116
+ "sha256": "64bcb90fe965835bc78887d873d3cfd0677b76c6d72cda04a84e295efee7c10c",
117
+ "source": "spec gate/bench receipt (spec_paths lane, 2026-07-25)"
118
+ },
119
+ "requirements.txt": {
120
+ "bytes": 191,
121
+ "sha256": "1cf4d1fa581aaa5deae0568f88388c51166bbd69e7a89080b5a53488a7ef39b6",
122
+ "source": "pack file"
123
+ },
124
+ "tokenizer.json": {
125
+ "bytes": 11422654,
126
+ "sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
127
+ "source": "donor Qwen/Qwen3-8B (same files as the parent tree root)"
128
+ },
129
+ "tokenizer_config.json": {
130
+ "bytes": 9732,
131
+ "sha256": "d5d09f07b48c3086c508b30d1c9114bd1189145b74e982a265350c923acd8101",
132
+ "source": "donor Qwen/Qwen3-8B (same files as the parent tree root)"
133
+ },
134
+ "vocab.json": {
135
+ "bytes": 2776833,
136
+ "sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
137
+ "source": "donor Qwen/Qwen3-8B (same files as the parent tree root)"
138
+ }
139
+ },
140
+ "addenda": [
141
+ "2026-07-25 spec_paths: EXPERIMENTAL --spec speculative decoding added (new modules mbatch.py/spec.py + CLI modes + README section). Exactness-gated: specgate 6/6 token-identical vs plain (receipts). Existing gate battery re-verified on the updated package."
142
+ ]
143
+ }
mlx/README.md ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Neutrino-8B — MLX pack (Apple silicon)
2
+
3
+ Run the [Neutrino-8B](../README.md) five-value (sub-2-bit) container
4
+ natively from Python on Apple silicon: MLX + our custom Metal kernels. The
5
+ ~3.61 GiB TRTC v4 arch-3 container is memory-mapped and the five-value
6
+ {0, ±s_lo, ±s_hi} planes stay bit-packed at rest — decode happens inside
7
+ the GEMV kernels; nothing in the decode path is fp16/fp32 weight material.
8
+
9
+ > [!IMPORTANT]
10
+ > **PRODUCT-CONTAINER RE-GATE COMPLETE (2026-07-27).** The final shipping
11
+ > container is the cons3-B25 ship brain (`neutrino-8b_v4.bin`,
12
+ > 3,875,404,812 bytes, sha256 `016c6f36…55fa0`, baked 2026-07-27 from
13
+ > consolidation-3 arm B step 25 — see the parent card). This MLX pack's
14
+ > runtime + kernels are **correctness-gated on that exact container**:
15
+ > both gates were re-run against it on an Apple M5 (16 GB, MLX 0.31.2,
16
+ > 6 GiB cap) and both PASS — receipts `receipts/pathgate_8b_cons3b25.json`
17
+ > and `receipts/refgate_8b_cons3b25.json`. The runtime is geometry-driven
18
+ > (header-dispatched), so the swap involved no code change.
19
+ >
20
+ > RETIRED, do **not** verify against: ifc-s50 `39787a2e…07a3be`, d25
21
+ > `06af07b5…c06e9b`, s50 `fe85f932…768b08d5`, wbase `5af0c75b…03f0a1`.
22
+
23
+ ## Install & run
24
+
25
+ ```bash
26
+ pip install -r requirements.txt # mlx, numpy, transformers
27
+ python -m fermion_mlx --model neutrino-8b_v4.bin --mode chat \
28
+ --tokenizer . --prompt "Why is the sky blue?"
29
+ ```
30
+
31
+ Python API:
32
+
33
+ ```python
34
+ import fermion_mlx
35
+ from transformers import AutoTokenizer
36
+
37
+ model = fermion_mlx.load("neutrino-8b_v4.bin") # ~4.2 GiB unified memory
38
+ tok = AutoTokenizer.from_pretrained(".")
39
+ text, stats = fermion_mlx.chat(model, tok,
40
+ [{"role": "user", "content": "hi"}])
41
+ ```
42
+
43
+ Memory: the loaded model holds ~3.8 GiB of packed planes + int8 embedding
44
+ lanes (untied: separate embed_in and lm_head records) plus the KV cache
45
+ (`maxseq` × 302 KiB/layer fp32). A 16 GB Mac runs it; 8 GB does not.
46
+ `--mem-limit-gb` makes the process fail fast instead of pressuring the
47
+ machine.
48
+
49
+ ## What runs on the GPU
50
+
51
+ Identical stack to the Neutrino-0.6B MLX pack (one geometry-generic
52
+ runtime): five-value 3-bit-planar GEMV kernels (v7 LUT decode) with fused
53
+ RMSNorm/SwiGLU/residual epilogues, the fused arch-3 per-head
54
+ Q/K-RMSNorm + rope + KV-append kernel, int8 embed/unembed lanes with
55
+ threadgroup x-staging, MLX SDPA with block-grown windows, `mx.compile`d
56
+ step.
57
+
58
+ ## Correctness receipts
59
+
60
+ House rule: greedy streams token-identical to the container's own fp32
61
+ expansion reference; near-tie flips at tiny logit margins are the only
62
+ tolerated class, each documented with its margin. Receipts in `receipts/`
63
+ (gated on the **shipping** container `016c6f36…55fa0`, re-run 2026-07-27):
64
+
65
+ | gate | reference | result |
66
+ |---|---|---|
67
+ | V1 refgate | fp32 HF expansion of **this** container (eager attn), teacher-forced 9 pos + 24-token greedy | **PASS — TF rel err 0.000% (max), cos 1.000000, argmax 9/9, greedy 24/24, 0 flips** |
68
+ | pathgate | fused-kernel path vs pure `mx.fast`-ops twin | 32-token greedy IDENTICAL |
69
+
70
+ ## Speed (honest numbers, with venue)
71
+
72
+ - Apple-silicon M-series decode receipt for the 8B: **25.0 tok/s median**
73
+ (128-token greedy × 3 runs, 24.3–25.6, peak MLX memory **3.67 GiB**),
74
+ measured under a strict 6 GiB memory cap on a 16 GB M5 MacBook, macOS,
75
+ MLX 0.31.2, 2026-07-25 (wbase container;
76
+ `receipts/bench_8b_wbase_m5_16gb_memcapped.json`). Decode is
77
+ memory-bandwidth-bound, so this is representative for M5-class machines;
78
+ a fuller M-series story (longer runs, larger windows, bigger-RAM
79
+ machines) lands with the Metal arch-3 port rollout. The CUDA receipts in
80
+ the parent tree (H100: 379 tok/s plain greedy, up to 650+ tok/s with
81
+ certified speculative decoding — **research-harness numbers**, measured
82
+ on the research stack, not the shipped binaries) and the shipped CPU
83
+ `bin/` fermion-run path remain the primary 8B performance story.
84
+
85
+ ## EXPERIMENTAL: speculative decoding with the 0.6B pack as draft
86
+
87
+ The package ships a `--spec` mode where the Neutrino-0.6B pack drafts and
88
+ this 8B verifies, with the house **greedy-exact acceptance rule**: a draft
89
+ token is accepted iff it equals the verifier's argmax at that position, so
90
+ the emitted stream is exactness-gated against plain decoding (receipt:
91
+ `receipts/specgate_8b_0p6b.json` — 3 prompts × {32, 256} tokens, spec vs
92
+ plain **token-identical 6/6**, both models in one process under a 6 GiB
93
+ MLX cap).
94
+
95
+ ```bash
96
+ python -m fermion_mlx --model neutrino-8b_v4.bin \
97
+ --draft-model neutrino-0.6b_v4.bin --mode spec \
98
+ --tokenizer . --prompt "..." --mem-limit-gb 6 # --spec-k 4 default
99
+ ```
100
+
101
+ Honest numbers (M5 MacBook 16 GB, 6 GiB cap, k=4, 512-token greedy × 3
102
+ runs, medians; `receipts/specbench_8b_0p6b_m5.json`):
103
+
104
+ | text type | plain 8B | 8B + 0.6B draft | speedup | acceptance |
105
+ |---|---|---|---|---|
106
+ | facts | 22.0 tok/s | **25.7 tok/s** | **1.17×** | 74% |
107
+ | chat-ish | 17.5 tok/s | 14.7 tok/s | 0.84× | 44% |
108
+ | prose | 17.9 tok/s | 12.2 tok/s | 0.68× | 30% |
109
+
110
+ Read before enabling: the speedup is real only on text where the 0.6B
111
+ drafts well (repetitive/factual continuations). Acceptance is
112
+ text-dependent because the draft is the *base* 0.6B, not a model distilled
113
+ from the 8B; and the current batched-verify kernel costs ~2.8× a single
114
+ step (M-wide mask-convert decode is compute-bound), so break-even needs
115
+ ~2.9 accepted tokens/round. A LUT-based batched decode + adaptive k are
116
+ the known day-2 upgrades. This flag never changes outputs (exactness rule
117
+ above) — worst case it changes speed. Not memory contention: both-model
118
+ peak stayed under 4.3 GiB of the 6 GiB cap.
119
+
120
+ ## Files
121
+
122
+ - `fermion_mlx/` — loader/runtime package (same bytes as the 0.6B pack's).
123
+ - tokenizer files — donor Qwen3-8B (shipped in the parent tree root too).
124
+ - `receipts/` — gate receipts (JSON), kernel-source sha sidecar.
125
+ - `MANIFEST.json` — file-by-file provenance + shas.
126
+
127
+ Weights: not duplicated in this subfolder — the parent `MANIFEST.json`
128
+ governs the container (interim vs final product swap included).
129
+
130
+ ## License
131
+
132
+ Apache-2.0 (this pack's code). Weights per the parent card (Apache-2.0,
133
+ derivative of Qwen/Qwen3-8B). `bin/` EULA unchanged.
mlx/fermion_mlx/__init__.py ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """fermion_mlx — run Fermion Research TRTC v4 arch-3 (Qwen3-family) ternary
2
+ containers on Apple silicon via MLX + custom Metal kernels.
3
+
4
+ import fermion_mlx
5
+ model = fermion_mlx.load("neutrino-0p6b_v4_base.bin")
6
+ text = fermion_mlx.generate(model, tokenizer, "The capital of France is",
7
+ max_tokens=64)
8
+
9
+ The five-value {0, ±s_lo, ±s_hi} weights stay bit-packed at rest and are
10
+ decoded inside the GEMV kernels; nothing is expanded to fp16/fp32 weights.
11
+ Kernel version: env TRIT_METAL_V (default 7 = the banked LUT-decode bodies).
12
+ """
13
+ import os
14
+
15
+ # choose the crown kernel bodies before the kernel module is imported
16
+ os.environ.setdefault("TRIT_METAL_V", "7")
17
+
18
+ from .model import MetalTritQwen3 # noqa: E402
19
+
20
+
21
+ def load(container_path, maxseq=1024, verbose=True):
22
+ """Memory-map a TRTC v4 arch-3 container and build the Metal model."""
23
+ return MetalTritQwen3(container_path, maxseq=maxseq, verbose=verbose)
24
+
25
+
26
+ DEFAULT_EOS = (151645, 151643) # <|im_end|>, <|endoftext|>
27
+
28
+
29
+ def _as_ids(x):
30
+ """Normalise whatever a tokenizer handed back into a flat list of ids.
31
+
32
+ transformers >= 4.56 flipped `apply_chat_template(tokenize=True)` to
33
+ return a BatchEncoding instead of a list of ids (return_dict defaults to
34
+ True). requirements.txt says `transformers>=4.53`, so a fresh install gets
35
+ the new behaviour and the old code fed a BatchEncoding straight into
36
+ `model.prefill`, which died with
37
+ `int() argument must be ... not 'tokenizers.Encoding'`.
38
+ """
39
+ if hasattr(x, "input_ids"):
40
+ x = x.input_ids
41
+ elif isinstance(x, dict) and "input_ids" in x:
42
+ x = x["input_ids"]
43
+ if len(x) and isinstance(x[0], (list, tuple)): # batched -> first row
44
+ x = x[0]
45
+ return [int(t) for t in x]
46
+
47
+
48
+ def _rp_greedy(model, ids, n, rp, eos=None):
49
+ """Greedy decode with the CTRL/HF multiplicative repetition penalty.
50
+
51
+ Byte-for-byte the rule the graded MLX battery used
52
+ (handoffs/results_0p6b_chatv2/scripts/local_mlx_battery_v2.py:30-54,
53
+ `rp_greedy`): the penalty applies to every id SEEN SO FAR in the whole
54
+ context, not to a 256-token window — that is the MLX-side convention and
55
+ it is what the banked MLX numbers on the model card were produced at.
56
+
57
+ This path materialises the logits every step, so it forfeits the fused
58
+ fast path's one-step async lookahead. It is only taken when rp != 1.0;
59
+ `model.generate` (rp = 1.0) is untouched, so the banked tok/s receipts
60
+ still describe the default.
61
+ """
62
+ import time
63
+
64
+ import mlx.core as mx
65
+ import numpy as np
66
+
67
+ eos = set(eos or ())
68
+ mask = model.new_mask()
69
+ x, pos0 = model.prefill(ids, mask)
70
+ seen = set(int(t) for t in ids)
71
+ toks = []
72
+ t0 = time.perf_counter()
73
+ for i in range(n):
74
+ pb = mx.array([pos0 + i], dtype=mx.int32)
75
+ W = model._window(pos0 + i)
76
+ nt, xn, lg = model.step_compiled(x, pb, mask, W)
77
+ lgn = np.array(lg, copy=True)
78
+ idxs = np.fromiter(seen, dtype=np.int64)
79
+ vals = lgn[idxs]
80
+ lgn[idxs] = np.where(vals > 0, vals / rp, vals * rp)
81
+ tsel = int(lgn.argmax())
82
+ toks.append(tsel)
83
+ if tsel in eos:
84
+ break
85
+ x = model.embed(tsel)
86
+ seen.add(tsel)
87
+ return toks, time.perf_counter() - t0
88
+
89
+
90
+ def _decode(model, tokenizer, ids, max_tokens, eos, rep_penalty):
91
+ if rep_penalty and float(rep_penalty) != 1.0:
92
+ toks, dt = _rp_greedy(model, ids, max_tokens, float(rep_penalty),
93
+ eos=eos)
94
+ else:
95
+ toks, dt = model.generate(ids, max_tokens, eos=eos)
96
+ if eos:
97
+ cut = next((i for i, t in enumerate(toks) if t in eos), len(toks))
98
+ toks = toks[:cut]
99
+ text = tokenizer.decode(toks)
100
+ rate = (len(toks) or 1) / dt if dt > 0 else 0.0
101
+ return text, {"tokens": len(toks), "seconds": dt, "tok_s": rate}
102
+
103
+
104
+ def generate(model, tokenizer, prompt, max_tokens=256, stop_on_eos=True,
105
+ rep_penalty=1.0):
106
+ """Greedy completion of a plain-text prompt. Returns (text, stats).
107
+
108
+ `rep_penalty` > 1.0 enables the CTRL-style repetition penalty the graded
109
+ MLX battery ran at (1.05 on the chat SKU); 1.0, the default, is the
110
+ untouched pure-argmax fast path.
111
+ """
112
+ ids = _as_ids(tokenizer(prompt))
113
+ eos = set(DEFAULT_EOS) if stop_on_eos else None
114
+ return _decode(model, tokenizer, ids, max_tokens, eos, rep_penalty)
115
+
116
+
117
+ def chat(model, tokenizer, messages, max_tokens=256, rep_penalty=1.0):
118
+ """Chat completion via the donor chat template (thinking off).
119
+
120
+ `rep_penalty` as in `generate`; the chat SKU's card documents 1.05.
121
+ """
122
+ try:
123
+ raw = tokenizer.apply_chat_template(messages, add_generation_prompt=True,
124
+ enable_thinking=False)
125
+ except TypeError:
126
+ raw = tokenizer.apply_chat_template(messages, add_generation_prompt=True)
127
+ ids = _as_ids(raw)
128
+ return _decode(model, tokenizer, ids, max_tokens, set(DEFAULT_EOS),
129
+ rep_penalty)
mlx/fermion_mlx/__main__.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """fermion_mlx CLI.
3
+
4
+ python -m fermion_mlx --model x.bin --mode gen --prompt "..."
5
+ python -m fermion_mlx --model x.bin --mode chat --prompt "..." --tokenizer DIR
6
+ python -m fermion_mlx --model x.bin --mode chat --prompt "..." --tokenizer DIR \
7
+ --rep-penalty 1.05 # the graded chat-SKU config
8
+ python -m fermion_mlx --model x.bin --mode gate --ref wbase_ref.npz
9
+ python -m fermion_mlx --model x.bin --mode cachegate --cache hf_greedy_cache.json
10
+ python -m fermion_mlx --model x.bin --mode pathgate
11
+ python -m fermion_mlx --model x.bin --mode bench --runs 5 --gen-tokens 512
12
+
13
+ EXPERIMENTAL speculative decoding (0.6B drafts, exactness-gated):
14
+ python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode spec \
15
+ --prompt "..." --tokenizer DIR --mem-limit-gb 6
16
+ python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode specgate \
17
+ --tokenizer DIR --mem-limit-gb 6 --receipt specgate.json
18
+ python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode specbench \
19
+ --tokenizer DIR --mem-limit-gb 6 --receipt specbench.json
20
+
21
+ Gate receipts are written next to --receipt (JSON) when given.
22
+ """
23
+ import argparse
24
+ import json
25
+ import sys
26
+ import time
27
+
28
+ import mlx.core as mx
29
+
30
+ import fermion_mlx
31
+ from fermion_mlx.gates import refgate, cachegate, pathgate
32
+
33
+ HOUSE_PROMPT = "The key idea behind ternary neural networks is"
34
+ # tokenizer-free fallback: Qwen3-tokenized HOUSE_PROMPT (donor-verified ids)
35
+ HOUSE_IDS = [785, 1376, 4522, 4815, 71617, 658, 29728, 14155, 374]
36
+
37
+
38
+ def _tok(args):
39
+ from transformers import AutoTokenizer
40
+ return AutoTokenizer.from_pretrained(args.tokenizer)
41
+
42
+
43
+ def main():
44
+ ap = argparse.ArgumentParser()
45
+ ap.add_argument("--model", required=True)
46
+ ap.add_argument("--mode", default="gen",
47
+ choices=["gen", "chat", "gate", "cachegate", "pathgate",
48
+ "bench", "spec", "specgate", "specbench"])
49
+ ap.add_argument("--draft-model", default="",
50
+ help="draft container for --mode spec* (e.g. the 0.6B pack)")
51
+ ap.add_argument("--spec-k", type=int, default=4,
52
+ help="draft tokens per speculative round")
53
+ ap.add_argument("--prompt", default=HOUSE_PROMPT)
54
+ ap.add_argument("--tokenizer", default="Qwen/Qwen3-0.6B")
55
+ ap.add_argument("--ref", default="")
56
+ ap.add_argument("--cache", default="")
57
+ ap.add_argument("--receipt", default="")
58
+ ap.add_argument("--tie-tol", type=float, default=0.04, dest="tie_tol")
59
+ ap.add_argument("--max-flips", type=int, default=8, dest="max_flips")
60
+ ap.add_argument("--gen-tokens", type=int, default=512)
61
+ ap.add_argument("--runs", type=int, default=5)
62
+ ap.add_argument("--maxseq", type=int, default=1024)
63
+ ap.add_argument("--no-tokenizer", action="store_true",
64
+ help="use the pinned house prompt ids (no transformers)")
65
+ ap.add_argument("--mem-limit-gb", type=float, default=0.0,
66
+ help="hard MLX memory limit; the process fails fast "
67
+ "instead of pressuring the machine (house OOM rule)")
68
+ ap.add_argument("--rep-penalty", type=float, default=1.0,
69
+ dest="rep_penalty",
70
+ help="CTRL-style repetition penalty over the whole "
71
+ "context, the rule the graded MLX battery ran at "
72
+ "(1.05 on the chat SKU). 1.0 (default) = the "
73
+ "untouched pure-argmax fast path.")
74
+ args = ap.parse_args()
75
+
76
+ if args.mem_limit_gb > 0:
77
+ mx.set_memory_limit(int(args.mem_limit_gb * 2**30))
78
+ print(f"[mem] MLX memory limit set to {args.mem_limit_gb:.1f} GiB")
79
+
80
+ model = fermion_mlx.load(args.model, maxseq=args.maxseq)
81
+
82
+ def save(rec):
83
+ if args.receipt:
84
+ json.dump(rec, open(args.receipt, "w"), indent=1)
85
+ print(f"receipt -> {args.receipt}")
86
+
87
+ if args.mode in ("spec", "specgate", "specbench"):
88
+ from fermion_mlx.spec import SpecDecoder, specgate, specbench
89
+ assert args.draft_model, "--mode spec* needs --draft-model"
90
+ draft = fermion_mlx.load(args.draft_model, maxseq=args.maxseq)
91
+ tok = _tok(args)
92
+ if args.mode == "specgate":
93
+ rec = specgate(model, draft, tok, k=args.spec_k,
94
+ receipt=args.receipt or None)
95
+ print(f"[specgate] {rec['verdict']}")
96
+ sys.exit(0 if rec["verdict"] == "PASS" else 1)
97
+ if args.mode == "specbench":
98
+ rec = specbench(model, draft, tok, k=args.spec_k,
99
+ n=args.gen_tokens, runs=args.runs,
100
+ receipt=args.receipt or None)
101
+ for t, v in rec["types"].items():
102
+ print(f"[specbench {t}] plain {v['plain_median_tok_s']} -> "
103
+ f"spec {v['spec_median_tok_s']} tok/s "
104
+ f"({v['speedup']}x, acc {v['acceptance_median']})")
105
+ return
106
+ sd = SpecDecoder(model, draft, k=args.spec_k)
107
+ ids = tok(args.prompt, add_special_tokens=False).input_ids
108
+ toks, dt, st = sd.generate(ids, args.gen_tokens)
109
+ print(tok.decode(toks))
110
+ print(f"[spec: {len(toks)} tok in {dt*1e3:.0f} ms -> "
111
+ f"{len(toks)/dt:.1f} tok/s | acceptance {st['acceptance']:.3f} "
112
+ f"| {st['tokens_per_round']} tok/round]")
113
+ return
114
+
115
+ if args.mode == "gate":
116
+ ok, rec = refgate(model, args.ref, args.tie_tol, args.max_flips)
117
+ rec["mode"], rec["model"] = "refgate", args.model
118
+ save(rec)
119
+ sys.exit(0 if ok else 1)
120
+
121
+ if args.mode == "cachegate":
122
+ ok, rec = cachegate(model, args.model, args.cache, args.tie_tol,
123
+ args.max_flips)
124
+ rec["mode"], rec["model"] = "cachegate", args.model
125
+ save(rec)
126
+ sys.exit(0 if ok else 1)
127
+
128
+ if args.mode == "pathgate":
129
+ ids = (HOUSE_IDS if args.no_tokenizer else
130
+ fermion_mlx._as_ids(_tok(args)(args.prompt)))
131
+ ok, rec = pathgate(model, ids, n=32)
132
+ rec["mode"], rec["model"] = "pathgate", args.model
133
+ save(rec)
134
+ sys.exit(0 if ok else 1)
135
+
136
+ if args.mode == "bench":
137
+ ids = (HOUSE_IDS if args.no_tokenizer else
138
+ fermion_mlx._as_ids(_tok(args)(args.prompt)))
139
+ n = args.gen_tokens
140
+ model.generate(ids, 16) # warmup + kernel JIT
141
+ rates, runs = [], []
142
+ for r in range(args.runs):
143
+ toks, dt = model.generate(ids, n) # no EOS stop: full budget
144
+ rates.append(n / dt)
145
+ runs.append({"run": r, "tokens": n, "seconds": dt, "tok_s": n / dt})
146
+ print(f" run {r}: {n} tok in {dt*1e3:.0f} ms -> {rates[-1]:.1f} tok/s")
147
+ rates.sort()
148
+ med = rates[len(rates) // 2]
149
+ peak = mx.get_peak_memory() / 2**30
150
+ print(f"[bench] median {med:.1f} tok/s over {args.runs} runs "
151
+ f"({n}-token greedy), peak mem {peak:.2f} GiB")
152
+ save({"mode": "bench", "model": args.model, "prompt_ids": ids,
153
+ "gen_tokens": n, "runs": runs, "median_tok_s": med,
154
+ "peak_mem_gib": peak,
155
+ "utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())})
156
+ return
157
+
158
+ tok = _tok(args)
159
+ if args.mode == "gen":
160
+ text, st = fermion_mlx.generate(model, tok, args.prompt,
161
+ max_tokens=args.gen_tokens,
162
+ rep_penalty=args.rep_penalty)
163
+ print(text)
164
+ print(f"[{st['tokens']} tokens in {st['seconds']*1e3:.0f} ms -> "
165
+ f"{st['tok_s']:.1f} tok/s]")
166
+ else:
167
+ text, st = fermion_mlx.chat(model, tok,
168
+ [{"role": "user", "content": args.prompt}],
169
+ max_tokens=args.gen_tokens,
170
+ rep_penalty=args.rep_penalty)
171
+ print(text)
172
+ print(f"[{st['tokens']} tokens in {st['seconds']*1e3:.0f} ms -> "
173
+ f"{st['tok_s']:.1f} tok/s]")
174
+
175
+
176
+ if __name__ == "__main__":
177
+ main()
mlx/fermion_mlx/arch3.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Arch-3 (Qwen3) Metal kernel: fused per-head Q/K-RMSNorm + NeoX rope +
3
+ KV-cache append, one dispatch per decode step.
4
+
5
+ The Qwen3 arch delta vs arch-1 is the per-head RMSNorm applied to the qkv
6
+ GEMV output BEFORE rope (HF order: q_norm/k_norm over head_dim, weights
7
+ shared across heads). This kernel is the arch-3 twin of
8
+ trit_kernels.rope_append: one simdgroup per head; q/k heads compute the
9
+ head's rms via simd_sum then rope the normed values; v heads copy raw
10
+ values into the cache. kc/vc/mask are written in place at position pos
11
+ (stream-serial ordering with the consumer SDPA — the banked rope_append
12
+ pattern; correctness enforced by the e2e gates).
13
+ """
14
+ import mlx.core as mx
15
+
16
+ _CACHE = {}
17
+ SG_PER_TG = 8
18
+
19
+
20
+ def _s8_source_v3(rows, cols, rs, sg_per_tg, fuse_norm, eps, rpsg=8):
21
+ """arch-3 lane addition: int8 GEMV with threadgroup x staging.
22
+
23
+ The vendored s8 v2 kernel re-reads the full x vector from device memory
24
+ once per simdgroup (rows/rpsg simdgroups x cols floats — for the 151936-
25
+ row Qwen3 unembed that is +50% of the weight bytes). v3 stages the
26
+ effective x (rmsnorm applied) into threadgroup memory once per
27
+ THREADGROUP, tile by tile; every simdgroup then streams only its int8
28
+ rows. Requires rows % (rpsg * sg_per_tg) == 0 (true for vocab 151936)."""
29
+ assert rs % 4 == 0 and cols % 4 == 0 and rows % (rpsg * sg_per_tg) == 0
30
+ tile = min(cols, 1024)
31
+ assert cols % tile == 0
32
+ ntiles = cols // tile
33
+ nthreads = sg_per_tg * 32
34
+ if fuse_norm:
35
+ norm_pre = f"""
36
+ threadgroup float ssq_sh[{sg_per_tg}];
37
+ {{
38
+ float ssq = 0.0f;
39
+ for (uint c = tid; c < {cols}u; c += {nthreads}u) {{
40
+ float v = x[c]; ssq = metal::fma(v, v, ssq);
41
+ }}
42
+ ssq = metal::simd_sum(ssq);
43
+ if (lane == 0u) ssq_sh[sg] = ssq;
44
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
45
+ float tt = 0.0f;
46
+ for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
47
+ rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
48
+ }}
49
+ """
50
+ stage = f"xb[cl] = x[c] * nw[c] * rms;"
51
+ else:
52
+ norm_pre = ""
53
+ stage = "xb[cl] = x[c];"
54
+ return f"""
55
+ uint tid = thread_position_in_threadgroup.x;
56
+ uint lane = tid & 31u;
57
+ uint sg = tid >> 5u;
58
+ uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
59
+ float rms = 1.0f;
60
+ {norm_pre}
61
+ threadgroup float xb[{tile}];
62
+ float acc[{rpsg}];
63
+ for (uint r = 0u; r < {rpsg}u; ++r) acc[r] = 0.0f;
64
+ const float4 one4 = float4(1.0f);
65
+ for (uint t = 0u; t < {ntiles}u; ++t) {{
66
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
67
+ for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
68
+ uint c = t * {tile}u + cl;
69
+ {stage}
70
+ }}
71
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
72
+ for (uint i = lane * 4u; i < {tile}u; i += 128u) {{
73
+ float4 xv = *(const threadgroup float4*)(xb + i);
74
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
75
+ char4 wv = *(const device char4*)((const device char*)W
76
+ + (ulong)(row0 + r) * {rs}u + t * {tile}u + i);
77
+ acc[r] += metal::dot(float4(wv.x, wv.y, wv.z, wv.w) * xv, one4);
78
+ }}
79
+ }}
80
+ }}
81
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
82
+ float a = metal::simd_sum(acc[r]);
83
+ if (lane == 0u) out[row0 + r] = scale[row0 + r] * a;
84
+ }}
85
+ """
86
+
87
+
88
+ def get_s8_gemv_v3(rows, cols, rs, fuse_norm=False, eps=1e-6):
89
+ key = ("s8v3", rows, cols, rs, fuse_norm, round(eps, 12))
90
+ if key in _CACHE:
91
+ return _CACHE[key]
92
+ inputs = ["W", "scale", "x"]
93
+ if fuse_norm:
94
+ inputs.append("nw")
95
+ src = _s8_source_v3(rows, cols, rs, SG_PER_TG, fuse_norm, eps)
96
+ k = mx.fast.metal_kernel(name="s8gemv3_r%d_c%d_n%d" % (rows, cols, fuse_norm),
97
+ input_names=inputs, output_names=["out"], source=src)
98
+ _CACHE[key] = (k, inputs)
99
+ return _CACHE[key]
100
+
101
+
102
+ def s8_gemv_v3(W, scale, x, norm_w=None, eps=1e-6, stream=None):
103
+ """drop-in for trit_kernels.s8_gemv where rows % 64 == 0."""
104
+ rows, rs = W.shape
105
+ cols = x.shape[0]
106
+ k, names = get_s8_gemv_v3(rows, cols, rs, norm_w is not None, eps)
107
+ vals = {"W": W, "scale": scale, "x": x, "nw": norm_w}
108
+ nsg = rows // 8
109
+ ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
110
+ return k(inputs=[vals[n] for n in names],
111
+ grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
112
+ output_shapes=[(rows,)], output_dtypes=[mx.float32], stream=stream)[0]
113
+
114
+
115
+ def get_qknorm_rope_append(n_heads, n_kv, hd, maxseq, eps):
116
+ key = ("qkra", n_heads, n_kv, hd, maxseq, round(eps, 12))
117
+ if key in _CACHE:
118
+ return _CACHE[key]
119
+ h2 = hd // 2
120
+ nheads_total = n_heads + 2 * n_kv
121
+ src = f"""
122
+ uint tid = thread_position_in_threadgroup.x;
123
+ uint lane = tid & 31u;
124
+ uint sg = tid >> 5u;
125
+ uint head = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
126
+ if (head >= {nheads_total}u) return;
127
+ uint pos = (uint)posbuf[0];
128
+ if (head == 0u && lane == 0u) {{ ((device float*)mask)[pos] = 0.0f; }}
129
+ device float* kc_w = (device float*)kc;
130
+ device float* vc_w = (device float*)vc;
131
+ if (head < {n_heads + n_kv}u) {{
132
+ uint base = head * {hd}u;
133
+ const device float* g = (head < {n_heads}u) ? qn : kn;
134
+ float ssq = 0.0f;
135
+ for (uint d = lane; d < {hd}u; d += 32u) {{
136
+ float v = qkv[base + d];
137
+ ssq = metal::fma(v, v, ssq);
138
+ }}
139
+ ssq = metal::simd_sum(ssq);
140
+ float rms = metal::rsqrt(ssq / {float(hd)}f + {eps:.9g}f);
141
+ for (uint d = lane; d < {hd}u; d += 32u) {{
142
+ float c = ct[pos * {h2}u + (d % {h2}u)];
143
+ float sn = st[pos * {h2}u + (d % {h2}u)];
144
+ float v = qkv[base + d] * rms * g[d];
145
+ float vr = (d < {h2}u)
146
+ ? -(qkv[base + d + {h2}u] * rms * g[d + {h2}u])
147
+ : (qkv[base + d - {h2}u] * rms * g[d - {h2}u]);
148
+ float r = metal::fma(v, c, vr * sn);
149
+ if (head < {n_heads}u) {{
150
+ qout[base + d] = r;
151
+ }} else {{
152
+ uint kh = head - {n_heads}u;
153
+ kc_w[(ulong)(kh * {maxseq}u + pos) * {hd}u + d] = r;
154
+ }}
155
+ }}
156
+ }} else {{
157
+ uint vh = head - {n_heads + n_kv}u;
158
+ for (uint d = lane; d < {hd}u; d += 32u) {{
159
+ vc_w[(ulong)(vh * {maxseq}u + pos) * {hd}u + d] =
160
+ qkv[{(n_heads + n_kv) * hd}u + vh * {hd}u + d];
161
+ }}
162
+ }}
163
+ """
164
+ k = mx.fast.metal_kernel(
165
+ name="qknorm_rope_append_%d_%d_%d" % (n_heads, n_kv, hd),
166
+ input_names=["qkv", "qn", "kn", "kc", "vc", "ct", "st", "posbuf", "mask"],
167
+ output_names=["qout"], source=src)
168
+ _CACHE[key] = k
169
+ return k
170
+
171
+
172
+ def qknorm_rope_append(qkv, qn, kn, kc, vc, ct, st, posbuf, mask,
173
+ n_heads, n_kv, hd, maxseq, eps, stream=None):
174
+ """qkv fp32 [(n_heads+2*n_kv)*hd] -> roped+normed q fp32 [n_heads*hd];
175
+ writes normed+roped k and raw v into kc/vc at position posbuf[0] and
176
+ zeroes mask[pos]."""
177
+ k = get_qknorm_rope_append(n_heads, n_kv, hd, maxseq, eps)
178
+ nheads_total = n_heads + 2 * n_kv
179
+ ntg = (nheads_total + SG_PER_TG - 1) // SG_PER_TG
180
+ return k(inputs=[qkv, qn, kn, kc, vc, ct, st, posbuf, mask],
181
+ grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
182
+ output_shapes=[(n_heads * hd,)], output_dtypes=[mx.float32],
183
+ stream=stream)[0]
mlx/fermion_mlx/container.py ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """TRTC v4 arch-3 (Qwen3) container reader — numpy memmap views, zero-copy
3
+ until the caller materializes.
4
+
5
+ Layout (ground truth: scripts/export_trit_c_model.py; cross-checks:
6
+ scripts/expand_trtc_v4_to_hf.py and kernels/gpu_e2e/runtime8b.py):
7
+ 60 B header <Iii9i2fi>: magic 'TRTC', version=4, arch=3, n_layers, hidden,
8
+ n_heads, n_kv_heads, head_dim, rotary_dims, intermediate, vocab, max_pos,
9
+ f32 rms_norm_eps, f32 rope_theta, i32 embed_out_kind (eok)
10
+ eok=2 (untied, 8B): int8 embed_in record right after the header
11
+ per layer: input_layernorm f32[H], post_attention_layernorm f32[H],
12
+ q_norm f32[head_dim], k_norm f32[head_dim],
13
+ then 7 trit records (q,k,v,o,gate,up,down) — arch-3 QKV are bias-less
14
+ final norm f32[H]
15
+ int8 embed_out (lm_head) record; eok=1 (tied, 0.6B) derives embed_in
16
+ from this record.
17
+ trit record: i32 rows, cols, cols_pad, row_stride(bytes);
18
+ u8 bp/bn/br [rows*row_stride] LSB-first; f32 s_lo[rows], s_hi[rows];
19
+ i32 rs_lo[rows], rs_hi[rows] (skipped); i32 has_bias (+ f32 bias[rows])
20
+ int8 record: i32 rows, cols, cols_pad, row_stride (== cols_pad);
21
+ s8 W[rows*row_stride]; f32 scale[rows]; i32 wsum[rows] (skipped)
22
+
23
+ Weight semantics: w[r,c] = (bp-bn) * (s_hi[r] if br else s_lo[r]);
24
+ int8 embeds: w[r,c] = scale[r] * Q[r,c].
25
+ """
26
+ import struct
27
+
28
+ import numpy as np
29
+
30
+ MAGIC = 0x43545254
31
+ HDR_FMT = "<Iii9i2fi"
32
+ HDR_BYTES = struct.calcsize(HDR_FMT) # 60
33
+ PROJS = ("q", "k", "v", "o", "gate", "up", "down")
34
+
35
+
36
+ class TritRecord:
37
+ __slots__ = ("rows", "cols", "cols_pad", "row_stride", "bp", "bn", "br",
38
+ "slo", "shi", "bias")
39
+
40
+
41
+ class Int8Record:
42
+ __slots__ = ("rows", "cols", "cols_pad", "row_stride", "W", "scale")
43
+
44
+
45
+ class Container:
46
+ pass
47
+
48
+
49
+ def _trit_record(mm, off):
50
+ r = TritRecord()
51
+ r.rows, r.cols, r.cols_pad, r.row_stride = (
52
+ int(v) for v in np.frombuffer(mm, np.int32, 4, off))
53
+ off += 16
54
+ n = r.rows * r.row_stride
55
+ r.bp = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
56
+ r.bn = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
57
+ r.br = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
58
+ r.slo = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
59
+ r.shi = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
60
+ off += 8 * r.rows # rs_lo, rs_hi trit-sum sidecars (integrity data, unused here)
61
+ has_bias = int(np.frombuffer(mm, np.int32, 1, off)[0]); off += 4
62
+ r.bias = None
63
+ if has_bias:
64
+ r.bias = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
65
+ return r, off
66
+
67
+
68
+ def _int8_record(mm, off):
69
+ r = Int8Record()
70
+ r.rows, r.cols, r.cols_pad, r.row_stride = (
71
+ int(v) for v in np.frombuffer(mm, np.int32, 4, off))
72
+ assert r.row_stride == r.cols_pad, "int8 record: row_stride must equal cols_pad"
73
+ off += 16
74
+ n = r.rows * r.row_stride
75
+ r.W = np.frombuffer(mm, np.int8, n, off).reshape(r.rows, r.row_stride); off += n
76
+ r.scale = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
77
+ off += 4 * r.rows # wsum
78
+ return r, off
79
+
80
+
81
+ def read_container(path):
82
+ """Memory-map a TRTC v4 arch-3 container; all arrays are views into the map."""
83
+ mm = np.memmap(path, dtype=np.uint8, mode="r")
84
+ h = struct.unpack(HDR_FMT, bytes(mm[:HDR_BYTES]))
85
+ c = Container()
86
+ (magic, c.version, c.arch, c.n_layers, c.hidden, c.n_heads, c.n_kv,
87
+ c.head_dim, c.rot, c.inter, c.vocab, c.maxpos, c.eps, c.theta, c.eok) = h
88
+ assert magic == MAGIC, f"bad magic {magic:#x}"
89
+ assert c.version == 4 and c.arch == 3, \
90
+ f"need TRTC v4 arch-3 (got v={c.version} arch={c.arch})"
91
+ assert c.eok in (1, 2), f"bad embed_out_kind {c.eok}"
92
+ off = HDR_BYTES
93
+ c.embed_in = None
94
+ if c.eok == 2: # untied: int8 embed_in follows the header
95
+ c.embed_in, off = _int8_record(mm, off)
96
+ assert (c.embed_in.rows, c.embed_in.cols) == (c.vocab, c.hidden)
97
+ H, hd = c.hidden, c.head_dim
98
+ q_dim, kv_dim = c.n_heads * hd, c.n_kv * hd
99
+ shapes = {"q": (q_dim, H), "k": (kv_dim, H), "v": (kv_dim, H),
100
+ "o": (H, q_dim), "gate": (c.inter, H), "up": (c.inter, H),
101
+ "down": (H, c.inter)}
102
+ c.layers = []
103
+ for li in range(c.n_layers):
104
+ L = {}
105
+ L["norm1"] = np.frombuffer(mm, np.float32, H, off); off += 4 * H
106
+ L["norm2"] = np.frombuffer(mm, np.float32, H, off); off += 4 * H
107
+ L["qnorm"] = np.frombuffer(mm, np.float32, hd, off); off += 4 * hd
108
+ L["knorm"] = np.frombuffer(mm, np.float32, hd, off); off += 4 * hd
109
+ for nm in PROJS:
110
+ L[nm], off = _trit_record(mm, off)
111
+ assert (L[nm].rows, L[nm].cols) == shapes[nm], \
112
+ f"L{li}.{nm}: {(L[nm].rows, L[nm].cols)} != {shapes[nm]}"
113
+ assert L[nm].bias is None, f"L{li}.{nm}: arch-3 records must be bias-less"
114
+ c.layers.append(L)
115
+ c.final_norm = np.frombuffer(mm, np.float32, H, off); off += 4 * H
116
+ c.embed_out, off = _int8_record(mm, off)
117
+ assert (c.embed_out.rows, c.embed_out.cols) == (c.vocab, c.hidden)
118
+ assert off == mm.size, f"reader ended at {off}, file is {mm.size}"
119
+ if c.embed_in is None: # tied: same int8 record serves both
120
+ c.embed_in = c.embed_out
121
+ c.mm = mm # keep the map alive
122
+ return c
123
+
124
+
125
+ # ---------------- fp32 reference math (gate-side, numpy) ----------------
126
+
127
+ def unpack_plane(plane, cols):
128
+ return np.unpackbits(plane, axis=1, bitorder="little")[:, :cols]
129
+
130
+
131
+ def dense_weights(rec, dtype=np.float32):
132
+ p = unpack_plane(rec.bp, rec.cols).astype(np.int8)
133
+ n = unpack_plane(rec.bn, rec.cols).astype(np.int8)
134
+ h = unpack_plane(rec.br, rec.cols)
135
+ s = (p - n).astype(dtype)
136
+ mag = np.where(h.astype(bool), rec.shi[:, None].astype(dtype),
137
+ rec.slo[:, None].astype(dtype))
138
+ return s * mag
139
+
140
+
141
+ def ref_rmsnorm(x, g, eps, dtype=np.float32):
142
+ x = x.astype(dtype)
143
+ return (x * (1.0 / np.sqrt(np.mean(x * x) + eps))) * g.astype(dtype)
mlx/fermion_mlx/gates.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """House correctness gates for the MLX arch-3 path.
3
+
4
+ refgate : V1 gate (dspec runtime8b.py protocol) — teacher-forced logits +
5
+ greedy-with-resync vs the container's own fp expansion reference
6
+ npz {prompt_ids, tf_logits, greedy, meta}. Every argmax
7
+ divergence must be a near-tie <= tie_tol of the logit scale;
8
+ greedy flips resynced and bounded.
9
+ cachegate : greedy-with-resync vs a banked hf_greedy_cache.json
10
+ (results_arch3_runtime format, keyed to container md5).
11
+ pathgate : path-consistency — the fused-kernel primary path and the pure
12
+ mx.fast-ops reference twin must produce the same greedy stream.
13
+
14
+ All gates return (ok, receipt_dict); receipts carry per-flip absolute and
15
+ relative margins so near-tie flips are documented, never waved through.
16
+ """
17
+ import hashlib
18
+ import json
19
+
20
+ import mlx.core as mx
21
+ import numpy as np
22
+
23
+
24
+ def _md5(path):
25
+ h = hashlib.md5()
26
+ with open(path, "rb") as f:
27
+ while chunk := f.read(1 << 24):
28
+ h.update(chunk)
29
+ return h.hexdigest()
30
+
31
+
32
+ def refgate(model, ref_path, tie_tol=0.04, max_flips=8):
33
+ ref = np.load(ref_path, allow_pickle=True)
34
+ ids = [int(t) for t in ref["prompt_ids"].tolist()]
35
+ tf = ref["tf_logits"]
36
+ greedy_ref = [int(t) for t in ref["greedy"].tolist()]
37
+ meta = json.loads(str(ref["meta"])) if "meta" in ref else {}
38
+ print(f"[refgate] prompt ids ({len(ids)}): {ids}")
39
+ print(f"[refgate] ref meta: {meta}")
40
+ rec = {"ref": str(ref_path), "meta": meta, "tf": [], "greedy_flips": [],
41
+ "tie_tol": tie_tol, "max_flips": max_flips}
42
+ # stage A: teacher-forced logits
43
+ mask = model.new_mask()
44
+ rels, ams, near, worst_gap = [], 0, 0, 0.0
45
+ for i, t in enumerate(ids):
46
+ _, _, lg = model.step(model.embed(t), i, mask)
47
+ g = np.array(lg, np.float64)
48
+ r = tf[i].astype(np.float64)
49
+ scale = float(np.abs(r).max())
50
+ rel = float(np.abs(g - r).max() / scale)
51
+ cos = float((g * r).sum() / (np.linalg.norm(g) * np.linalg.norm(r)))
52
+ gam, ram = int(g.argmax()), int(r.argmax())
53
+ am = gam == ram
54
+ ams += am
55
+ rels.append(rel)
56
+ extra = ""
57
+ if not am:
58
+ gap = float(g[gam] - g[ram]) / scale
59
+ worst_gap = max(worst_gap, gap)
60
+ near += gap <= tie_tol
61
+ extra = f" gap {100*gap:.3f}% ({'near-tie' if gap <= tie_tol else 'REAL'})"
62
+ rec["tf"].append({"pos": i, "rel": rel, "cos": cos, "argmax_match": am})
63
+ print(f" pos {i:2d}: rel {100*rel:6.3f}% | cos {cos:.6f} | argmax "
64
+ f"{'match' if am else 'MISMATCH'} (mlx {gam} ref {ram}){extra}")
65
+ print(f"[refgate] stage A: TF rel max {100*max(rels):.3f}% / mean "
66
+ f"{100*float(np.mean(rels)):.3f}%, argmax {ams}/{len(ids)} "
67
+ f"(+{near} near-tie), worst gap {100*worst_gap:.3f}%")
68
+ rec["tf_summary"] = {"rel_max": max(rels), "rel_mean": float(np.mean(rels)),
69
+ "argmax": f"{ams}/{len(ids)}", "near_tie": near}
70
+ # stage B: greedy with resync
71
+ mask = model.new_mask()
72
+ x, pos0 = model.prefill(ids, mask, compiled=False)
73
+ flips, g_toks, ok = 0, [], True
74
+ for s in range(len(greedy_ref)):
75
+ _, _, lg = model.step(x, pos0 + s, mask)
76
+ l = np.array(lg, np.float64)
77
+ g_tok = int(l.argmax())
78
+ g_toks.append(g_tok)
79
+ tok = g_tok
80
+ if g_tok != greedy_ref[s]:
81
+ gap_abs = float(l[g_tok] - l[greedy_ref[s]])
82
+ scale = float(np.abs(l).max())
83
+ gap = gap_abs / scale
84
+ flips += 1
85
+ worst_gap = max(worst_gap, gap)
86
+ rec["greedy_flips"].append({"step": s, "mlx": g_tok,
87
+ "ref": greedy_ref[s],
88
+ "margin_abs_logits": gap_abs,
89
+ "margin_rel": gap})
90
+ print(f" step {s}: mlx {g_tok} vs ref {greedy_ref[s]}, gap "
91
+ f"{gap_abs:.4f} logits = {100*gap:.3f}% of scale -> resync")
92
+ if flips > max_flips:
93
+ ok = False
94
+ break
95
+ tok = greedy_ref[s]
96
+ x = model.embed(tok)
97
+ mx.eval(x)
98
+ print(f"[refgate] ref greedy: {greedy_ref}")
99
+ print(f"[refgate] mlx greedy: {g_toks}")
100
+ ok = ok and worst_gap <= tie_tol and flips <= max_flips
101
+ rec.update(flips=flips, worst_gap=worst_gap, ok=bool(ok),
102
+ mlx_greedy=g_toks, ref_greedy=greedy_ref)
103
+ print(f"[refgate] {'PASS' if ok else 'FAIL'} (worst gap {100*worst_gap:.3f}% "
104
+ f"tol {100*tie_tol:.1f}%; {flips} flips, tol {max_flips})")
105
+ return ok, rec
106
+
107
+
108
+ def cachegate(model, container_path, cache_path, tie_tol=0.04, max_flips=8):
109
+ cache = json.load(open(cache_path))
110
+ md5 = _md5(container_path)
111
+ print(f"[cachegate] container md5 {md5} vs cache key {cache['container_md5']}")
112
+ assert md5 == cache["container_md5"], "cache is for a different container"
113
+ rec = {"cache": str(cache_path), "container_md5": md5, "prompts": [],
114
+ "tie_tol": tie_tol, "max_flips_per_prompt": max_flips}
115
+ ok = True
116
+ tot_flips, worst = 0, 0.0
117
+ for pi, (prompt, ref) in enumerate(zip(cache["prompts"], cache["tokens"])):
118
+ mask = model.new_mask()
119
+ x, pos0 = model.prefill([int(t) for t in prompt], mask)
120
+ flips, fliprec = 0, []
121
+ for s, rt in enumerate(ref):
122
+ _, _, lg = model.step(x, pos0 + s, mask)
123
+ l = np.array(lg, np.float64)
124
+ g_tok = int(l.argmax())
125
+ tok = g_tok
126
+ if g_tok != int(rt):
127
+ gap_abs = float(l[g_tok] - l[int(rt)])
128
+ gap = gap_abs / float(np.abs(l).max())
129
+ worst = max(worst, gap)
130
+ flips += 1
131
+ fliprec.append({"step": s, "mlx": g_tok, "ref": int(rt),
132
+ "margin_abs_logits": gap_abs, "margin_rel": gap})
133
+ print(f" p{pi} step {s}: mlx {g_tok} vs hf {rt}, gap "
134
+ f"{gap_abs:.4f} logits = {100*gap:.3f}% -> resync")
135
+ tok = int(rt)
136
+ x = model.embed(tok)
137
+ mx.eval(x)
138
+ tot_flips += flips
139
+ good = flips <= max_flips
140
+ ok = ok and good
141
+ rec["prompts"].append({"prompt": prompt, "steps": len(ref),
142
+ "flips": flips, "flip_detail": fliprec,
143
+ "pass": bool(good)})
144
+ print(f" prompt {pi}: {len(ref)} steps, {flips} resynced flips -> "
145
+ f"{'PASS' if good else 'FAIL'}")
146
+ ok = ok and worst <= tie_tol
147
+ rec.update(total_flips=tot_flips, worst_gap=worst, ok=bool(ok))
148
+ print(f"[cachegate] {'PASS' if ok else 'FAIL'} ({tot_flips} flips total, "
149
+ f"worst gap {100*worst:.3f}% tol {100*tie_tol:.1f}%)")
150
+ return ok, rec
151
+
152
+
153
+ def pathgate(model, ids, n=32):
154
+ """fused-kernel path vs pure-mx.fast-ops twin: greedy streams must be
155
+ IDENTICAL (same weights, same GEMV kernels; isolates the new kernel)."""
156
+ fused, _ = model.generate(ids, n, compiled=True)
157
+ ref = model.generate_ref(ids, n)
158
+ same = fused == ref
159
+ d = next((i for i in range(min(len(fused), len(ref)))
160
+ if fused[i] != ref[i]), None)
161
+ print(f"[pathgate] fused-vs-mxops {n}-token greedy: "
162
+ f"{'IDENTICAL' if same else f'FIRST DIVERGENCE at {d}'}")
163
+ return same, {"n": n, "identical": bool(same), "first_divergence": d,
164
+ "fused": fused, "mxops": ref}
mlx/fermion_mlx/mbatch.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Small-batch (M-token) GEMM kernels for speculative verify — EXPERIMENTAL.
3
+
4
+ Why these exist: the shipped decode path is M=1 GEMV. Speculative decoding
5
+ only wins if the verifier scores k+1 draft positions while streaming the
6
+ weight planes ONCE; re-running the GEMV k+1 times re-reads the weights k+1
7
+ times and cannot beat plain decode. These kernels keep the v5/v7 interleaved
8
+ plane layout + one-weight-read structure and add an M-wide accumulator:
9
+
10
+ trit_gemm_m : five-value planes X[M, cols] -> out[M, rows]
11
+ v7 lane mapping (lane = row(8) x word-slot(4), planes read
12
+ once, fully coalesced) + v6 mask-convert-FMA decode (float4
13
+ (uint4(a) & m4) == 2^bit when set; x pre-scaled by 2^-bit,
14
+ exact in fp32). No LUT: a per-token LUT tile for M=5 exceeds
15
+ the 32KB threadgroup budget at inter=12288.
16
+ s8_gemm_m : int8 rows (lm_head / untied embeds), one simdgroup per row,
17
+ weight char4 loaded once and dotted against all M x-vectors.
18
+
19
+ No norm/GLU/residual template flags here: the spec verify path applies
20
+ RMSNorm / SwiGLU / residual adds as plain MLX elementwise ops on [M, ...]
21
+ tensors (cheap at M<=8), keeping these kernels pure GEMMs.
22
+
23
+ Additive module — nothing in trit_kernels.py / arch3.py / model.py changes.
24
+ """
25
+ import mlx.core as mx
26
+
27
+ SG_PER_TG = 8
28
+ _CACHE = {}
29
+
30
+
31
+ def _trit_gemm_m_source(M, rows, cols, rs):
32
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
33
+ W = rs // 4
34
+ kbody = []
35
+ for k in range(8):
36
+ m4 = [1 << (4 * k + i) for i in range(4)]
37
+ ps = ", ".join("%.9ef" % (2.0 ** -(4 * k + i)) for i in range(4))
38
+ loads = "\n".join(
39
+ f""" {{
40
+ float4 xs = x4[{m}u * {cols // 4}u + wl * 8u + {k}u] * ps{k};
41
+ accLo[{m}] += metal::dot(sa, xs);
42
+ accHi[{m}] += metal::dot(sb, xs);
43
+ }}""" for m in range(M))
44
+ kbody.append(f""" {{
45
+ const uint4 m4 = uint4({m4[0]}u, {m4[1]}u, {m4[2]}u, {m4[3]}u);
46
+ const float4 ps{k} = float4({ps});
47
+ float4 sa = float4(uint4(a) & m4) - float4(uint4(b) & m4);
48
+ float4 sb = float4(uint4(cc) & m4) - float4(uint4(d) & m4);
49
+ {loads}
50
+ }}""")
51
+ kbody = "\n".join(kbody)
52
+ outs = "\n".join(
53
+ f""" accLo[{m}] += metal::simd_shuffle_down(accLo[{m}], 16u);
54
+ accLo[{m}] += metal::simd_shuffle_down(accLo[{m}], 8u);
55
+ accHi[{m}] += metal::simd_shuffle_down(accHi[{m}], 16u);
56
+ accHi[{m}] += metal::simd_shuffle_down(accHi[{m}], 8u);"""
57
+ for m in range(M))
58
+ writes = "\n".join(
59
+ f" out[{m}u * {rows}u + row] = "
60
+ f"metal::fma(slo[row], accLo[{m}], shi[row] * accHi[{m}]);"
61
+ for m in range(M))
62
+ return f"""
63
+ uint tid = thread_position_in_threadgroup.x;
64
+ uint lane = tid & 31u;
65
+ uint sg = tid >> 5u;
66
+ uint blk = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
67
+ uint row0 = blk * 8u;
68
+ if (row0 >= {rows}u) return;
69
+ uint r = lane & 7u;
70
+ uint wsl = lane >> 3u;
71
+ const device uint* p32 = (const device uint*)bp;
72
+ const device uint* n32 = (const device uint*)bn;
73
+ const device uint* h32 = (const device uint*)br;
74
+ const device float4* x4 = (const device float4*)x;
75
+ float accLo[{M}], accHi[{M}];
76
+ for (uint m = 0u; m < {M}u; ++m) {{ accLo[m] = 0.0f; accHi[m] = 0.0f; }}
77
+ for (uint wl = wsl; wl < {W}u; wl += 4u) {{
78
+ ulong base = ((ulong)blk * {W}u + wl) * 8u + r;
79
+ uint pw = p32[base], nw2 = n32[base], hw = h32[base];
80
+ uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
81
+ {kbody}
82
+ }}
83
+ {outs}
84
+ if (lane < 8u) {{
85
+ uint row = row0 + lane;
86
+ {writes}
87
+ }}
88
+ """
89
+
90
+
91
+ def _s8_gemm_m_source(M, rows, cols, rs):
92
+ assert rs % 4 == 0 and cols % 4 == 0
93
+ accs = "\n".join(
94
+ f" acc[{m}] += metal::dot(wf, x4[{m}u * {cols // 4}u + i]);"
95
+ for m in range(M))
96
+ reduces = "\n".join(
97
+ f""" {{
98
+ float a = metal::simd_sum(acc[{m}]);
99
+ if (lane == 0u) out[{m}u * {rows}u + row] = scale[row] * a;
100
+ }}""" for m in range(M))
101
+ return f"""
102
+ uint tid = thread_position_in_threadgroup.x;
103
+ uint lane = tid & 31u;
104
+ uint sg = tid >> 5u;
105
+ uint row = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
106
+ if (row >= {rows}u) return;
107
+ const device char* w = (const device char*)W + (ulong)row * {rs}u;
108
+ const device float4* x4 = (const device float4*)x;
109
+ float acc[{M}];
110
+ for (uint m = 0u; m < {M}u; ++m) acc[m] = 0.0f;
111
+ for (uint i = lane; i < {cols // 4}u; i += 32u) {{
112
+ char4 wv = *(const device char4*)(w + i * 4u);
113
+ float4 wf = float4(wv.x, wv.y, wv.z, wv.w);
114
+ {accs}
115
+ }}
116
+ {reduces}
117
+ """
118
+
119
+
120
+ def _get(kind, M, rows, cols, rs):
121
+ key = (kind, M, rows, cols, rs)
122
+ if key in _CACHE:
123
+ return _CACHE[key]
124
+ if kind == "trit":
125
+ src = _trit_gemm_m_source(M, rows, cols, rs)
126
+ k = mx.fast.metal_kernel(
127
+ name=f"tritgemm_m{M}_r{rows}_c{cols}",
128
+ input_names=["bp", "bn", "br", "slo", "shi", "x"],
129
+ output_names=["out"], source=src)
130
+ else:
131
+ src = _s8_gemm_m_source(M, rows, cols, rs)
132
+ k = mx.fast.metal_kernel(
133
+ name=f"s8gemm_m{M}_r{rows}_c{cols}",
134
+ input_names=["W", "scale", "x"],
135
+ output_names=["out"], source=src)
136
+ _CACHE[key] = k
137
+ return k
138
+
139
+
140
+ def trit_gemm_m(planes, slo, shi, X, stream=None):
141
+ """planes v5-interleaved (KERNEL_VERSION>=5 prep). X fp32 [M, cols]
142
+ (already normed / activated). Returns fp32 [M, rows]."""
143
+ bp, bn, br = planes
144
+ rows, rs = bp.shape
145
+ M, cols = X.shape
146
+ k = _get("trit", M, rows, cols, rs)
147
+ nsg = rows // 8
148
+ ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
149
+ return k(inputs=[bp, bn, br, slo, shi, X],
150
+ grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
151
+ output_shapes=[(M, rows)], output_dtypes=[mx.float32],
152
+ stream=stream)[0]
153
+
154
+
155
+ def s8_gemm_m(W, scale, X, stream=None):
156
+ """int8 W [rows, rs], X fp32 [M, cols] (already normed). -> [M, rows]."""
157
+ rows, rs = W.shape
158
+ M, cols = X.shape
159
+ k = _get("s8", M, rows, cols, rs)
160
+ ntg = (rows + SG_PER_TG - 1) // SG_PER_TG
161
+ return k(inputs=[W, scale, X],
162
+ grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
163
+ output_shapes=[(M, rows)], output_dtypes=[mx.float32],
164
+ stream=stream)[0]
mlx/fermion_mlx/model.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """End-to-end MLX/Metal decode for TRTC v4 **arch-3 (Qwen3)** five-value
3
+ containers — Neutrino-0.6B (tied int8 embeds) and Neutrino-8B (untied).
4
+
5
+ Every transformer linear runs on the banked five-value {0,±s_lo,±s_hi}
6
+ 3-bit-planar Metal GEMV kernels (vendored trit_kernels.py, byte-identical
7
+ to kernels/metal/trit_kernels.py): qkv / o / gate+up / down fused per layer
8
+ + the int8 lm_head GEMV. The arch-3 delta — per-head Q/K-RMSNorm before
9
+ rope, biasless qkv, int8 embed_in — is handled by the new fused
10
+ qknorm_rope_append kernel (arch3.py) and an int8 embedding dequant.
11
+
12
+ Fusions per decode token: rmsnorm folded into qkv/gu/lm_head GEMVs, SwiGLU
13
+ folded into the down GEMV, residual adds folded into o/down epilogues,
14
+ QK-norm+rope+KV-append in one dispatch. MLX supplies SDPA, argmax and the
15
+ lazy-graph dispatch batching.
16
+
17
+ Two forward implementations:
18
+ _step_fn : primary path (fused kernel, preallocated KV + additive mask,
19
+ mx.compile-able) — the shipped/gated/benched path.
20
+ step_ref : pure-MLX-ops reference twin for the NEW kernel (mx.fast
21
+ rms_norm/rope, concat KV) — used by the path-consistency gate.
22
+ """
23
+ import time
24
+
25
+ import mlx.core as mx
26
+ import numpy as np
27
+
28
+ from .container import read_container
29
+ from .trit_kernels import trit_gemv, s8_gemv, prep_planes
30
+ from . import trit_kernels
31
+ from .arch3 import qknorm_rope_append, s8_gemv_v3
32
+
33
+ NEG_INF = -1e9
34
+
35
+
36
+ class _Layer:
37
+ pass
38
+
39
+
40
+ def _cat(arrs):
41
+ return np.ascontiguousarray(np.concatenate(arrs, axis=0))
42
+
43
+
44
+ class MetalTritQwen3:
45
+ """TRTC v4 arch-3 walker (0.6B or 8B, dims from the container header)."""
46
+
47
+ def __init__(self, path, maxseq=1024, verbose=True):
48
+ t0 = time.time()
49
+ c = read_container(path)
50
+ self.meta = c
51
+ self.eps, self.theta = float(c.eps), float(c.theta)
52
+ self.n_layers, self.hidden = c.n_layers, c.hidden
53
+ self.n_heads, self.n_kv, self.hd = c.n_heads, c.n_kv, c.head_dim
54
+ self.inter, self.vocab, self.eok = c.inter, c.vocab, c.eok
55
+ assert c.rot == self.hd, "kernels assume full-head_dim rope"
56
+ self.scale = self.hd ** -0.5
57
+ self.qd = self.n_heads * self.hd
58
+ self.kd = self.n_kv * self.hd
59
+ self.maxseq = maxseq
60
+
61
+ # int8 lm_head (+ tied/untied int8 embed_in)
62
+ eo = c.embed_out
63
+ self.eoW = mx.array(np.ascontiguousarray(eo.W)) # int8 [V, H]
64
+ self.eo_scale = mx.array(np.ascontiguousarray(eo.scale))
65
+ mx.eval(self.eoW, self.eo_scale)
66
+ if c.eok == 2:
67
+ ei = c.embed_in
68
+ self.embW = mx.array(np.ascontiguousarray(ei.W)) # int8 [V, H]
69
+ self.embS = mx.array(np.ascontiguousarray(ei.scale))
70
+ mx.eval(self.embW, self.embS)
71
+ else:
72
+ self.embW, self.embS = self.eoW, self.eo_scale
73
+
74
+ self.layers = []
75
+ for L in c.layers:
76
+ q, k, v, o, gate, up, down = (L[n] for n in
77
+ ("q", "k", "v", "o", "gate", "up", "down"))
78
+ y = _Layer()
79
+ y.norm1 = mx.array(np.ascontiguousarray(L["norm1"]))
80
+ y.norm2 = mx.array(np.ascontiguousarray(L["norm2"]))
81
+ y.qnorm = mx.array(np.ascontiguousarray(L["qnorm"]))
82
+ y.knorm = mx.array(np.ascontiguousarray(L["knorm"]))
83
+ y.qkv_planes = prep_planes(tuple(
84
+ mx.array(_cat([getattr(r, pl) for r in (q, k, v)]))
85
+ for pl in ("bp", "bn", "br")), q.cols)
86
+ y.qkv_slo = mx.array(_cat([q.slo, k.slo, v.slo]))
87
+ y.qkv_shi = mx.array(_cat([q.shi, k.shi, v.shi]))
88
+ y.o_planes = prep_planes(tuple(
89
+ mx.array(np.ascontiguousarray(getattr(o, pl)))
90
+ for pl in ("bp", "bn", "br")), o.cols)
91
+ y.o_slo = mx.array(np.ascontiguousarray(o.slo))
92
+ y.o_shi = mx.array(np.ascontiguousarray(o.shi))
93
+ y.gu_planes = prep_planes(tuple(
94
+ mx.array(_cat([getattr(gate, pl), getattr(up, pl)]))
95
+ for pl in ("bp", "bn", "br")), gate.cols)
96
+ y.gu_slo = mx.array(_cat([gate.slo, up.slo]))
97
+ y.gu_shi = mx.array(_cat([gate.shi, up.shi]))
98
+ y.dn_planes = prep_planes(tuple(
99
+ mx.array(np.ascontiguousarray(getattr(down, pl)))
100
+ for pl in ("bp", "bn", "br")), down.cols)
101
+ y.dn_slo = mx.array(np.ascontiguousarray(down.slo))
102
+ y.dn_shi = mx.array(np.ascontiguousarray(down.shi))
103
+ mx.eval(*(y.qkv_planes + y.o_planes + y.gu_planes + y.dn_planes),
104
+ y.qkv_slo, y.qkv_shi, y.o_slo, y.o_shi,
105
+ y.gu_slo, y.gu_shi, y.dn_slo, y.dn_shi,
106
+ y.norm1, y.norm2, y.qnorm, y.knorm)
107
+ self.layers.append(y)
108
+ self.final_norm = mx.array(np.ascontiguousarray(c.final_norm))
109
+
110
+ # rope tables + preallocated KV + mask
111
+ inv = self.theta ** -(np.arange(0, self.hd, 2, dtype=np.float64) / self.hd)
112
+ ang = np.outer(np.arange(maxseq, dtype=np.float64), inv)
113
+ self.ct = mx.array(np.cos(ang).astype(np.float32).ravel())
114
+ self.st = mx.array(np.sin(ang).astype(np.float32).ravel())
115
+ self.kc = [mx.zeros((self.n_kv, maxseq, self.hd), dtype=mx.float32)
116
+ for _ in self.layers]
117
+ self.vc = [mx.zeros((self.n_kv, maxseq, self.hd), dtype=mx.float32)
118
+ for _ in self.layers]
119
+ mx.eval(self.ct, self.st, self.final_norm, *self.kc, *self.vc)
120
+ self._stepc = None
121
+ # x-restaged int8 unembed when the row count allows it
122
+ self._s8 = s8_gemv_v3 if self.vocab % 64 == 0 else s8_gemv
123
+ if verbose:
124
+ gb = mx.get_active_memory() / 2**30
125
+ print(f"loaded {path} in {time.time()-t0:.1f}s (arch-3, "
126
+ f"{self.n_layers}L/{self.hidden}H/{self.n_heads}q/{self.n_kv}kv/"
127
+ f"{self.inter}i/{self.vocab}V eok={self.eok}, kernel v"
128
+ f"{trit_kernels.KERNEL_VERSION}, maxseq {maxseq}, "
129
+ f"active mem {gb:.2f} GiB)")
130
+
131
+ # ---------------- primary path ----------------
132
+
133
+ def new_mask(self):
134
+ m = mx.full((self.maxseq,), NEG_INF, dtype=mx.float32)
135
+ mx.eval(m)
136
+ return m
137
+
138
+ def embed(self, tok):
139
+ """int8 embedding dequant: fp32 row = scale[tok] * W8[tok]."""
140
+ return self.embW[tok].astype(mx.float32) * self.embS[tok]
141
+
142
+ def _window(self, pos):
143
+ """KV window for position pos: grown in 256-slot blocks. Slicing off
144
+ the -1e9-masked tail is numerically exact (exp underflows to 0.0 in
145
+ fp32), so shrinking the SDPA read never changes the math — it only
146
+ stops streaming megabytes of masked cache per step."""
147
+ return min(self.maxseq, ((pos + 1 + 255) // 256) * 256)
148
+
149
+ def _step_fn(self, x, posbuf, mask, W):
150
+ """x fp32 [H] at position posbuf[0] -> (next_tok, x_next, logits).
151
+ Fixed shapes per W: SDPA over the first W cache slots with additive
152
+ mask (stale slots -1e9; qknorm_rope_append maintains mask[pos]=0)."""
153
+ eps = self.eps
154
+ m4 = mask[:W].reshape(1, 1, 1, W)
155
+ for li, L in enumerate(self.layers):
156
+ qkv = trit_gemv(L.qkv_planes, L.qkv_slo, L.qkv_shi, x,
157
+ norm_w=L.norm1, eps=eps)
158
+ q = qknorm_rope_append(qkv, L.qnorm, L.knorm, self.kc[li], self.vc[li],
159
+ self.ct, self.st, posbuf, mask,
160
+ self.n_heads, self.n_kv, self.hd,
161
+ self.maxseq, eps)
162
+ q = q.reshape(1, self.n_heads, 1, self.hd)
163
+ a = mx.fast.scaled_dot_product_attention(
164
+ q, self.kc[li][:, :W][None], self.vc[li][:, :W][None],
165
+ scale=self.scale, mask=m4)
166
+ a = a.reshape(self.qd)
167
+ x = trit_gemv(L.o_planes, L.o_slo, L.o_shi, a, resid=x, eps=eps)
168
+ gu = trit_gemv(L.gu_planes, L.gu_slo, L.gu_shi, x,
169
+ norm_w=L.norm2, eps=eps)
170
+ x = trit_gemv(L.dn_planes, L.dn_slo, L.dn_shi, gu, glu=True,
171
+ resid=x, eps=eps)
172
+ logits = self._s8(self.eoW, self.eo_scale, x, norm_w=self.final_norm, eps=eps)
173
+ nt = mx.argmax(logits)
174
+ return nt, self.embed(nt), logits
175
+
176
+ def step(self, x, pos, mask):
177
+ """uncompiled single step (gates)."""
178
+ return self._step_fn(x, mx.array([pos], dtype=mx.int32), mask,
179
+ self._window(pos))
180
+
181
+ def step_compiled(self, x, posbuf, mask, W):
182
+ if self._stepc is None:
183
+ self._stepc = {}
184
+ if W not in self._stepc:
185
+ self._stepc[W] = mx.compile(
186
+ lambda xx, pb, mk, _W=W: self._step_fn(xx, pb, mk, _W))
187
+ return self._stepc[W](x, posbuf, mask)
188
+
189
+ def prefill(self, ids, mask, compiled=True):
190
+ """feed ids[:-1]; returns (x armed with ids[-1], pos of ids[-1])."""
191
+ for i, t in enumerate(ids[:-1]):
192
+ pb = mx.array([i], dtype=mx.int32)
193
+ W = self._window(i)
194
+ if compiled:
195
+ nt, xn, lg = self.step_compiled(self.embed(int(t)), pb, mask, W)
196
+ else:
197
+ nt, xn, lg = self._step_fn(self.embed(int(t)), pb, mask, W)
198
+ mx.eval(lg)
199
+ x = self.embed(int(ids[-1]))
200
+ mx.eval(x)
201
+ return x, len(ids) - 1
202
+
203
+ def generate(self, ids, n, compiled=True, eos=None):
204
+ """greedy decode n tokens after prompt ids; returns (tokens,
205
+ decode_seconds). eos: optional set of stop ids (stop AFTER emitting)."""
206
+ mask = self.new_mask()
207
+ x, pos0 = self.prefill(ids, mask, compiled=compiled)
208
+ toks = []
209
+ prev = None
210
+ t0 = time.perf_counter()
211
+ for i in range(n):
212
+ pos = pos0 + i
213
+ pb = mx.array([pos], dtype=mx.int32)
214
+ W = self._window(pos)
215
+ if compiled:
216
+ nt, x, lg = self.step_compiled(x, pb, mask, W)
217
+ else:
218
+ nt, x, lg = self._step_fn(x, pb, mask, W)
219
+ mx.async_eval(nt, x)
220
+ if prev is not None:
221
+ toks.append(int(prev))
222
+ if eos and toks[-1] in eos:
223
+ prev = None
224
+ break
225
+ prev = nt
226
+ if prev is not None:
227
+ toks.append(int(prev))
228
+ mx.eval(x)
229
+ dt = time.perf_counter() - t0
230
+ return toks, dt
231
+
232
+ # ---------------- pure-MLX-ops reference twin ----------------
233
+
234
+ def step_ref(self, x, kv, pos):
235
+ """same GEMV kernels, but QK-norm/rope/cache via mx.fast ops +
236
+ concat KV — the cross-check for the NEW fused kernel."""
237
+ eps = self.eps
238
+ for li, L in enumerate(self.layers):
239
+ qkv = trit_gemv(L.qkv_planes, L.qkv_slo, L.qkv_shi, x,
240
+ norm_w=L.norm1, eps=eps)
241
+ q = mx.fast.rms_norm(qkv[:self.qd].reshape(self.n_heads, self.hd),
242
+ L.qnorm, eps)
243
+ k = mx.fast.rms_norm(qkv[self.qd:self.qd + self.kd]
244
+ .reshape(self.n_kv, self.hd), L.knorm, eps)
245
+ qk = mx.concatenate([q, k], axis=0).reshape(
246
+ 1, self.n_heads + self.n_kv, 1, self.hd)
247
+ qk = mx.fast.rope(qk, self.hd, traditional=False, base=self.theta,
248
+ scale=1.0, offset=pos)
249
+ qh = qk[:, :self.n_heads]
250
+ kh = qk[:, self.n_heads:]
251
+ vh = qkv[self.qd + self.kd:].reshape(1, self.n_kv, 1, self.hd)
252
+ if kv[li] is None:
253
+ kv[li] = (kh, vh)
254
+ else:
255
+ kv[li] = (mx.concatenate([kv[li][0], kh], axis=2),
256
+ mx.concatenate([kv[li][1], vh], axis=2))
257
+ keys, values = kv[li]
258
+ a = mx.fast.scaled_dot_product_attention(qh, keys, values,
259
+ scale=self.scale)
260
+ a = a.reshape(self.qd)
261
+ x = trit_gemv(L.o_planes, L.o_slo, L.o_shi, a, resid=x, eps=eps)
262
+ gu = trit_gemv(L.gu_planes, L.gu_slo, L.gu_shi, x,
263
+ norm_w=L.norm2, eps=eps)
264
+ x = trit_gemv(L.dn_planes, L.dn_slo, L.dn_shi, gu, glu=True,
265
+ resid=x, eps=eps)
266
+ return self._s8(self.eoW, self.eo_scale, x, norm_w=self.final_norm, eps=eps)
267
+
268
+ def generate_ref(self, ids, n):
269
+ kv = [None] * self.n_layers
270
+ for i, t in enumerate(ids[:-1]):
271
+ lg = self.step_ref(self.embed(int(t)), kv, i)
272
+ mx.eval(lg)
273
+ x = self.embed(int(ids[-1]))
274
+ toks = []
275
+ for i in range(n):
276
+ lg = self.step_ref(x, kv, len(ids) - 1 + i)
277
+ nt = mx.argmax(lg)
278
+ x = self.embed(nt)
279
+ mx.eval(x)
280
+ toks.append(int(nt))
281
+ return toks
mlx/fermion_mlx/spec.py ADDED
@@ -0,0 +1,256 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """EXPERIMENTAL speculative decoding: 0.6B pack drafts, 8B pack verifies.
3
+
4
+ House exactness rule (greedy-exact acceptance): a draft token is accepted
5
+ iff it equals the verifier's argmax at that position. The emitted stream is
6
+ therefore exactly the verifier's own greedy stream *as computed by the
7
+ batched verify path* — the ship gate (specgate) checks that stream is
8
+ token-identical to the shipped single-token path at 32 and 256 tokens.
9
+
10
+ Mechanics per round (k=4 draft tokens, M=k+1 verify batch):
11
+ 1. draft consumes its pending tokens, then chains k greedy proposals
12
+ d_1..d_k on its own single-token path;
13
+ 2. verifier scores [pending[-1], d_1..d_k] in ONE batched pass
14
+ (mbatch.trit_gemm_m / s8_gemm_m — weight planes stream once, which is
15
+ the entire speedup mechanism on a bandwidth-bound GPU);
16
+ 3. accept the longest prefix with d_i == argmax_{i-1}; emit accepted
17
+ tokens + the verifier's own next token (correction or bonus);
18
+ 4. KV rewind: positions are the source of truth. The verifier's stale
19
+ rows get overwritten by the next round's appends before any read (the
20
+ verify mask only allows slots <= row position). The draft re-masks its
21
+ stale slots (additive mask back to -1e9) and rolls its position back.
22
+
23
+ Both models live in one process; run under --mem-limit-gb 6 (house OOM
24
+ rule). Additive module: model.py / gates.py / existing CLI modes untouched.
25
+ """
26
+ import json
27
+ import time
28
+
29
+ import mlx.core as mx
30
+
31
+ from .mbatch import trit_gemm_m, s8_gemm_m
32
+
33
+ NEG_INF = -1e9
34
+
35
+
36
+ def _silu(g):
37
+ return g * mx.sigmoid(g)
38
+
39
+
40
+ class SpecDecoder:
41
+ def __init__(self, verifier, draft, k=4):
42
+ assert verifier.vocab == draft.vocab, "draft/verifier vocab mismatch"
43
+ self.V, self.D, self.k = verifier, draft, k
44
+ self._vc = {} # compiled verify fns keyed (M, W)
45
+ self._specmask = verifier.new_mask() # kernel side-channel, never read
46
+ self.stats = None
47
+
48
+ # ---------------- batched verify ----------------
49
+
50
+ def _verify(self, toks, pos0):
51
+ from .arch3 import qknorm_rope_append
52
+ V = self.V
53
+ M = len(toks)
54
+ W = V._window(pos0 + M - 1)
55
+ key = (M, W)
56
+ if key not in self._vc:
57
+ # build an uncompiled closure once; mx.compile wrapper around it
58
+ eps = V.eps
59
+
60
+ def fn(X, pbs, amask, _M=M, _W=W):
61
+ m4 = amask.reshape(1, 1, _M, _W)
62
+ for li, L in enumerate(V.layers):
63
+ qkv = trit_gemm_m(L.qkv_planes, L.qkv_slo, L.qkv_shi,
64
+ mx.fast.rms_norm(X, L.norm1, eps))
65
+ qrows = [qknorm_rope_append(
66
+ qkv[m], L.qnorm, L.knorm, V.kc[li], V.vc[li],
67
+ V.ct, V.st, pbs[m:m + 1], self._specmask,
68
+ V.n_heads, V.n_kv, V.hd, V.maxseq, eps)
69
+ for m in range(_M)]
70
+ q = mx.stack(qrows).reshape(_M, V.n_heads, V.hd)
71
+ q = q.transpose(1, 0, 2)[None]
72
+ a = mx.fast.scaled_dot_product_attention(
73
+ q, V.kc[li][:, :_W][None], V.vc[li][:, :_W][None],
74
+ scale=V.scale, mask=m4)
75
+ a = a[0].transpose(1, 0, 2).reshape(_M, V.qd)
76
+ X = X + trit_gemm_m(L.o_planes, L.o_slo, L.o_shi, a)
77
+ gu = trit_gemm_m(L.gu_planes, L.gu_slo, L.gu_shi,
78
+ mx.fast.rms_norm(X, L.norm2, eps))
79
+ h = _silu(gu[:, :V.inter]) * gu[:, V.inter:]
80
+ X = X + trit_gemm_m(L.dn_planes, L.dn_slo, L.dn_shi, h)
81
+ logits = s8_gemm_m(V.eoW, V.eo_scale,
82
+ mx.fast.rms_norm(X, V.final_norm, eps))
83
+ return mx.argmax(logits, axis=1)
84
+
85
+ self._vc[key] = mx.compile(fn)
86
+ tarr = mx.array(toks, dtype=mx.int32)
87
+ X = V.embW[tarr].astype(mx.float32) * V.embS[tarr][:, None]
88
+ pbs = mx.array([pos0 + m for m in range(M)], dtype=mx.int32)
89
+ idx = mx.arange(W)[None, :]
90
+ rowp = (pos0 + mx.arange(M))[:, None]
91
+ amask = mx.where(idx <= rowp, mx.array(0.0, dtype=mx.float32),
92
+ mx.array(NEG_INF, dtype=mx.float32))
93
+ am = self._vc[(M, W)](X, pbs, amask)
94
+ mx.eval(am)
95
+ return [int(t) for t in am]
96
+
97
+ # ---------------- draft helpers (single-token path) ----------------
98
+
99
+ def _draft_chain(self, pending, dmask, dpos):
100
+ """consume pending tokens, then chain k greedy proposals.
101
+ Returns (proposals, new_dpos). Draft KV/mask advance by
102
+ len(pending) + k - 1 consumed inputs."""
103
+ # NOTE: each step is dispatched with async_eval before the next is
104
+ # built — the KV/mask writes are input mutations invisible to the
105
+ # lazy graph, and batching several steps into one evaluation makes
106
+ # MLX copy-on-write the caches and silently drop intermediate
107
+ # appends (found the hard way; the shipped generate() uses the same
108
+ # per-step dispatch discipline).
109
+ D, k = self.D, self.k
110
+ pos = dpos
111
+ nt = x = None
112
+ for t in pending:
113
+ pb = mx.array([pos], dtype=mx.int32)
114
+ nt, x, _ = D.step_compiled(D.embed(int(t)), pb, dmask,
115
+ D._window(pos))
116
+ mx.async_eval(nt, x)
117
+ pos += 1
118
+ props = [nt] # d_1
119
+ for _ in range(k - 1):
120
+ pb = mx.array([pos], dtype=mx.int32)
121
+ nt, x, _ = D.step_compiled(x, pb, dmask, D._window(pos))
122
+ mx.async_eval(nt, x)
123
+ props.append(nt)
124
+ pos += 1
125
+ mx.eval(*props)
126
+ return [int(p) for p in props], pos
127
+
128
+ # ---------------- main loop ----------------
129
+
130
+ def generate(self, ids, n):
131
+ """greedy spec decode: n tokens after prompt ids.
132
+ Returns (tokens, decode_seconds, stats)."""
133
+ V, D, k = self.V, self.D, self.k
134
+ vmask = V.new_mask()
135
+ dmask = D.new_mask()
136
+ _, p0 = V.prefill(ids, vmask)
137
+ _, _ = D.prefill(ids, dmask)
138
+ # both consumed ids[:-1]; next input ids[-1] at position p0
139
+ pending = [int(ids[-1])]
140
+ vpos = dpos = p0
141
+ toks = []
142
+ rounds = accepted = proposed = 0
143
+ hist = [0] * (k + 1)
144
+ t0 = time.perf_counter()
145
+ while len(toks) < n:
146
+ # invariant: pending[-1] sits at position vpos for the verifier
147
+ # and at dpos + len(pending) - 1 for the draft — same slot.
148
+ assert dpos + len(pending) - 1 == vpos, (dpos, len(pending), vpos)
149
+ props, dpos_after = self._draft_chain(pending, dmask, dpos)
150
+ am = self._verify([pending[-1]] + props, vpos)
151
+ nacc = 0
152
+ for i in range(k):
153
+ if props[i] == am[i]:
154
+ nacc += 1
155
+ else:
156
+ break
157
+ rounds += 1
158
+ proposed += k
159
+ accepted += nacc
160
+ hist[nacc] += 1
161
+ base = vpos # position of pending[-1]
162
+ if nacc == k:
163
+ emitted = props + [am[k]]
164
+ pending = [props[k - 1], am[k]]
165
+ # draft consumed through d_{k-1} at base+k-1: no rewind
166
+ dpos = dpos_after
167
+ else:
168
+ emitted = props[:nacc] + [am[nacc]]
169
+ pending = [am[nacc]]
170
+ # draft rewind: valid through input d_nacc at base+nacc
171
+ lo, hi = base + nacc + 1, dpos_after
172
+ if hi > lo:
173
+ dmask[lo:hi] = NEG_INF
174
+ dpos = lo
175
+ vpos = base + nacc + 1 # verifier valid: <= base+nacc
176
+ toks.extend(emitted)
177
+ dt = time.perf_counter() - t0
178
+ toks = toks[:n]
179
+ self.stats = {
180
+ "k": k, "rounds": rounds, "proposed": proposed,
181
+ "accepted": accepted,
182
+ "acceptance": round(accepted / max(proposed, 1), 4),
183
+ "tokens_per_round": round(len(toks) / max(rounds, 1), 3),
184
+ "accept_hist": hist,
185
+ }
186
+ return toks, dt, self.stats
187
+
188
+
189
+ # ---------------- gate + bench drivers ----------------
190
+
191
+ GATE_PROMPTS = {
192
+ "facts": "The three laws of thermodynamics state that",
193
+ "prose": "Once upon a time, in a quiet village by the sea,",
194
+ "chat": "User: How do I reverse a list in Python?\nAssistant:",
195
+ }
196
+
197
+
198
+ def specgate(verifier, draft, tok, k=4, lens=(32, 256), receipt=None):
199
+ """[G1 frozen] WITH --spec vs WITHOUT: token-identical at each length,
200
+ every prompt. Any mismatch = flag does not ship."""
201
+ sd = SpecDecoder(verifier, draft, k=k)
202
+ out = {"gate": "specgate", "k": k, "lens": list(lens), "prompts": {},
203
+ "verdict": "PASS"}
204
+ for name, text in GATE_PROMPTS.items():
205
+ ids = tok(text, add_special_tokens=False).input_ids if tok else None
206
+ assert ids, "specgate needs a tokenizer"
207
+ res = {}
208
+ for n in lens:
209
+ plain, _ = verifier.generate(ids, n)
210
+ spec, _, st = sd.generate(ids, n)
211
+ same = plain == spec
212
+ first = next((i for i, (a, b) in enumerate(zip(plain, spec))
213
+ if a != b), -1)
214
+ res[str(n)] = {"identical": same, "first_divergence": first,
215
+ "acceptance": st["acceptance"]}
216
+ if not same:
217
+ out["verdict"] = "FAIL"
218
+ print(f" [{name} n={n}] identical={same} "
219
+ f"acc={st['acceptance']:.3f}", flush=True)
220
+ out["prompts"][name] = res
221
+ if receipt:
222
+ json.dump(out, open(receipt, "w"), indent=1)
223
+ print(f"receipt -> {receipt}")
224
+ return out
225
+
226
+
227
+ def specbench(verifier, draft, tok, k=4, n=512, runs=3, receipt=None):
228
+ """[G2 frozen] plain-8B vs 8B+draft medians, 3 text types, same process."""
229
+ sd = SpecDecoder(verifier, draft, k=k)
230
+ out = {"bench": "specbench", "k": k, "n": n, "runs": runs, "types": {}}
231
+ for name, text in GATE_PROMPTS.items():
232
+ ids = tok(text, add_special_tokens=False).input_ids
233
+ plain_r, spec_r, accs = [], [], []
234
+ for r in range(runs):
235
+ t, dt = verifier.generate(ids, n)
236
+ plain_r.append(n / dt)
237
+ st_toks, sdt, st = sd.generate(ids, n)
238
+ spec_r.append(n / sdt)
239
+ accs.append(st["acceptance"])
240
+ print(f" [{name} run{r}] plain {plain_r[-1]:.1f} tok/s | "
241
+ f"spec {spec_r[-1]:.1f} tok/s | acc {accs[-1]:.3f} | "
242
+ f"tok/round {st['tokens_per_round']}", flush=True)
243
+ med = lambda v: sorted(v)[len(v) // 2]
244
+ out["types"][name] = {
245
+ "plain_median_tok_s": round(med(plain_r), 2),
246
+ "spec_median_tok_s": round(med(spec_r), 2),
247
+ "speedup": round(med(spec_r) / med(plain_r), 3),
248
+ "acceptance_median": round(med(accs), 4),
249
+ "plain_runs": [round(v, 2) for v in plain_r],
250
+ "spec_runs": [round(v, 2) for v in spec_r],
251
+ }
252
+ out["peak_mem_gib"] = round(mx.get_peak_memory() / 2**30, 2)
253
+ if receipt:
254
+ json.dump(out, open(receipt, "w"), indent=1)
255
+ print(f"receipt -> {receipt}")
256
+ return out
mlx/fermion_mlx/trit_kernels.py ADDED
@@ -0,0 +1,877 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Five-value {0, +-s_lo, +-s_hi} 3-bit-planar GEMV as mx.fast.metal_kernel.
3
+
4
+ Decode: per output row, three bit planes (bp=positive, bn=negative, br=high-mag),
5
+ LSB-first. sign = p - n, magnitude selects per-row s_lo / s_hi. The kernel keeps
6
+ two fp32 accumulators (lo/hi) so the per-row scales multiply once at the end —
7
+ the inner loop is pure bit-extract + FMA (the Metal twin of the NEON tbl /
8
+ CUDA PRMT decode; MSL has no byte shuffle, ALU decode is the standard move).
9
+
10
+ Layout: one simdgroup per output row, SG_PER_TG simdgroups per threadgroup.
11
+ Lanes stride uchar4 loads across the row's plane bytes; simd_sum reduction.
12
+
13
+ Template flags (baked into per-shape kernel source, cached):
14
+ HAS_BIAS : add fp32 bias[row]
15
+ FUSE_NORM : x is pre-norm; kernel computes rmsnorm(x)*g inline (g = extra input)
16
+ ADD_RESID : out[row] = resid[row] + y
17
+ GLU : down-proj mode; x has 2*INTER entries [gate;up], col value is
18
+ silu(gate[c]) * up[c] (fuses the SwiGLU elementwise pass into GEMV)
19
+ """
20
+ import mlx.core as mx
21
+
22
+ _HEADER = """
23
+ static inline float trit_silu(float v) { return v / (1.0f + metal::exp(-v)); }
24
+ """
25
+
26
+ _CACHE = {}
27
+
28
+
29
+ def _norm_pre(cols, eps):
30
+ return f"""
31
+ float ssq = 0.0f;
32
+ for (uint c = lane; c < {cols}u; c += 32u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
33
+ ssq = metal::simd_sum(ssq);
34
+ float rms = metal::rsqrt(ssq / {float(cols)}f + {eps:.9g}f);
35
+ """
36
+
37
+
38
+ def _gemv_source(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
39
+ """v1: per-bit scalar decode with sparsity branches."""
40
+ assert rs % 4 == 0, "row_stride must be multiple of 4 for uchar4 loads"
41
+ tail = rs * 8 != cols
42
+ # x effective value macro
43
+ if glu:
44
+ xv = "trit_silu(x[c]) * x[c + %d]" % cols
45
+ norm_pre = ""
46
+ elif fuse_norm:
47
+ xv = "x[c] * nw[c] * rms"
48
+ norm_pre = _norm_pre(cols, eps)
49
+ else:
50
+ xv = "x[c]"
51
+ norm_pre = ""
52
+ guard = f"if (c >= {cols}u) break;" if tail else ""
53
+ return f"""
54
+ uint tid = thread_position_in_threadgroup.x;
55
+ uint lane = tid & 31u;
56
+ uint sg = tid >> 5u;
57
+ uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
58
+ if (row >= {rows}u) return;
59
+ {norm_pre}
60
+ const device uchar4* p4 = (const device uchar4*)bp + row * {rs // 4}u;
61
+ const device uchar4* n4 = (const device uchar4*)bn + row * {rs // 4}u;
62
+ const device uchar4* h4 = (const device uchar4*)br + row * {rs // 4}u;
63
+ float accLo = 0.0f, accHi = 0.0f;
64
+ for (uint i = lane; i < {rs // 4}u; i += 32u) {{
65
+ uchar4 pb = p4[i], nb = n4[i], hb = h4[i];
66
+ uint cb = i * 32u;
67
+ for (uint k = 0u; k < 4u; ++k) {{
68
+ uint pw = pb[k], nw2 = nb[k], hw = hb[k];
69
+ if ((pw | nw2) == 0u) continue;
70
+ for (uint j = 0u; j < 8u; ++j) {{
71
+ uint c = cb + k * 8u + j;
72
+ {guard}
73
+ float s = (float)((pw >> j) & 1u) - (float)((nw2 >> j) & 1u);
74
+ if (s == 0.0f) continue;
75
+ float v = s * ({xv});
76
+ if ((hw >> j) & 1u) accHi += v; else accLo += v;
77
+ }}
78
+ }}
79
+ }}
80
+ accLo = metal::simd_sum(accLo);
81
+ accHi = metal::simd_sum(accHi);
82
+ if (lane == 0u) {{
83
+ float y = metal::fma(slo[row], accLo, shi[row] * accHi);
84
+ {"y += bias[row];" if has_bias else ""}
85
+ {"y += resid[row];" if add_resid else ""}
86
+ out[row] = y;
87
+ }}
88
+ """
89
+
90
+
91
+ def _gemv_source_v2(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
92
+ """v2: branchless decode. Planes loaded as uint32 (32 weights per load),
93
+ per-4-bit float4 select against float4 x loads. Requires cols % 32 == 0
94
+ and rs % 4 == 0 with rs*8 == cols (true for all Qwen shapes)."""
95
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0
96
+ if glu:
97
+ xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
98
+ float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
99
+ norm_pre = ""
100
+ xpre = f"""
101
+ const device float4* xg4 = (const device float4*)x;
102
+ const device float4* xu4 = (const device float4*)(x + {cols}u);"""
103
+ elif fuse_norm:
104
+ xload = """float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"""
105
+ norm_pre = _norm_pre(cols, eps)
106
+ xpre = """
107
+ const device float4* x4 = (const device float4*)x;
108
+ const device float4* nw4 = (const device float4*)nw;"""
109
+ else:
110
+ xload = """float4 xv = x4[w * 8u + k];"""
111
+ norm_pre = ""
112
+ xpre = """
113
+ const device float4* x4 = (const device float4*)x;"""
114
+ return f"""
115
+ uint tid = thread_position_in_threadgroup.x;
116
+ uint lane = tid & 31u;
117
+ uint sg = tid >> 5u;
118
+ uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
119
+ if (row >= {rows}u) return;
120
+ {norm_pre}{xpre}
121
+ const device uint* p32 = (const device uint*)bp + row * {rs // 4}u;
122
+ const device uint* n32 = (const device uint*)bn + row * {rs // 4}u;
123
+ const device uint* h32 = (const device uint*)br + row * {rs // 4}u;
124
+ float4 accLo4 = 0.0f, accHi4 = 0.0f;
125
+ for (uint w = lane; w < {rs // 4}u; w += 32u) {{
126
+ uint pw = p32[w], nw2 = n32[w], hw = h32[w];
127
+ for (uint k = 0u; k < 8u; ++k) {{
128
+ {xload}
129
+ uint sh = k * 4u;
130
+ float4 s = float4(float((pw >> sh) & 1u), float((pw >> (sh+1u)) & 1u),
131
+ float((pw >> (sh+2u)) & 1u), float((pw >> (sh+3u)) & 1u))
132
+ - float4(float((nw2 >> sh) & 1u), float((nw2 >> (sh+1u)) & 1u),
133
+ float((nw2 >> (sh+2u)) & 1u), float((nw2 >> (sh+3u)) & 1u));
134
+ float4 v = s * xv;
135
+ bool4 hi = bool4((hw >> sh) & 1u, (hw >> (sh+1u)) & 1u,
136
+ (hw >> (sh+2u)) & 1u, (hw >> (sh+3u)) & 1u);
137
+ accHi4 += metal::select(float4(0.0f), v, hi);
138
+ accLo4 += metal::select(v, float4(0.0f), hi);
139
+ }}
140
+ }}
141
+ float accLo = metal::simd_sum(accLo4.x + accLo4.y + accLo4.z + accLo4.w);
142
+ float accHi = metal::simd_sum(accHi4.x + accHi4.y + accHi4.z + accHi4.w);
143
+ if (lane == 0u) {{
144
+ float y = metal::fma(slo[row], accLo, shi[row] * accHi);
145
+ {"y += bias[row];" if has_bias else ""}
146
+ {"y += resid[row];" if add_resid else ""}
147
+ out[row] = y;
148
+ }}
149
+ """
150
+
151
+
152
+ def _s8_source(rows, cols, rs, sg_per_tg, fuse_norm, eps):
153
+ assert rs % 4 == 0
154
+ if fuse_norm:
155
+ xv = lambda c: f"x[{c}] * nw[{c}] * rms"
156
+ norm_pre = f"""
157
+ float ssq = 0.0f;
158
+ for (uint c = lane; c < {cols}u; c += 32u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
159
+ ssq = metal::simd_sum(ssq);
160
+ float rms = metal::rsqrt(ssq / {float(cols)}f + {eps:.9g}f);
161
+ """
162
+ else:
163
+ xv = lambda c: f"x[{c}]"
164
+ norm_pre = ""
165
+ assert rs * 8 >= cols and rs >= cols, "int8 record: expect row_stride >= cols"
166
+ nvec = cols // 4
167
+ return f"""
168
+ uint tid = thread_position_in_threadgroup.x;
169
+ uint lane = tid & 31u;
170
+ uint sg = tid >> 5u;
171
+ uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
172
+ if (row >= {rows}u) return;
173
+ {norm_pre}
174
+ const device char* w = (const device char*)W + (ulong)row * {rs}u;
175
+ float acc = 0.0f;
176
+ for (uint i = lane * 4u; i + 3u < {cols}u; i += 128u) {{
177
+ char4 wv = *(const device char4*)(w + i);
178
+ uint c = i;
179
+ acc = metal::fma((float)wv.x, {xv('c')}, acc);
180
+ acc = metal::fma((float)wv.y, {xv('c + 1u')}, acc);
181
+ acc = metal::fma((float)wv.z, {xv('c + 2u')}, acc);
182
+ acc = metal::fma((float)wv.w, {xv('c + 3u')}, acc);
183
+ }}
184
+ acc = metal::simd_sum(acc);
185
+ if (lane == 0u) out[row] = scale[row] * acc;
186
+ """ if nvec * 4 == cols else None
187
+
188
+
189
+ SG_PER_TG = int(__import__("os").environ.get("TRIT_METAL_SGPTG", "8"))
190
+
191
+
192
+ import os
193
+ KERNEL_VERSION = int(os.environ.get("TRIT_METAL_V", "1"))
194
+ RPSG = int(os.environ.get("TRIT_METAL_RPSG", "8"))
195
+
196
+
197
+ def _gemv_source_v3(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
198
+ rpsg=RPSG):
199
+ """v3: rpsg rows per simdgroup; x (and silu/norm work) loaded once per
200
+ column window and reused across rows in registers — kills the per-row
201
+ x-reload that bound v1/v2 at ~20 GB/s. 4-wide horizontal accumulate."""
202
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % rpsg == 0
203
+ if glu:
204
+ xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
205
+ float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
206
+ norm_pre = ""
207
+ xpre = f"""
208
+ const device float4* xg4 = (const device float4*)x;
209
+ const device float4* xu4 = (const device float4*)(x + {cols}u);"""
210
+ elif fuse_norm:
211
+ xload = "float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"
212
+ norm_pre = _norm_pre(cols, eps)
213
+ xpre = """
214
+ const device float4* x4 = (const device float4*)x;
215
+ const device float4* nw4 = (const device float4*)nw;"""
216
+ else:
217
+ xload = "float4 xv = x4[w * 8u + k];"
218
+ norm_pre = ""
219
+ xpre = """
220
+ const device float4* x4 = (const device float4*)x;"""
221
+ return f"""
222
+ uint tid = thread_position_in_threadgroup.x;
223
+ uint lane = tid & 31u;
224
+ uint sg = tid >> 5u;
225
+ uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
226
+ if (row0 >= {rows}u) return;
227
+ {norm_pre}{xpre}
228
+ const device uint* p32 = (const device uint*)bp;
229
+ const device uint* n32 = (const device uint*)bn;
230
+ const device uint* h32 = (const device uint*)br;
231
+ float accLo[{rpsg}], accHi[{rpsg}];
232
+ for (uint r = 0u; r < {rpsg}u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
233
+ const float4 one4 = float4(1.0f);
234
+ for (uint w = lane; w < {rs // 4}u; w += 32u) {{
235
+ uint pw[{rpsg}], nw2[{rpsg}], hw[{rpsg}];
236
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
237
+ ulong off = (ulong)(row0 + r) * {rs // 4}u + w;
238
+ pw[r] = p32[off]; nw2[r] = n32[off]; hw[r] = h32[off];
239
+ }}
240
+ for (uint k = 0u; k < 8u; ++k) {{
241
+ {xload}
242
+ uint sh = k * 4u;
243
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
244
+ float4 s = float4(float((pw[r] >> sh) & 1u), float((pw[r] >> (sh+1u)) & 1u),
245
+ float((pw[r] >> (sh+2u)) & 1u), float((pw[r] >> (sh+3u)) & 1u))
246
+ - float4(float((nw2[r] >> sh) & 1u), float((nw2[r] >> (sh+1u)) & 1u),
247
+ float((nw2[r] >> (sh+2u)) & 1u), float((nw2[r] >> (sh+3u)) & 1u));
248
+ float4 v = s * xv;
249
+ bool4 hi = bool4((hw[r] >> sh) & 1u, (hw[r] >> (sh+1u)) & 1u,
250
+ (hw[r] >> (sh+2u)) & 1u, (hw[r] >> (sh+3u)) & 1u);
251
+ accHi[r] += metal::dot(metal::select(float4(0.0f), v, hi), one4);
252
+ accLo[r] += metal::dot(metal::select(v, float4(0.0f), hi), one4);
253
+ }}
254
+ }}
255
+ }}
256
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
257
+ float lo = metal::simd_sum(accLo[r]);
258
+ float hi = metal::simd_sum(accHi[r]);
259
+ if (lane == 0u) {{
260
+ uint row = row0 + r;
261
+ float y = metal::fma(slo[row], lo, shi[row] * hi);
262
+ {"y += bias[row];" if has_bias else ""}
263
+ {"y += resid[row];" if add_resid else ""}
264
+ out[row] = y;
265
+ }}
266
+ }}
267
+ """
268
+
269
+
270
+ def _gemv_source_v4(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
271
+ rpsg=RPSG):
272
+ """v4: threadgroup-memory LUT decode (Metal twin of NEON vqtbl1q).
273
+ Per 4-column group g, lut[g][m] = sum of xv[4g+i] over set bits of m
274
+ (16 subset sums). Split planes on the fly into lo/hi pos/neg nibbles
275
+ (a=p&~h, b=n&~h, c=p&h, d=n&h): 4 weights cost 4 LUT loads + 4 adds.
276
+ LUT stride 17 floats kills the 16-float bank-conflict pattern. Tiled over
277
+ columns when the LUT would exceed threadgroup memory (down proj)."""
278
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % rpsg == 0
279
+ # column tiling: LUT floats = tile/4*17, xbuf floats = tile -> keep <= ~26KB
280
+ ntiles = 1
281
+ while (cols // ntiles) % 32 != 0 or (cols // ntiles) * (17 / 4 + 1) * 4 > 26624:
282
+ ntiles += 1
283
+ assert ntiles <= 16
284
+ tile = cols // ntiles
285
+ gpt = tile // 4 # groups per tile
286
+ w0 = tile // 32 # 32-weight words per tile (per row)
287
+ nthreads = sg_per_tg * 32
288
+ if glu:
289
+ xv_expr = "{ float gg = x[c]; xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + %du]; }" % cols
290
+ elif fuse_norm:
291
+ xv_expr = "xb[cl] = x[c] * nw[c] * rms;"
292
+ else:
293
+ xv_expr = "xb[cl] = x[c];"
294
+ if fuse_norm:
295
+ norm_pre = f"""
296
+ threadgroup float ssq_sh[{sg_per_tg}];
297
+ {{
298
+ float ssq = 0.0f;
299
+ for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
300
+ ssq = metal::simd_sum(ssq);
301
+ if (lane == 0u) ssq_sh[sg] = ssq;
302
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
303
+ float t = 0.0f;
304
+ for (uint i = 0u; i < {sg_per_tg}u; ++i) t += ssq_sh[i];
305
+ rms = metal::rsqrt(t / {float(cols)}f + {eps:.9g}f);
306
+ }}
307
+ """
308
+ else:
309
+ norm_pre = ""
310
+ return f"""
311
+ uint tid = thread_position_in_threadgroup.x;
312
+ uint lane = tid & 31u;
313
+ uint sg = tid >> 5u;
314
+ uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
315
+ float rms = 1.0f;
316
+ {norm_pre}
317
+ threadgroup float xb[{tile}];
318
+ threadgroup float lut[{gpt} * 17];
319
+ const device uint* p32 = (const device uint*)bp;
320
+ const device uint* n32 = (const device uint*)bn;
321
+ const device uint* h32 = (const device uint*)br;
322
+ float accLo[{rpsg}], accHi[{rpsg}];
323
+ for (uint r = 0u; r < {rpsg}u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
324
+ for (uint t = 0u; t < {ntiles}u; ++t) {{
325
+ // stage effective x for this tile
326
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
327
+ for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
328
+ uint c = t * {tile}u + cl;
329
+ {xv_expr}
330
+ }}
331
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
332
+ // build 16 subset sums per 4-col group
333
+ for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
334
+ uint g = e >> 4u, m = e & 15u;
335
+ const threadgroup float* xg = xb + g * 4u;
336
+ float v = 0.0f;
337
+ if (m & 1u) v += xg[0];
338
+ if (m & 2u) v += xg[1];
339
+ if (m & 4u) v += xg[2];
340
+ if (m & 8u) v += xg[3];
341
+ lut[g * 17u + m] = v;
342
+ }}
343
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
344
+ if (row0 < {rows}u) {{
345
+ for (uint wl = lane; wl < {w0}u; wl += 32u) {{
346
+ uint w = t * {w0}u + wl;
347
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
348
+ ulong off = (ulong)(row0 + r) * {rs // 4}u + w;
349
+ uint pw = p32[off], nw2 = n32[off], hw = h32[off];
350
+ uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
351
+ uint gbase = wl * 8u;
352
+ float lo = 0.0f, hi = 0.0f;
353
+ for (uint k = 0u; k < 8u; ++k) {{
354
+ const threadgroup float* lg = lut + (gbase + k) * 17u;
355
+ uint sh = k * 4u;
356
+ lo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
357
+ hi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
358
+ }}
359
+ accLo[r] += lo; accHi[r] += hi;
360
+ }}
361
+ }}
362
+ }}
363
+ }}
364
+ if (row0 >= {rows}u) return;
365
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
366
+ float lo = metal::simd_sum(accLo[r]);
367
+ float hi = metal::simd_sum(accHi[r]);
368
+ if (lane == 0u) {{
369
+ uint row = row0 + r;
370
+ float y = metal::fma(slo[row], lo, shi[row] * hi);
371
+ {"y += bias[row];" if has_bias else ""}
372
+ {"y += resid[row];" if add_resid else ""}
373
+ out[row] = y;
374
+ }}
375
+ }}
376
+ """
377
+
378
+
379
+ import os as _os
380
+ _TILE_MAX = int(_os.environ.get("TRIT_METAL_TILE", "448"))
381
+
382
+
383
+ def _v5_tile(cols):
384
+ """largest tile <= TILE_MAX with tile % 32 == 0 and cols % tile == 0."""
385
+ for t in range(_TILE_MAX, 31, -32):
386
+ if cols % t == 0 and t % 32 == 0:
387
+ return t
388
+ raise ValueError(cols)
389
+
390
+
391
+ def _gemv_source_v5(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
392
+ rpsg=8):
393
+ """v5 = v4 LUT decode with the two occupancy/bandwidth fixes:
394
+ (1) planes pre-interleaved on load: uint32 words stored [row_block, word,
395
+ row_in_block] so a lane's 8-row load is 32 contiguous bytes (streams
396
+ at DRAM bandwidth; the v3/v4 scattered pattern ran at 59% of it);
397
+ (2) small LUT tiles (<= ~7.6KB threadgroup memory) so several threadgroups
398
+ fit per GPU core (v4's 19-26KB allowed only one, exposing latency).
399
+ Requires planes packed with pack_planes_v5. rpsg fixed at 8 by layout."""
400
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
401
+ tile = _v5_tile(cols)
402
+ ntiles = cols // tile
403
+ gpt = tile // 4
404
+ w0 = tile // 32
405
+ W = rs // 4
406
+ nthreads = sg_per_tg * 32
407
+ if glu:
408
+ stage = f"""
409
+ for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
410
+ uint c = t * {tile}u + cl;
411
+ float gg = x[c];
412
+ xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + {cols}u];
413
+ }}
414
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);"""
415
+ xval = "xb[gl * 4u + i]"
416
+ xb_decl = f"threadgroup float xb[{tile}];"
417
+ elif fuse_norm:
418
+ stage = ""
419
+ xval = "x[t * %du + gl * 4u + i] * nw[t * %du + gl * 4u + i] * rms" % (tile, tile)
420
+ xb_decl = ""
421
+ else:
422
+ stage = ""
423
+ xval = "x[t * %du + gl * 4u + i]" % tile
424
+ xb_decl = ""
425
+ if fuse_norm:
426
+ norm_pre = f"""
427
+ threadgroup float ssq_sh[{sg_per_tg}];
428
+ {{
429
+ float ssq = 0.0f;
430
+ for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
431
+ ssq = metal::simd_sum(ssq);
432
+ if (lane == 0u) ssq_sh[sg] = ssq;
433
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
434
+ float tt = 0.0f;
435
+ for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
436
+ rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
437
+ }}
438
+ """
439
+ else:
440
+ norm_pre = ""
441
+ return f"""
442
+ uint tid = thread_position_in_threadgroup.x;
443
+ uint lane = tid & 31u;
444
+ uint sg = tid >> 5u;
445
+ uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg; // 8-row block
446
+ uint row0 = blk * 8u;
447
+ float rms = 1.0f;
448
+ {norm_pre}
449
+ {xb_decl}
450
+ threadgroup float lut[{gpt} * 17];
451
+ const device uint* p32 = (const device uint*)bp;
452
+ const device uint* n32 = (const device uint*)bn;
453
+ const device uint* h32 = (const device uint*)br;
454
+ float accLo[8], accHi[8];
455
+ for (uint r = 0u; r < 8u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
456
+ for (uint t = 0u; t < {ntiles}u; ++t) {{
457
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
458
+ {stage}
459
+ for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
460
+ uint gl = e >> 4u, m = e & 15u;
461
+ float v = 0.0f;
462
+ for (uint i = 0u; i < 4u; ++i)
463
+ if ((m >> i) & 1u) v += {xval};
464
+ lut[gl * 17u + m] = v;
465
+ }}
466
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
467
+ if (row0 < {rows}u) {{
468
+ for (uint wl = lane; wl < {w0}u; wl += 32u) {{
469
+ uint w = t * {w0}u + wl;
470
+ ulong base = ((ulong)blk * {W}u + w) * 8u; // 8 rows contiguous
471
+ uint gbase = wl * 8u;
472
+ for (uint r = 0u; r < 8u; ++r) {{
473
+ uint pw = p32[base + r], nw2 = n32[base + r], hw = h32[base + r];
474
+ uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
475
+ float lo = 0.0f, hi = 0.0f;
476
+ for (uint k = 0u; k < 8u; ++k) {{
477
+ const threadgroup float* lg = lut + (gbase + k) * 17u;
478
+ uint sh = k * 4u;
479
+ lo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
480
+ hi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
481
+ }}
482
+ accLo[r] += lo; accHi[r] += hi;
483
+ }}
484
+ }}
485
+ }}
486
+ }}
487
+ if (row0 >= {rows}u) return;
488
+ for (uint r = 0u; r < 8u; ++r) {{
489
+ float lo = metal::simd_sum(accLo[r]);
490
+ float hi = metal::simd_sum(accHi[r]);
491
+ if (lane == 0u) {{
492
+ uint row = row0 + r;
493
+ float y = metal::fma(slo[row], lo, shi[row] * hi);
494
+ {"y += bias[row];" if has_bias else ""}
495
+ {"y += resid[row];" if add_resid else ""}
496
+ out[row] = y;
497
+ }}
498
+ }}
499
+ """
500
+
501
+
502
+ def _gemv_source_v6(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
503
+ """v6: interleaved 8-row plane layout (pack_planes_v5) + mask-convert-fma
504
+ decode. For bit j of word a: (float)(a & (1<<j)) == 2^j when set, so
505
+ multiplying by x pre-scaled with 2^-j turns decode into AND + convert +
506
+ FMA (no shifts) — 4 masks (lo/hi x pos/neg) ~3.5 instr/weight, exact in
507
+ fp32 (power-of-two scales). x reused across the 8 rows in registers."""
508
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
509
+ W = rs // 4
510
+ if glu:
511
+ xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
512
+ float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
513
+ norm_pre = ""
514
+ xpre = f"""
515
+ const device float4* xg4 = (const device float4*)x;
516
+ const device float4* xu4 = (const device float4*)(x + {cols}u);"""
517
+ elif fuse_norm:
518
+ xload = "float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"
519
+ norm_pre = _norm_pre(cols, eps)
520
+ xpre = """
521
+ const device float4* x4 = (const device float4*)x;
522
+ const device float4* nw4 = (const device float4*)nw;"""
523
+ else:
524
+ xload = "float4 xv = x4[w * 8u + k];"
525
+ norm_pre = ""
526
+ xpre = """
527
+ const device float4* x4 = (const device float4*)x;"""
528
+ # unrolled k-body with literal masks / prescales
529
+ kbody = []
530
+ for k in range(8):
531
+ m = [1 << (4 * k + i) for i in range(4)]
532
+ ps = ", ".join("%.9ef" % (2.0 ** -(4 * k + i)) for i in range(4))
533
+ kbody.append(f""" {{
534
+ uint k = {k}u; {xload}
535
+ float4 xs = xv * float4({ps});
536
+ const uint4 m4 = uint4({m[0]}u, {m[1]}u, {m[2]}u, {m[3]}u);
537
+ for (uint r = 0u; r < 8u; ++r) {{
538
+ uint pw = pl[r], nw2 = nl[r], hw = hl[r];
539
+ uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
540
+ accLo[r] += metal::dot(float4(uint4(a) & m4) - float4(uint4(b) & m4), xs);
541
+ accHi[r] += metal::dot(float4(uint4(cc) & m4) - float4(uint4(d) & m4), xs);
542
+ }}
543
+ }}""")
544
+ kbody = "\n".join(kbody)
545
+ return f"""
546
+ uint tid = thread_position_in_threadgroup.x;
547
+ uint lane = tid & 31u;
548
+ uint sg = tid >> 5u;
549
+ uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
550
+ uint row0 = blk * 8u;
551
+ if (row0 >= {rows}u) return;
552
+ {norm_pre}{xpre}
553
+ const device uint* p32 = (const device uint*)bp;
554
+ const device uint* n32 = (const device uint*)bn;
555
+ const device uint* h32 = (const device uint*)br;
556
+ float accLo[8], accHi[8];
557
+ for (uint r = 0u; r < 8u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
558
+ for (uint w = lane; w < {W}u; w += 32u) {{
559
+ ulong base = ((ulong)blk * {W}u + w) * 8u;
560
+ uint pl[8], nl[8], hl[8];
561
+ for (uint r = 0u; r < 8u; ++r) {{ pl[r] = p32[base+r]; nl[r] = n32[base+r]; hl[r] = h32[base+r]; }}
562
+ {kbody}
563
+ }}
564
+ for (uint r = 0u; r < 8u; ++r) {{
565
+ float lo = metal::simd_sum(accLo[r]);
566
+ float hi = metal::simd_sum(accHi[r]);
567
+ if (lane == 0u) {{
568
+ uint row = row0 + r;
569
+ float y = metal::fma(slo[row], lo, shi[row] * hi);
570
+ {"y += bias[row];" if has_bias else ""}
571
+ {"y += resid[row];" if add_resid else ""}
572
+ out[row] = y;
573
+ }}
574
+ }}
575
+ """
576
+
577
+
578
+ def _gemv_source_v7(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
579
+ """v7: v5 interleaved layout + LUT decode with full lane utilization.
580
+ Lane = (row r = lane&7, word-slot = lane>>3): each lane accumulates ONE
581
+ row over words wslot, wslot+4, ... — all 32 lanes busy for any w0 >= 4.
582
+ Row sums gathered with two simd_shuffle_down folds. LUT tile 448 cols
583
+ (7.6KB threadgroup mem) so multiple threadgroups stay resident per core."""
584
+ assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
585
+ tile = _v5_tile(cols)
586
+ ntiles = cols // tile
587
+ gpt = tile // 4
588
+ w0 = tile // 32
589
+ W = rs // 4
590
+ nthreads = sg_per_tg * 32
591
+ if glu:
592
+ stage = f"""
593
+ for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
594
+ uint c = t * {tile}u + cl;
595
+ float gg = x[c];
596
+ xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + {cols}u];
597
+ }}
598
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);"""
599
+ xval = "xb[gl * 4u + i]"
600
+ xb_decl = f"threadgroup float xb[{tile}];"
601
+ elif fuse_norm:
602
+ stage = ""
603
+ xval = "x[t * %du + gl * 4u + i] * nw[t * %du + gl * 4u + i] * rms" % (tile, tile)
604
+ xb_decl = ""
605
+ else:
606
+ stage = ""
607
+ xval = "x[t * %du + gl * 4u + i]" % tile
608
+ xb_decl = ""
609
+ if fuse_norm:
610
+ norm_pre = f"""
611
+ threadgroup float ssq_sh[{sg_per_tg}];
612
+ {{
613
+ float ssq = 0.0f;
614
+ for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
615
+ ssq = metal::simd_sum(ssq);
616
+ if (lane == 0u) ssq_sh[sg] = ssq;
617
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
618
+ float tt = 0.0f;
619
+ for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
620
+ rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
621
+ }}
622
+ """
623
+ else:
624
+ norm_pre = ""
625
+ return f"""
626
+ uint tid = thread_position_in_threadgroup.x;
627
+ uint lane = tid & 31u;
628
+ uint sg = tid >> 5u;
629
+ uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
630
+ uint row0 = blk * 8u;
631
+ uint r = lane & 7u;
632
+ uint wsl = lane >> 3u;
633
+ float rms = 1.0f;
634
+ {norm_pre}
635
+ {xb_decl}
636
+ threadgroup float lut[{gpt} * 17];
637
+ const device uint* p32 = (const device uint*)bp;
638
+ const device uint* n32 = (const device uint*)bn;
639
+ const device uint* h32 = (const device uint*)br;
640
+ float accLo = 0.0f, accHi = 0.0f;
641
+ for (uint t = 0u; t < {ntiles}u; ++t) {{
642
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
643
+ {stage}
644
+ for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
645
+ uint gl = e >> 4u, m = e & 15u;
646
+ float v = 0.0f;
647
+ for (uint i = 0u; i < 4u; ++i)
648
+ if ((m >> i) & 1u) v += {xval};
649
+ lut[gl * 17u + m] = v;
650
+ }}
651
+ threadgroup_barrier(metal::mem_flags::mem_threadgroup);
652
+ if (row0 < {rows}u) {{
653
+ for (uint wl = wsl; wl < {w0}u; wl += 4u) {{
654
+ ulong base = ((ulong)blk * {W}u + t * {w0}u + wl) * 8u + r;
655
+ uint pw = p32[base], nw2 = n32[base], hw = h32[base];
656
+ uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
657
+ uint gbase = wl * 8u;
658
+ for (uint k = 0u; k < 8u; ++k) {{
659
+ const threadgroup float* lg = lut + (gbase + k) * 17u;
660
+ uint sh = k * 4u;
661
+ accLo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
662
+ accHi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
663
+ }}
664
+ }}
665
+ }}
666
+ }}
667
+ if (row0 >= {rows}u) return;
668
+ accLo += metal::simd_shuffle_down(accLo, 16u);
669
+ accLo += metal::simd_shuffle_down(accLo, 8u);
670
+ accHi += metal::simd_shuffle_down(accHi, 16u);
671
+ accHi += metal::simd_shuffle_down(accHi, 8u);
672
+ if (lane < 8u) {{
673
+ uint row = row0 + lane;
674
+ float y = metal::fma(slo[row], accLo, shi[row] * accHi);
675
+ {"y += bias[row];" if has_bias else ""}
676
+ {"y += resid[row];" if add_resid else ""}
677
+ out[row] = y;
678
+ }}
679
+ """
680
+
681
+
682
+ def prep_planes(planes, cols=None):
683
+ """Trim row padding (row_stride*8 > cols in real exports; pad bits are
684
+ zero) so rs*8 == cols, then apply the layout transform required by the
685
+ active kernel version. planes = (bp, bn, br) mx.uint8 [rows, rs]."""
686
+ if cols is not None and cols % 8 == 0 and planes[0].shape[1] > cols // 8:
687
+ planes = tuple(mx.contiguous(p[:, :cols // 8]) for p in planes)
688
+ if KERNEL_VERSION >= 5: # v5/v6 interleaved layout
689
+ return tuple(pack_planes_v5(p) for p in planes)
690
+ return planes
691
+
692
+
693
+ def pack_planes_v5(plane_mx):
694
+ """[rows, rs] uint8 -> v5 interleaved layout (same byte count):
695
+ uint32 words reordered to [row_block(8), word, row_in_block]."""
696
+ rows, rs = plane_mx.shape
697
+ assert rows % 8 == 0 and rs % 4 == 0
698
+ w = plane_mx.view(mx.uint32).reshape(rows // 8, 8, rs // 4)
699
+ return mx.contiguous(w.transpose(0, 2, 1)).view(mx.uint8).reshape(rows, rs)
700
+
701
+
702
+ def _rows_per_sg(rows, cols, rs, version, glu):
703
+ if version == 3 and rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % RPSG == 0:
704
+ return RPSG
705
+ return 1
706
+
707
+
708
+ def get_trit_gemv(rows, cols, rs, has_bias=False, fuse_norm=False, add_resid=False,
709
+ glu=False, eps=1e-6, version=None):
710
+ version = KERNEL_VERSION if version is None else version
711
+ if version >= 2 and not (rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0):
712
+ version = 1 # v2+ layout preconditions not met, fall back
713
+ if version >= 3 and rows % (8 if version >= 5 else RPSG) != 0:
714
+ version = 2
715
+ key = ("trit", rows, cols, rs, has_bias, fuse_norm, add_resid, glu, round(eps, 12), version)
716
+ if key in _CACHE:
717
+ return _CACHE[key]
718
+ inputs = ["bp", "bn", "br", "slo", "shi", "x"]
719
+ if has_bias:
720
+ inputs.append("bias")
721
+ if fuse_norm and not glu:
722
+ inputs.append("nw")
723
+ if add_resid:
724
+ inputs.append("resid")
725
+ gen = {1: _gemv_source, 2: _gemv_source_v2, 3: _gemv_source_v3,
726
+ 4: _gemv_source_v4, 5: _gemv_source_v5, 6: _gemv_source_v6, 7: _gemv_source_v7}[version]
727
+ src = gen(rows, cols, rs, SG_PER_TG, has_bias, fuse_norm, add_resid, glu, eps)
728
+ name = "tritgemv%d_r%d_c%d_b%d_n%d_a%d_g%d" % (version, rows, cols, has_bias, fuse_norm,
729
+ add_resid, glu)
730
+ k = mx.fast.metal_kernel(name=name, input_names=inputs, output_names=["out"],
731
+ source=src, header=_HEADER)
732
+ _CACHE[key] = (k, inputs, version)
733
+ return _CACHE[key]
734
+
735
+
736
+ def _s8_source_v2(rows, cols, rs, sg_per_tg, fuse_norm, eps, rpsg=RPSG):
737
+ """v2 int8 GEMV: rpsg rows per simdgroup, float4 x reuse across rows."""
738
+ assert rs % 4 == 0 and cols % 4 == 0 and rows % rpsg == 0
739
+ if fuse_norm:
740
+ xload = "float4 xv = x4[i] * nw4[i] * rms;"
741
+ norm_pre = _norm_pre(cols, eps)
742
+ xpre = """
743
+ const device float4* x4 = (const device float4*)x;
744
+ const device float4* nw4 = (const device float4*)nw;"""
745
+ else:
746
+ xload = "float4 xv = x4[i];"
747
+ norm_pre = ""
748
+ xpre = """
749
+ const device float4* x4 = (const device float4*)x;"""
750
+ return f"""
751
+ uint tid = thread_position_in_threadgroup.x;
752
+ uint lane = tid & 31u;
753
+ uint sg = tid >> 5u;
754
+ uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
755
+ if (row0 >= {rows}u) return;
756
+ {norm_pre}{xpre}
757
+ float acc[{rpsg}];
758
+ for (uint r = 0u; r < {rpsg}u; ++r) acc[r] = 0.0f;
759
+ const float4 one4 = float4(1.0f);
760
+ for (uint i = lane; i < {cols // 4}u; i += 32u) {{
761
+ {xload}
762
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
763
+ char4 wv = *(const device char4*)((const device char*)W
764
+ + (ulong)(row0 + r) * {rs}u + i * 4u);
765
+ acc[r] += metal::dot(float4(wv.x, wv.y, wv.z, wv.w) * xv, one4);
766
+ }}
767
+ }}
768
+ for (uint r = 0u; r < {rpsg}u; ++r) {{
769
+ float a = metal::simd_sum(acc[r]);
770
+ if (lane == 0u) out[row0 + r] = scale[row0 + r] * a;
771
+ }}
772
+ """
773
+
774
+
775
+ def get_s8_gemv(rows, cols, rs, fuse_norm=False, eps=1e-6, version=None):
776
+ version = KERNEL_VERSION if version is None else version
777
+ v = 2 if (version >= 2 and rs % 4 == 0 and cols % 4 == 0 and rows % RPSG == 0) else 1
778
+ key = ("s8", rows, cols, rs, fuse_norm, round(eps, 12), v)
779
+ if key in _CACHE:
780
+ return _CACHE[key]
781
+ inputs = ["W", "scale", "x"]
782
+ if fuse_norm:
783
+ inputs.append("nw")
784
+ src = (_s8_source_v2 if v == 2 else _s8_source)(rows, cols, rs, SG_PER_TG, fuse_norm, eps)
785
+ assert src is not None, "cols must be multiple of 4"
786
+ name = "s8gemv%d_r%d_c%d_n%d" % (v, rows, cols, fuse_norm)
787
+ k = mx.fast.metal_kernel(name=name, input_names=inputs, output_names=["out"],
788
+ source=src, header=_HEADER)
789
+ _CACHE[key] = (k, inputs, v)
790
+ return _CACHE[key]
791
+
792
+
793
+ def _grid_for(rows, rpsg=1):
794
+ nsg = (rows + rpsg - 1) // rpsg # one simdgroup per rpsg rows
795
+ ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
796
+ return (ntg * SG_PER_TG * 32, 1, 1), (SG_PER_TG * 32, 1, 1)
797
+
798
+
799
+ def trit_gemv(planes, slo, shi, x, bias=None, norm_w=None, resid=None, glu=False,
800
+ out_rows=None, eps=1e-6, stream=None):
801
+ """planes = (bp, bn, br) mx.uint8 [rows, rs]. x fp32. Returns fp32 [out_rows]."""
802
+ bp, bn, br = planes
803
+ rows, rs = bp.shape
804
+ out_rows = out_rows if out_rows is not None else rows
805
+ cols = x.shape[0] if not glu else x.shape[0] // 2
806
+ k, names, version = get_trit_gemv(rows, cols, rs, bias is not None, norm_w is not None,
807
+ resid is not None, glu, eps)
808
+ vals = {"bp": bp, "bn": bn, "br": br, "slo": slo, "shi": shi, "x": x,
809
+ "bias": bias, "nw": norm_w, "resid": resid}
810
+ grid, tg = _grid_for(out_rows, 8 if version >= 5 else (RPSG if version >= 3 else 1))
811
+ return k(inputs=[vals[n] for n in names], grid=grid, threadgroup=tg,
812
+ output_shapes=[(out_rows,)], output_dtypes=[mx.float32], stream=stream)[0]
813
+
814
+
815
+ def s8_gemv(W, scale, x, norm_w=None, eps=1e-6, stream=None):
816
+ rows, rs = W.shape
817
+ cols = x.shape[0]
818
+ k, names, v = get_s8_gemv(rows, cols, rs, norm_w is not None, eps)
819
+ vals = {"W": W, "scale": scale, "x": x, "nw": norm_w}
820
+ grid, tg = _grid_for(rows, RPSG if v == 2 else 1)
821
+ return k(inputs=[vals[n] for n in names], grid=grid, threadgroup=tg,
822
+ output_shapes=[(rows,)], output_dtypes=[mx.float32], stream=stream)[0]
823
+
824
+
825
+ # ---------------- fused rope + KV cache append ----------------
826
+
827
+ def get_rope_append(n_heads, n_kv, hd, maxseq):
828
+ """One dispatch: rope q (n_heads*hd) and k (n_kv*hd) from the fused qkv
829
+ vector, write roped k and raw v in-place into the preallocated caches at
830
+ position pos (device scalar), output roped q. NeoX rotate-half, full hd.
831
+ kc/vc passed as inputs and written in place (stream-serial ordering with
832
+ the consumer sdpa; correctness enforced by the e2e gate)."""
833
+ key = ("rope_append", n_heads, n_kv, hd, maxseq)
834
+ if key in _CACHE:
835
+ return _CACHE[key]
836
+ qd, kd = n_heads * hd, n_kv * hd
837
+ h2 = hd // 2
838
+ src = f"""
839
+ uint i = thread_position_in_grid.x; // one thread per q/k element
840
+ uint pos = (uint)posbuf[0];
841
+ device float* kc_w = (device float*)kc;
842
+ device float* vc_w = (device float*)vc;
843
+ if (i == 0u) {{ ((device float*)mask)[pos] = 0.0f; }}
844
+ if (i < {qd}u + {kd}u) {{
845
+ uint hidx = i / {hd}u, d = i % {hd}u;
846
+ float c = ct[pos * {h2}u + (d % {h2}u)];
847
+ float sn = st[pos * {h2}u + (d % {h2}u)];
848
+ float v = qkv[i];
849
+ float vrot = (d < {h2}u) ? -qkv[hidx * {hd}u + d + {h2}u]
850
+ : qkv[hidx * {hd}u + d - {h2}u];
851
+ float r = metal::fma(v, c, vrot * sn);
852
+ if (i < {qd}u) {{
853
+ qout[i] = r;
854
+ }} else {{
855
+ uint kh = (i - {qd}u) / {hd}u;
856
+ kc_w[(ulong)(kh * {maxseq}u + pos) * {hd}u + d] = r;
857
+ }}
858
+ }} else if (i < {qd}u + 2u * {kd}u) {{
859
+ uint j = i - {qd}u - {kd}u;
860
+ uint vh = j / {hd}u, d = j % {hd}u;
861
+ vc_w[(ulong)(vh * {maxseq}u + pos) * {hd}u + d] = qkv[{qd}u + {kd}u + j];
862
+ }}
863
+ """
864
+ k = mx.fast.metal_kernel(name="rope_append_%d_%d_%d" % (n_heads, n_kv, hd),
865
+ input_names=["qkv", "kc", "vc", "ct", "st", "posbuf", "mask"],
866
+ output_names=["qout"], source=src)
867
+ _CACHE[key] = k
868
+ return k
869
+
870
+
871
+ def rope_append(qkv, kc, vc, ct, st, posbuf, mask, n_heads, n_kv, hd, maxseq, stream=None):
872
+ k = get_rope_append(n_heads, n_kv, hd, maxseq)
873
+ n = (n_heads + 2 * n_kv) * hd
874
+ nthr = ((n + 255) // 256) * 256
875
+ return k(inputs=[qkv, kc, vc, ct, st, posbuf, mask], grid=(nthr, 1, 1),
876
+ threadgroup=(256, 1, 1),
877
+ output_shapes=[(n_heads * hd,)], output_dtypes=[mx.float32], stream=stream)[0]
mlx/merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
mlx/receipts/bench_8b_wbase_m5_16gb_memcapped.json ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "mode": "bench",
3
+ "model": "artifacts/wbase_v4.bin",
4
+ "prompt_ids": [
5
+ 785,
6
+ 1376,
7
+ 4522,
8
+ 4815,
9
+ 71617,
10
+ 658,
11
+ 29728,
12
+ 14155,
13
+ 374
14
+ ],
15
+ "gen_tokens": 128,
16
+ "runs": [
17
+ {
18
+ "run": 0,
19
+ "tokens": 128,
20
+ "seconds": 5.003734500001883,
21
+ "tok_s": 25.580893630537716
22
+ },
23
+ {
24
+ "run": 1,
25
+ "tokens": 128,
26
+ "seconds": 5.117534165998222,
27
+ "tok_s": 25.012046006542374
28
+ },
29
+ {
30
+ "run": 2,
31
+ "tokens": 128,
32
+ "seconds": 5.271790290993522,
33
+ "tok_s": 24.280176739708118
34
+ }
35
+ ],
36
+ "median_tok_s": 25.012046006542374,
37
+ "peak_mem_gib": 3.671223647892475,
38
+ "utc": "2026-07-25T09:38:39Z"
39
+ }
mlx/receipts/pathgate_8b_cons3b25.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "n": 32,
3
+ "identical": true,
4
+ "first_divergence": null,
5
+ "fused": [
6
+ 429,
7
+ 807,
8
+ 646,
9
+ 4009,
10
+ 264,
11
+ 729,
12
+ 448,
13
+ 264,
14
+ 2613,
15
+ 1372,
16
+ 315,
17
+ 5029,
18
+ 11,
19
+ 892,
20
+ 3643,
21
+ 1105,
22
+ 11050,
23
+ 369,
24
+ 3654,
25
+ 9079,
26
+ 13,
27
+ 4354,
28
+ 11,
29
+ 279,
30
+ 3491,
31
+ 374,
32
+ 429,
33
+ 279,
34
+ 1372,
35
+ 315,
36
+ 5029,
37
+ 374
38
+ ],
39
+ "mxops": [
40
+ 429,
41
+ 807,
42
+ 646,
43
+ 4009,
44
+ 264,
45
+ 729,
46
+ 448,
47
+ 264,
48
+ 2613,
49
+ 1372,
50
+ 315,
51
+ 5029,
52
+ 11,
53
+ 892,
54
+ 3643,
55
+ 1105,
56
+ 11050,
57
+ 369,
58
+ 3654,
59
+ 9079,
60
+ 13,
61
+ 4354,
62
+ 11,
63
+ 279,
64
+ 3491,
65
+ 374,
66
+ 429,
67
+ 279,
68
+ 1372,
69
+ 315,
70
+ 5029,
71
+ 374
72
+ ],
73
+ "mode": "pathgate",
74
+ "model": "<staging>/Neutrino-8B/neutrino-8b_v4.bin"
75
+ }
mlx/receipts/pathgate_8b_d25.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "n": 32,
3
+ "identical": true,
4
+ "first_divergence": null,
5
+ "fused": [
6
+ 429,
7
+ 807,
8
+ 646,
9
+ 387,
10
+ 1483,
11
+ 311,
12
+ 4009,
13
+ 279,
14
+ 5480,
15
+ 22711,
16
+ 315,
17
+ 264,
18
+ 33365,
19
+ 13,
20
+ 576,
21
+ 5480,
22
+ 22711,
23
+ 525,
24
+ 279,
25
+ 5128,
26
+ 429,
27
+ 8651,
28
+ 2155,
29
+ 6846,
30
+ 304,
31
+ 279,
32
+ 1946,
33
+ 3550,
34
+ 13,
35
+ 758,
36
+ 264,
37
+ 71617
38
+ ],
39
+ "mxops": [
40
+ 429,
41
+ 807,
42
+ 646,
43
+ 387,
44
+ 1483,
45
+ 311,
46
+ 4009,
47
+ 279,
48
+ 5480,
49
+ 22711,
50
+ 315,
51
+ 264,
52
+ 33365,
53
+ 13,
54
+ 576,
55
+ 5480,
56
+ 22711,
57
+ 525,
58
+ 279,
59
+ 5128,
60
+ 429,
61
+ 8651,
62
+ 2155,
63
+ 6846,
64
+ 304,
65
+ 279,
66
+ 1946,
67
+ 3550,
68
+ 13,
69
+ 758,
70
+ 264,
71
+ 71617
72
+ ],
73
+ "mode": "pathgate",
74
+ "model": "/tmp/neutrino-8b_v4_d25.bin"
75
+ }
mlx/receipts/pathgate_8b_product.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "n": 32,
3
+ "identical": true,
4
+ "first_divergence": null,
5
+ "fused": [
6
+ 429,
7
+ 807,
8
+ 525,
9
+ 264,
10
+ 943,
11
+ 315,
12
+ 29728,
13
+ 3922,
14
+ 429,
15
+ 5711,
16
+ 2326,
17
+ 2155,
18
+ 5302,
19
+ 369,
20
+ 1817,
21
+ 48284,
22
+ 13,
23
+ 1096,
24
+ 374,
25
+ 2155,
26
+ 504,
27
+ 8606,
28
+ 7868,
29
+ 29728,
30
+ 14155,
31
+ 11,
32
+ 892,
33
+ 1172,
34
+ 990,
35
+ 1378,
36
+ 5302,
37
+ 320
38
+ ],
39
+ "mxops": [
40
+ 429,
41
+ 807,
42
+ 525,
43
+ 264,
44
+ 943,
45
+ 315,
46
+ 29728,
47
+ 3922,
48
+ 429,
49
+ 5711,
50
+ 2326,
51
+ 2155,
52
+ 5302,
53
+ 369,
54
+ 1817,
55
+ 48284,
56
+ 13,
57
+ 1096,
58
+ 374,
59
+ 2155,
60
+ 504,
61
+ 8606,
62
+ 7868,
63
+ 29728,
64
+ 14155,
65
+ 11,
66
+ 892,
67
+ 1172,
68
+ 990,
69
+ 1378,
70
+ 5302,
71
+ 320
72
+ ],
73
+ "mode": "pathgate",
74
+ "model": "/tmp/neutrino-8b_v4.bin"
75
+ }
mlx/receipts/pathgate_8b_wbase.json ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "n": 32,
3
+ "identical": true,
4
+ "first_divergence": null,
5
+ "fused": [
6
+ 429,
7
+ 807,
8
+ 646,
9
+ 4009,
10
+ 894,
11
+ 729,
12
+ 448,
13
+ 264,
14
+ 3654,
15
+ 1372,
16
+ 315,
17
+ 5029,
18
+ 11,
19
+ 323,
20
+ 279,
21
+ 1372,
22
+ 315,
23
+ 5029,
24
+ 374,
25
+ 5435,
26
+ 311,
27
+ 279,
28
+ 1372,
29
+ 315,
30
+ 7798,
31
+ 304,
32
+ 279,
33
+ 3922,
34
+ 13,
35
+ 576,
36
+ 1372,
37
+ 315
38
+ ],
39
+ "mxops": [
40
+ 429,
41
+ 807,
42
+ 646,
43
+ 4009,
44
+ 894,
45
+ 729,
46
+ 448,
47
+ 264,
48
+ 3654,
49
+ 1372,
50
+ 315,
51
+ 5029,
52
+ 11,
53
+ 323,
54
+ 279,
55
+ 1372,
56
+ 315,
57
+ 5029,
58
+ 374,
59
+ 5435,
60
+ 311,
61
+ 279,
62
+ 1372,
63
+ 315,
64
+ 7798,
65
+ 304,
66
+ 279,
67
+ 3922,
68
+ 13,
69
+ 576,
70
+ 1372,
71
+ 315
72
+ ],
73
+ "mode": "pathgate",
74
+ "model": "artifacts/wbase_v4.bin"
75
+ }
mlx/receipts/refgate_8b_cons3b25.json ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ref": "/tmp/c3gate/cons3b25_ref.npz",
3
+ "meta": {
4
+ "tag": "cons3b25",
5
+ "donor": "Qwen/Qwen3-8B",
6
+ "dtype": "float32",
7
+ "attn": "eager",
8
+ "prompt": "The key idea behind ternary neural networks is",
9
+ "made": "2026-07-27T15:05:30Z"
10
+ },
11
+ "tf": [
12
+ {
13
+ "pos": 0,
14
+ "rel": 2.6224000711845977e-06,
15
+ "cos": 0.999999999999292,
16
+ "argmax_match": true
17
+ },
18
+ {
19
+ "pos": 1,
20
+ "rel": 1.9424399919302914e-06,
21
+ "cos": 0.9999999999994499,
22
+ "argmax_match": true
23
+ },
24
+ {
25
+ "pos": 2,
26
+ "rel": 1.9770419300937743e-06,
27
+ "cos": 0.9999999999993497,
28
+ "argmax_match": true
29
+ },
30
+ {
31
+ "pos": 3,
32
+ "rel": 2.279632526743816e-06,
33
+ "cos": 0.9999999999994535,
34
+ "argmax_match": true
35
+ },
36
+ {
37
+ "pos": 4,
38
+ "rel": 1.9337612922869644e-06,
39
+ "cos": 0.9999999999990218,
40
+ "argmax_match": true
41
+ },
42
+ {
43
+ "pos": 5,
44
+ "rel": 2.324533691836032e-06,
45
+ "cos": 0.9999999999990492,
46
+ "argmax_match": true
47
+ },
48
+ {
49
+ "pos": 6,
50
+ "rel": 2.616610329592598e-06,
51
+ "cos": 0.9999999999988447,
52
+ "argmax_match": true
53
+ },
54
+ {
55
+ "pos": 7,
56
+ "rel": 1.6640498515280545e-06,
57
+ "cos": 0.9999999999993727,
58
+ "argmax_match": true
59
+ },
60
+ {
61
+ "pos": 8,
62
+ "rel": 1.8894559618594419e-06,
63
+ "cos": 0.9999999999993779,
64
+ "argmax_match": true
65
+ }
66
+ ],
67
+ "greedy_flips": [],
68
+ "tie_tol": 0.04,
69
+ "max_flips": 8,
70
+ "tf_summary": {
71
+ "rel_max": 2.6224000711845977e-06,
72
+ "rel_mean": 2.1388806274506187e-06,
73
+ "argmax": "9/9",
74
+ "near_tie": 0
75
+ },
76
+ "flips": 0,
77
+ "worst_gap": 0.0,
78
+ "ok": true,
79
+ "mlx_greedy": [
80
+ 429,
81
+ 807,
82
+ 646,
83
+ 4009,
84
+ 264,
85
+ 729,
86
+ 448,
87
+ 264,
88
+ 2613,
89
+ 1372,
90
+ 315,
91
+ 5029,
92
+ 11,
93
+ 892,
94
+ 3643,
95
+ 1105,
96
+ 11050,
97
+ 369,
98
+ 3654,
99
+ 9079,
100
+ 13,
101
+ 4354,
102
+ 11,
103
+ 279
104
+ ],
105
+ "ref_greedy": [
106
+ 429,
107
+ 807,
108
+ 646,
109
+ 4009,
110
+ 264,
111
+ 729,
112
+ 448,
113
+ 264,
114
+ 2613,
115
+ 1372,
116
+ 315,
117
+ 5029,
118
+ 11,
119
+ 892,
120
+ 3643,
121
+ 1105,
122
+ 11050,
123
+ 369,
124
+ 3654,
125
+ 9079,
126
+ 13,
127
+ 4354,
128
+ 11,
129
+ 279
130
+ ],
131
+ "mode": "refgate",
132
+ "model": "neutrino-8b_v4.bin"
133
+ }
mlx/receipts/refgate_8b_d25.json ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ref": "/tmp/d25_ref.npz",
3
+ "meta": {
4
+ "tag": "d25",
5
+ "donor": "Qwen/Qwen3-8B",
6
+ "dtype": "float32",
7
+ "attn": "eager",
8
+ "prompt": "The key idea behind ternary neural networks is",
9
+ "made": "2026-07-26T09:41:51Z"
10
+ },
11
+ "tf": [
12
+ {
13
+ "pos": 0,
14
+ "rel": 3.7357846722178054e-06,
15
+ "cos": 0.9999999999992958,
16
+ "argmax_match": true
17
+ },
18
+ {
19
+ "pos": 1,
20
+ "rel": 1.9618753776610103e-06,
21
+ "cos": 0.9999999999994419,
22
+ "argmax_match": true
23
+ },
24
+ {
25
+ "pos": 2,
26
+ "rel": 1.8195449731555086e-06,
27
+ "cos": 0.999999999999342,
28
+ "argmax_match": true
29
+ },
30
+ {
31
+ "pos": 3,
32
+ "rel": 2.111957507414951e-06,
33
+ "cos": 0.999999999999451,
34
+ "argmax_match": true
35
+ },
36
+ {
37
+ "pos": 4,
38
+ "rel": 1.8725257620352456e-06,
39
+ "cos": 0.9999999999988299,
40
+ "argmax_match": true
41
+ },
42
+ {
43
+ "pos": 5,
44
+ "rel": 1.8537546715748062e-06,
45
+ "cos": 0.9999999999990721,
46
+ "argmax_match": true
47
+ },
48
+ {
49
+ "pos": 6,
50
+ "rel": 1.8210546698822445e-06,
51
+ "cos": 0.9999999999988646,
52
+ "argmax_match": true
53
+ },
54
+ {
55
+ "pos": 7,
56
+ "rel": 1.8684000228879002e-06,
57
+ "cos": 0.9999999999994065,
58
+ "argmax_match": true
59
+ },
60
+ {
61
+ "pos": 8,
62
+ "rel": 1.9054552787661224e-06,
63
+ "cos": 0.9999999999993701,
64
+ "argmax_match": true
65
+ }
66
+ ],
67
+ "greedy_flips": [],
68
+ "tie_tol": 0.04,
69
+ "max_flips": 8,
70
+ "tf_summary": {
71
+ "rel_max": 3.7357846722178054e-06,
72
+ "rel_mean": 2.105594770621733e-06,
73
+ "argmax": "9/9",
74
+ "near_tie": 0
75
+ },
76
+ "flips": 0,
77
+ "worst_gap": 0.0,
78
+ "ok": true,
79
+ "mlx_greedy": [
80
+ 429,
81
+ 807,
82
+ 646,
83
+ 387,
84
+ 1483,
85
+ 311,
86
+ 4009,
87
+ 279,
88
+ 5480,
89
+ 22711,
90
+ 315,
91
+ 264,
92
+ 33365,
93
+ 13,
94
+ 576,
95
+ 5480,
96
+ 22711,
97
+ 525,
98
+ 279,
99
+ 5128,
100
+ 429,
101
+ 8651,
102
+ 2155,
103
+ 6846
104
+ ],
105
+ "ref_greedy": [
106
+ 429,
107
+ 807,
108
+ 646,
109
+ 387,
110
+ 1483,
111
+ 311,
112
+ 4009,
113
+ 279,
114
+ 5480,
115
+ 22711,
116
+ 315,
117
+ 264,
118
+ 33365,
119
+ 13,
120
+ 576,
121
+ 5480,
122
+ 22711,
123
+ 525,
124
+ 279,
125
+ 5128,
126
+ 429,
127
+ 8651,
128
+ 2155,
129
+ 6846
130
+ ],
131
+ "mode": "refgate",
132
+ "model": "/tmp/neutrino-8b_v4_d25.bin"
133
+ }
mlx/receipts/refgate_8b_product.json ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ref": "/tmp/s50_ref.npz",
3
+ "meta": {
4
+ "tag": "s50",
5
+ "donor": "Qwen/Qwen3-8B",
6
+ "dtype": "float32",
7
+ "attn": "eager",
8
+ "prompt": "The key idea behind ternary neural networks is",
9
+ "made": "2026-07-26T00:09:27Z"
10
+ },
11
+ "tf": [
12
+ {
13
+ "pos": 0,
14
+ "rel": 2.7188532122724473e-06,
15
+ "cos": 0.9999999999992888,
16
+ "argmax_match": true
17
+ },
18
+ {
19
+ "pos": 1,
20
+ "rel": 2.1881101921352207e-06,
21
+ "cos": 0.9999999999994555,
22
+ "argmax_match": true
23
+ },
24
+ {
25
+ "pos": 2,
26
+ "rel": 2.319768487104987e-06,
27
+ "cos": 0.999999999999356,
28
+ "argmax_match": true
29
+ },
30
+ {
31
+ "pos": 3,
32
+ "rel": 1.8684499152657963e-06,
33
+ "cos": 0.9999999999994532,
34
+ "argmax_match": true
35
+ },
36
+ {
37
+ "pos": 4,
38
+ "rel": 1.8324882362981365e-06,
39
+ "cos": 0.9999999999979642,
40
+ "argmax_match": true
41
+ },
42
+ {
43
+ "pos": 5,
44
+ "rel": 2.1302047849215587e-06,
45
+ "cos": 0.9999999999989747,
46
+ "argmax_match": true
47
+ },
48
+ {
49
+ "pos": 6,
50
+ "rel": 2.7137296248146236e-06,
51
+ "cos": 0.9999999999988849,
52
+ "argmax_match": true
53
+ },
54
+ {
55
+ "pos": 7,
56
+ "rel": 1.7059300688086893e-06,
57
+ "cos": 0.9999999999993904,
58
+ "argmax_match": true
59
+ },
60
+ {
61
+ "pos": 8,
62
+ "rel": 1.9090595614651556e-06,
63
+ "cos": 0.9999999999993883,
64
+ "argmax_match": true
65
+ }
66
+ ],
67
+ "greedy_flips": [],
68
+ "tie_tol": 0.04,
69
+ "max_flips": 8,
70
+ "tf_summary": {
71
+ "rel_max": 2.7188532122724473e-06,
72
+ "rel_mean": 2.154066009231846e-06,
73
+ "argmax": "9/9",
74
+ "near_tie": 0
75
+ },
76
+ "flips": 0,
77
+ "worst_gap": 0.0,
78
+ "ok": true,
79
+ "mlx_greedy": [
80
+ 429,
81
+ 807,
82
+ 525,
83
+ 264,
84
+ 943,
85
+ 315,
86
+ 29728,
87
+ 3922,
88
+ 429,
89
+ 5711,
90
+ 2326,
91
+ 2155,
92
+ 5302,
93
+ 369,
94
+ 1817,
95
+ 48284,
96
+ 13,
97
+ 1096,
98
+ 374,
99
+ 2155,
100
+ 504,
101
+ 8606,
102
+ 7868,
103
+ 29728
104
+ ],
105
+ "ref_greedy": [
106
+ 429,
107
+ 807,
108
+ 525,
109
+ 264,
110
+ 943,
111
+ 315,
112
+ 29728,
113
+ 3922,
114
+ 429,
115
+ 5711,
116
+ 2326,
117
+ 2155,
118
+ 5302,
119
+ 369,
120
+ 1817,
121
+ 48284,
122
+ 13,
123
+ 1096,
124
+ 374,
125
+ 2155,
126
+ 504,
127
+ 8606,
128
+ 7868,
129
+ 29728
130
+ ],
131
+ "mode": "refgate",
132
+ "model": "/tmp/neutrino-8b_v4.bin"
133
+ }
mlx/receipts/refgate_8b_wbase.json ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ref": "artifacts/wbase_ref.npz",
3
+ "meta": {
4
+ "tag": "wbase",
5
+ "donor": "Qwen/Qwen3-8B",
6
+ "dtype": "float32",
7
+ "attn": "eager",
8
+ "prompt": "The key idea behind ternary neural networks is",
9
+ "made": "2026-07-25T05:16:03Z"
10
+ },
11
+ "tf": [
12
+ {
13
+ "pos": 0,
14
+ "rel": 2.5888214527867327e-06,
15
+ "cos": 0.9999999999993819,
16
+ "argmax_match": true
17
+ },
18
+ {
19
+ "pos": 1,
20
+ "rel": 2.474946032917843e-06,
21
+ "cos": 0.9999999999994814,
22
+ "argmax_match": true
23
+ },
24
+ {
25
+ "pos": 2,
26
+ "rel": 2.3891665631361155e-06,
27
+ "cos": 0.9999999999993758,
28
+ "argmax_match": true
29
+ },
30
+ {
31
+ "pos": 3,
32
+ "rel": 2.329101721715938e-06,
33
+ "cos": 0.9999999999994569,
34
+ "argmax_match": true
35
+ },
36
+ {
37
+ "pos": 4,
38
+ "rel": 1.6647379290477042e-06,
39
+ "cos": 0.9999999999984548,
40
+ "argmax_match": true
41
+ },
42
+ {
43
+ "pos": 5,
44
+ "rel": 2.36762622893542e-06,
45
+ "cos": 0.9999999999989674,
46
+ "argmax_match": true
47
+ },
48
+ {
49
+ "pos": 6,
50
+ "rel": 1.7604135798303548e-06,
51
+ "cos": 0.9999999999987743,
52
+ "argmax_match": true
53
+ },
54
+ {
55
+ "pos": 7,
56
+ "rel": 1.7215424255002515e-06,
57
+ "cos": 0.9999999999994331,
58
+ "argmax_match": true
59
+ },
60
+ {
61
+ "pos": 8,
62
+ "rel": 1.7872186699375681e-06,
63
+ "cos": 0.9999999999993865,
64
+ "argmax_match": true
65
+ }
66
+ ],
67
+ "greedy_flips": [],
68
+ "tie_tol": 0.04,
69
+ "max_flips": 8,
70
+ "tf_summary": {
71
+ "rel_max": 2.5888214527867327e-06,
72
+ "rel_mean": 2.120397178200881e-06,
73
+ "argmax": "9/9",
74
+ "near_tie": 0
75
+ },
76
+ "flips": 0,
77
+ "worst_gap": 0.0,
78
+ "ok": true,
79
+ "mlx_greedy": [
80
+ 429,
81
+ 807,
82
+ 646,
83
+ 4009,
84
+ 894,
85
+ 729,
86
+ 448,
87
+ 264,
88
+ 3654,
89
+ 1372,
90
+ 315,
91
+ 5029,
92
+ 11,
93
+ 323,
94
+ 279,
95
+ 1372,
96
+ 315,
97
+ 5029,
98
+ 374,
99
+ 5435,
100
+ 311,
101
+ 279,
102
+ 1372,
103
+ 315
104
+ ],
105
+ "ref_greedy": [
106
+ 429,
107
+ 807,
108
+ 646,
109
+ 4009,
110
+ 894,
111
+ 729,
112
+ 448,
113
+ 264,
114
+ 3654,
115
+ 1372,
116
+ 315,
117
+ 5029,
118
+ 11,
119
+ 323,
120
+ 279,
121
+ 1372,
122
+ 315,
123
+ 5029,
124
+ 374,
125
+ 5435,
126
+ 311,
127
+ 279,
128
+ 1372,
129
+ 315
130
+ ],
131
+ "mode": "refgate",
132
+ "model": "artifacts/wbase_v4.bin"
133
+ }
mlx/receipts/specbench_8b_0p6b_m5.json ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bench": "specbench",
3
+ "k": 4,
4
+ "n": 512,
5
+ "runs": 3,
6
+ "types": {
7
+ "facts": {
8
+ "plain_median_tok_s": 22.0,
9
+ "spec_median_tok_s": 25.71,
10
+ "speedup": 1.168,
11
+ "acceptance_median": 0.7442,
12
+ "plain_runs": [
13
+ 23.52,
14
+ 22.0,
15
+ 21.08
16
+ ],
17
+ "spec_runs": [
18
+ 26.3,
19
+ 25.71,
20
+ 23.76
21
+ ]
22
+ },
23
+ "prose": {
24
+ "plain_median_tok_s": 17.86,
25
+ "spec_median_tok_s": 12.15,
26
+ "speedup": 0.68,
27
+ "acceptance_median": 0.2994,
28
+ "plain_runs": [
29
+ 19.12,
30
+ 17.86,
31
+ 17.84
32
+ ],
33
+ "spec_runs": [
34
+ 12.48,
35
+ 12.1,
36
+ 12.15
37
+ ]
38
+ },
39
+ "chat": {
40
+ "plain_median_tok_s": 17.53,
41
+ "spec_median_tok_s": 14.68,
42
+ "speedup": 0.837,
43
+ "acceptance_median": 0.4395,
44
+ "plain_runs": [
45
+ 17.72,
46
+ 17.53,
47
+ 17.35
48
+ ],
49
+ "spec_runs": [
50
+ 15.01,
51
+ 14.67,
52
+ 14.68
53
+ ]
54
+ }
55
+ },
56
+ "peak_mem_gib": 4.3
57
+ }
mlx/receipts/specgate_8b_0p6b.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "gate": "specgate",
3
+ "k": 4,
4
+ "lens": [
5
+ 32,
6
+ 256
7
+ ],
8
+ "prompts": {
9
+ "facts": {
10
+ "32": {
11
+ "identical": true,
12
+ "first_divergence": -1,
13
+ "acceptance": 0.3214
14
+ },
15
+ "256": {
16
+ "identical": true,
17
+ "first_divergence": -1,
18
+ "acceptance": 0.6714
19
+ }
20
+ },
21
+ "prose": {
22
+ "32": {
23
+ "identical": true,
24
+ "first_divergence": -1,
25
+ "acceptance": 0.4038
26
+ },
27
+ "256": {
28
+ "identical": true,
29
+ "first_divergence": -1,
30
+ "acceptance": 0.2896
31
+ }
32
+ },
33
+ "chat": {
34
+ "32": {
35
+ "identical": true,
36
+ "first_divergence": -1,
37
+ "acceptance": 0.55
38
+ },
39
+ "256": {
40
+ "identical": true,
41
+ "first_divergence": -1,
42
+ "acceptance": 0.4289
43
+ }
44
+ }
45
+ },
46
+ "verdict": "PASS"
47
+ }