Automatic Speech Recognition
Transformers
ONNX
English
onnxruntime
speech
asr
granite
ibm
quantized
int8
fp16
non-autoregressive
nar
Instructions to use smcleod/ibm-granite-speech-4.1-2b-nar-onnx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use smcleod/ibm-granite-speech-4.1-2b-nar-onnx with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="smcleod/ibm-granite-speech-4.1-2b-nar-onnx")# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("smcleod/ibm-granite-speech-4.1-2b-nar-onnx", dtype="auto") - Notebooks
- Google Colab
- Kaggle
Add files using upload-large-folder tool
Browse files- .gitattributes +4 -0
- LICENSE +202 -0
- editor.onnx +3 -0
- editor.onnx_data +3 -0
- editor_int8.onnx +3 -0
- editor_int8.onnx_data +3 -0
- encoder.onnx +3 -0
- encoder.onnx_data +3 -0
- encoder_int8.onnx +3 -0
- encoder_int8.onnx_data +3 -0
- export_nar_editor.py +639 -0
- export_nar_encoder.py +743 -0
- granite_export_metadata.json +373 -0
- preprocessor_config.json +11 -0
- quantise.py +299 -0
- special_tokens_map.json +30 -0
- tokenizer.json +0 -0
- tokenizer_config.json +783 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
encoder_int8.onnx_data filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
encoder.onnx_data filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
editor.onnx_data filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
editor_int8.onnx_data filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
editor.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:524863dc275c529cd198d57bf7c309c897ec93db27c86a5d51ea8dffede8b0cf
|
| 3 |
+
size 1838936
|
editor.onnx_data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6882668bc9eef72941824bfbabadb0d42d693398a942774e53f2553e38ad83f1
|
| 3 |
+
size 6527000576
|
editor_int8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3a1c6cc4f5fb9f0f926bfeb7b0a8f26272b5c5c781bf34648bcfdcfecff79d6a
|
| 3 |
+
size 6418060
|
editor_int8.onnx_data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c84ab94b8b79b834f55877d7c794dfb3f4a710e31770e517fb628b1d36b43d03
|
| 3 |
+
size 1632247808
|
encoder.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bb87c12e45340b614d619cfa3549c10ead32d1f56b69bf18dbbda144d7704d32
|
| 3 |
+
size 1068038
|
encoder.onnx_data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:67fe37eda473f984e6a1c574f0265765c196e1cdf39f3750d026608ffc0e6d4a
|
| 3 |
+
size 2491061620
|
encoder_int8.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5fe3e39656c436f7c5ff2f4f2e11fbc6b55aed007cba37de1fb3b81c0d946671
|
| 3 |
+
size 3310811
|
encoder_int8.onnx_data
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:64764e9911a47cf724df22f7857e701624e3f81279973e96a20acce245d1086d
|
| 3 |
+
size 935864692
|
export_nar_editor.py
ADDED
|
@@ -0,0 +1,639 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Sam McLeod
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""Export the NAR editor (Granite 4.0 1B LLM run bidirectionally) as a single
|
| 15 |
+
ONNX graph and verify parity against the captured PyTorch baseline.
|
| 16 |
+
|
| 17 |
+
Wraps `model.llm.model` + `model.llm.lm_head` in an nn.Module whose forward
|
| 18 |
+
takes (inputs_embeds, position_ids, attention_mask) where attention_mask is a
|
| 19 |
+
4-D additive mask (zeros = attention allowed everywhere). Exports with
|
| 20 |
+
torch.onnx.export at opset 20, IR 10, single sidecar.
|
| 21 |
+
|
| 22 |
+
End-to-end parity test:
|
| 23 |
+
1. Run the already-exported encoder.onnx on the reference clip.
|
| 24 |
+
2. Run CTC greedy decode + slot insertion + flat embedding assembly via the
|
| 25 |
+
upstream model's bound methods (matches what Rust glue will do).
|
| 26 |
+
3. Run the resulting `inputs_embeds` through both the patched PyTorch editor
|
| 27 |
+
and the exported editor.onnx, then compare logits.
|
| 28 |
+
4. Decode the ONNX logits at the text positions and verify the transcript
|
| 29 |
+
matches the upstream NAR transcript exactly.
|
| 30 |
+
|
| 31 |
+
Usage:
|
| 32 |
+
HF_HOME=$TMPDIR/hf_home HF_MODULES_CACHE=$TMPDIR/hf_modules \
|
| 33 |
+
uv run python src/export_nar_editor.py
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
from __future__ import annotations
|
| 37 |
+
|
| 38 |
+
import argparse
|
| 39 |
+
import json
|
| 40 |
+
import os
|
| 41 |
+
import time
|
| 42 |
+
from pathlib import Path
|
| 43 |
+
from typing import Any
|
| 44 |
+
|
| 45 |
+
import numpy as np
|
| 46 |
+
import soundfile as sf
|
| 47 |
+
import torch
|
| 48 |
+
import torch.nn as nn
|
| 49 |
+
import torch.nn.functional as F
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
# Resolve roots so the script works whether it lives at <repo>/src/<name>.py
|
| 53 |
+
# (project layout) or <bundle>/<name>.py (HF bundle layout). Defaults exist for
|
| 54 |
+
# the project layout; bundle users should pass explicit --audio / --baseline /
|
| 55 |
+
# --model-dir / --out-dir.
|
| 56 |
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
| 57 |
+
REPO_ROOT = SCRIPT_DIR.parent if SCRIPT_DIR.name == "src" else SCRIPT_DIR
|
| 58 |
+
DEFAULT_AUDIO = REPO_ROOT / "test_data" / "10226_10111_000000.wav"
|
| 59 |
+
DEFAULT_BASELINE = REPO_ROOT / "test_data" / "baselines" / "nar.json"
|
| 60 |
+
DEFAULT_MODEL_DIR = REPO_ROOT / "models" / "granite-speech-4.1-2b-nar"
|
| 61 |
+
DEFAULT_OUT_DIR = REPO_ROOT / "exports" / "granite-speech-4.1-2b-nar"
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def load_audio(path: Path) -> np.ndarray:
|
| 65 |
+
waveform, sr = sf.read(str(path), dtype="float32")
|
| 66 |
+
if waveform.ndim > 1:
|
| 67 |
+
waveform = waveform.mean(axis=1)
|
| 68 |
+
assert sr == 16000, f"expected 16 kHz, got {sr}"
|
| 69 |
+
return waveform
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def tensor_stats(t: torch.Tensor | np.ndarray | None) -> dict[str, Any] | None:
|
| 73 |
+
if t is None:
|
| 74 |
+
return None
|
| 75 |
+
if isinstance(t, torch.Tensor):
|
| 76 |
+
x = t.detach().float().cpu().numpy()
|
| 77 |
+
dtype_str = str(t.dtype).replace("torch.", "")
|
| 78 |
+
else:
|
| 79 |
+
x = np.asarray(t).astype(np.float32, copy=False)
|
| 80 |
+
dtype_str = str(t.dtype)
|
| 81 |
+
flat = x.flatten()
|
| 82 |
+
return {
|
| 83 |
+
"shape": list(x.shape),
|
| 84 |
+
"dtype": dtype_str,
|
| 85 |
+
"mean": float(flat.mean()) if flat.size else None,
|
| 86 |
+
"std": float(flat.std()) if flat.size else None,
|
| 87 |
+
"min": float(flat.min()) if flat.size else None,
|
| 88 |
+
"max": float(flat.max()) if flat.size else None,
|
| 89 |
+
"first10": [float(v) for v in flat[:10]],
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
# ---------------------------------------------------------------------------
|
| 94 |
+
# Wrapper module: LLM backbone + lm_head exposed as a single graph.
|
| 95 |
+
# ---------------------------------------------------------------------------
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class NAREditor(nn.Module):
|
| 99 |
+
"""Wrap llm.model + llm.lm_head with a 4-D additive attention-mask input.
|
| 100 |
+
|
| 101 |
+
Inputs:
|
| 102 |
+
inputs_embeds: float32 [1, N_total, D_llm] pre-built flat sequence
|
| 103 |
+
position_ids: int64 [1, N_total] cumulative per-sample positions
|
| 104 |
+
attention_mask: float32 [1, 1, N_total, N_total] additive mask
|
| 105 |
+
Zeros = attention allowed everywhere (bidirectional).
|
| 106 |
+
|
| 107 |
+
Output:
|
| 108 |
+
logits: float32 [1, N_total, V_llm]
|
| 109 |
+
|
| 110 |
+
The Granite model in transformers 5.8 calls `create_causal_mask`, which
|
| 111 |
+
in turn calls `_preprocess_mask_arguments`. That helper short-circuits and
|
| 112 |
+
returns any 4-D attention mask as-is. So feeding zeros disables causality.
|
| 113 |
+
"""
|
| 114 |
+
|
| 115 |
+
def __init__(self, llm_model: nn.Module, lm_head: nn.Module) -> None:
|
| 116 |
+
super().__init__()
|
| 117 |
+
self.llm_model = llm_model
|
| 118 |
+
self.lm_head = lm_head
|
| 119 |
+
|
| 120 |
+
def forward(
|
| 121 |
+
self,
|
| 122 |
+
inputs_embeds: torch.Tensor,
|
| 123 |
+
position_ids: torch.Tensor,
|
| 124 |
+
attention_mask: torch.Tensor,
|
| 125 |
+
) -> torch.Tensor:
|
| 126 |
+
out = self.llm_model(
|
| 127 |
+
inputs_embeds=inputs_embeds,
|
| 128 |
+
position_ids=position_ids,
|
| 129 |
+
attention_mask=attention_mask,
|
| 130 |
+
use_cache=False,
|
| 131 |
+
)
|
| 132 |
+
return self.lm_head(out.last_hidden_state)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# ---------------------------------------------------------------------------
|
| 136 |
+
# Model loading (mirrors capture_baselines.py::capture_nar).
|
| 137 |
+
# ---------------------------------------------------------------------------
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def load_nar_model(model_dir: Path) -> tuple[nn.Module, Any]:
|
| 141 |
+
"""Load NAR model with the same patches capture_baselines.py uses."""
|
| 142 |
+
from transformers import AutoConfig, AutoFeatureExtractor, AutoModel
|
| 143 |
+
|
| 144 |
+
granite_local = REPO_ROOT / "models" / "granite-4.0-1b-base"
|
| 145 |
+
if not granite_local.exists():
|
| 146 |
+
raise FileNotFoundError(
|
| 147 |
+
f"Expected local Granite 4.0 base at {granite_local}; "
|
| 148 |
+
"run `hf download ibm-granite/granite-4.0-1b-base "
|
| 149 |
+
"--include '*.json' --include 'tokenizer*' --include '*.txt' "
|
| 150 |
+
f"--local-dir {granite_local}` first."
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
config = AutoConfig.from_pretrained(str(model_dir), trust_remote_code=True)
|
| 154 |
+
config.llm_name = str(granite_local)
|
| 155 |
+
config.attn_implementation = "eager"
|
| 156 |
+
config._attn_implementation = "eager"
|
| 157 |
+
for sub_attr in ("llm_config", "encoder_config", "projector_config"):
|
| 158 |
+
sub = getattr(config, sub_attr, None)
|
| 159 |
+
if sub is not None:
|
| 160 |
+
for attr in ("attn_implementation", "_attn_implementation"):
|
| 161 |
+
try:
|
| 162 |
+
setattr(sub, attr, "eager")
|
| 163 |
+
except Exception:
|
| 164 |
+
pass
|
| 165 |
+
|
| 166 |
+
print(f" loading model from {model_dir} (eager)")
|
| 167 |
+
t0 = time.time()
|
| 168 |
+
model = AutoModel.from_pretrained(
|
| 169 |
+
str(model_dir),
|
| 170 |
+
trust_remote_code=True,
|
| 171 |
+
torch_dtype=torch.float32,
|
| 172 |
+
attn_implementation="eager",
|
| 173 |
+
config=config,
|
| 174 |
+
)
|
| 175 |
+
model.eval()
|
| 176 |
+
# The NAR config nests `llm_config.dtype = "bfloat16"`, which overrides
|
| 177 |
+
# the top-level `torch_dtype=float32` request for the LLM submodule.
|
| 178 |
+
# Force the whole model (including LLM and lm_head) to fp32.
|
| 179 |
+
model = model.to(torch.float32)
|
| 180 |
+
print(f" loaded in {time.time() - t0:.1f}s")
|
| 181 |
+
|
| 182 |
+
fe = AutoFeatureExtractor.from_pretrained(str(model_dir), trust_remote_code=True)
|
| 183 |
+
return model, fe
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
# ---------------------------------------------------------------------------
|
| 187 |
+
# Build inputs_embeds for parity from the already-exported encoder.onnx.
|
| 188 |
+
# ---------------------------------------------------------------------------
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def build_editor_inputs(
|
| 192 |
+
model: nn.Module,
|
| 193 |
+
fe: Any,
|
| 194 |
+
waveform: np.ndarray,
|
| 195 |
+
encoder_onnx_path: Path,
|
| 196 |
+
) -> tuple[torch.Tensor, torch.Tensor, list[int], list[int], list[str]]:
|
| 197 |
+
"""Mirror what Rust glue does: encoder.onnx -> CTC greedy + slot inserts ->
|
| 198 |
+
flat (audio, text_with_slots) sequence.
|
| 199 |
+
|
| 200 |
+
Returns:
|
| 201 |
+
flat_embeds: [1, N_total, 2048]
|
| 202 |
+
flat_position_ids: [1, N_total]
|
| 203 |
+
projected_lengths: per-sample audio-length list
|
| 204 |
+
text_lengths: per-sample text-length list
|
| 205 |
+
text_ctc_preds: per-sample CTC strings (for debug)
|
| 206 |
+
"""
|
| 207 |
+
import onnxruntime as ort
|
| 208 |
+
|
| 209 |
+
waveform_t = torch.from_numpy(waveform.copy())
|
| 210 |
+
inputs = fe([waveform_t], device="cpu")
|
| 211 |
+
input_features = inputs["input_features"].to(torch.float32)
|
| 212 |
+
attention_mask_int = inputs["attention_mask"].to(torch.int64)
|
| 213 |
+
|
| 214 |
+
print(f" running encoder.onnx for inputs_embeds construction")
|
| 215 |
+
sess = ort.InferenceSession(str(encoder_onnx_path), providers=["CPUExecutionProvider"])
|
| 216 |
+
out_names = [o.name for o in sess.get_outputs()]
|
| 217 |
+
ort_outputs = sess.run(
|
| 218 |
+
out_names,
|
| 219 |
+
{
|
| 220 |
+
"input_features": input_features.numpy().astype(np.float32),
|
| 221 |
+
"attention_mask": attention_mask_int.numpy().astype(np.int64),
|
| 222 |
+
},
|
| 223 |
+
)
|
| 224 |
+
out_map = dict(zip(out_names, ort_outputs))
|
| 225 |
+
bpe_dense = torch.from_numpy(out_map["bpe_logits_dense"]) # [B, T_bpe, V_bpe]
|
| 226 |
+
bpe_mask = torch.from_numpy(out_map["bpe_mask"]) # [B, T_bpe] bool
|
| 227 |
+
audio_embeds = torch.from_numpy(out_map["audio_embeds"]) # [B, T_audio, 2048]
|
| 228 |
+
audio_lengths = torch.from_numpy(out_map["audio_lengths"]) # [B] int64
|
| 229 |
+
|
| 230 |
+
# Reconstruct sparse BPE logits ([N_valid, V_bpe]) and per-sample lengths.
|
| 231 |
+
bpe_lengths = bpe_mask.sum(dim=1).tolist()
|
| 232 |
+
bpe_logits_flat = bpe_dense[bpe_mask] # [N_valid, V_bpe]
|
| 233 |
+
|
| 234 |
+
# CTC greedy decode -> List[str] (one per sample).
|
| 235 |
+
text_ctc_preds = model._decode_bpe_ctc_greedy(bpe_logits_flat, bpe_lengths)
|
| 236 |
+
print(f" text_ctc_preds: {text_ctc_preds!r}")
|
| 237 |
+
|
| 238 |
+
# Build flat LLM inputs. The upstream `_build_flat_llm_inputs` calls
|
| 239 |
+
# `self.projector(encoder_embs)` to produce audio embeddings; we already
|
| 240 |
+
# have those from the encoder.onnx. To avoid re-running the projector,
|
| 241 |
+
# we replicate the slot-insertion + concat steps inline here.
|
| 242 |
+
# This must match the upstream method byte-for-byte modulo the projector
|
| 243 |
+
# source (encoder.onnx vs PyTorch projector).
|
| 244 |
+
if model.config.scale_projected_embeddings and hasattr(model.llm.config, "embedding_multiplier"):
|
| 245 |
+
audio_embeds_scaled = audio_embeds / model.llm.config.embedding_multiplier
|
| 246 |
+
else:
|
| 247 |
+
audio_embeds_scaled = audio_embeds
|
| 248 |
+
audio_embeds_scaled = audio_embeds_scaled.to(model.llm.model.embed_tokens.weight.dtype)
|
| 249 |
+
|
| 250 |
+
pred_text_llm_tokens = model.llm_tokenizer(text_ctc_preds)
|
| 251 |
+
text_ids_with_slots = [
|
| 252 |
+
model.add_insertion_slots(torch.tensor(x))
|
| 253 |
+
for x in pred_text_llm_tokens.input_ids
|
| 254 |
+
]
|
| 255 |
+
|
| 256 |
+
embed_tokens = model.llm.model.embed_tokens
|
| 257 |
+
embeds_list = []
|
| 258 |
+
position_ids_list = []
|
| 259 |
+
text_lengths = []
|
| 260 |
+
projected_lengths = audio_lengths.tolist()
|
| 261 |
+
|
| 262 |
+
for i, audio_len in enumerate(projected_lengths):
|
| 263 |
+
audio = audio_embeds_scaled[i, :audio_len]
|
| 264 |
+
text_emb = embed_tokens(text_ids_with_slots[i])
|
| 265 |
+
sample = torch.cat([audio, text_emb], dim=0)
|
| 266 |
+
embeds_list.append(sample)
|
| 267 |
+
position_ids_list.append(torch.arange(sample.shape[0]))
|
| 268 |
+
text_lengths.append(text_ids_with_slots[i].shape[0])
|
| 269 |
+
|
| 270 |
+
flat_embeds = torch.cat(embeds_list, dim=0).unsqueeze(0).to(torch.float32)
|
| 271 |
+
flat_position_ids = torch.cat(position_ids_list, dim=0).unsqueeze(0).to(torch.int64)
|
| 272 |
+
|
| 273 |
+
print(
|
| 274 |
+
f" flat_embeds={tuple(flat_embeds.shape)} "
|
| 275 |
+
f"flat_position_ids={tuple(flat_position_ids.shape)} "
|
| 276 |
+
f"projected_lengths={projected_lengths} text_lengths={text_lengths}"
|
| 277 |
+
)
|
| 278 |
+
return flat_embeds, flat_position_ids, projected_lengths, text_lengths, text_ctc_preds
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
# ---------------------------------------------------------------------------
|
| 282 |
+
# Export.
|
| 283 |
+
# ---------------------------------------------------------------------------
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
def export_onnx(
|
| 287 |
+
wrapper: NAREditor,
|
| 288 |
+
sample_inputs_embeds: torch.Tensor,
|
| 289 |
+
sample_position_ids: torch.Tensor,
|
| 290 |
+
sample_attention_mask: torch.Tensor,
|
| 291 |
+
out_path: Path,
|
| 292 |
+
opset: int = 20,
|
| 293 |
+
ir_version: int = 10,
|
| 294 |
+
) -> None:
|
| 295 |
+
import tempfile
|
| 296 |
+
|
| 297 |
+
import onnx
|
| 298 |
+
|
| 299 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 300 |
+
print(f" exporting to {out_path} (opset={opset}, ir_version={ir_version})")
|
| 301 |
+
|
| 302 |
+
dynamic_axes = {
|
| 303 |
+
"inputs_embeds": {1: "N"},
|
| 304 |
+
"position_ids": {1: "N"},
|
| 305 |
+
"attention_mask": {2: "N", 3: "N"},
|
| 306 |
+
"logits": {1: "N"},
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
with tempfile.TemporaryDirectory(prefix="nar_editor_onnx_") as scratch_dir:
|
| 310 |
+
scratch_path = Path(scratch_dir) / "editor.onnx"
|
| 311 |
+
t0 = time.time()
|
| 312 |
+
torch.onnx.export(
|
| 313 |
+
wrapper,
|
| 314 |
+
(sample_inputs_embeds, sample_position_ids, sample_attention_mask),
|
| 315 |
+
str(scratch_path),
|
| 316 |
+
input_names=["inputs_embeds", "position_ids", "attention_mask"],
|
| 317 |
+
output_names=["logits"],
|
| 318 |
+
dynamic_axes=dynamic_axes,
|
| 319 |
+
opset_version=opset,
|
| 320 |
+
do_constant_folding=True,
|
| 321 |
+
export_params=True,
|
| 322 |
+
dynamo=False,
|
| 323 |
+
)
|
| 324 |
+
print(f" stage-1 torch.onnx.export done in {time.time() - t0:.1f}s")
|
| 325 |
+
|
| 326 |
+
print(" stage-2: re-saving with single .onnx_data sidecar + ir bump")
|
| 327 |
+
model_proto = onnx.load(str(scratch_path), load_external_data=True)
|
| 328 |
+
if model_proto.ir_version < ir_version:
|
| 329 |
+
model_proto.ir_version = ir_version
|
| 330 |
+
|
| 331 |
+
for tensor in model_proto.graph.initializer:
|
| 332 |
+
tensor.ClearField("data_location")
|
| 333 |
+
tensor.ClearField("external_data")
|
| 334 |
+
|
| 335 |
+
sidecar_name = out_path.name + "_data"
|
| 336 |
+
if (out_path.parent / sidecar_name).exists():
|
| 337 |
+
(out_path.parent / sidecar_name).unlink()
|
| 338 |
+
if out_path.exists():
|
| 339 |
+
out_path.unlink()
|
| 340 |
+
|
| 341 |
+
onnx.save_model(
|
| 342 |
+
model_proto,
|
| 343 |
+
str(out_path),
|
| 344 |
+
save_as_external_data=True,
|
| 345 |
+
all_tensors_to_one_file=True,
|
| 346 |
+
location=sidecar_name,
|
| 347 |
+
size_threshold=1024,
|
| 348 |
+
convert_attribute=False,
|
| 349 |
+
)
|
| 350 |
+
onnx.checker.check_model(str(out_path), full_check=False)
|
| 351 |
+
|
| 352 |
+
# Quick op-domain audit: the doc target says no `com.microsoft` ops.
|
| 353 |
+
domains = sorted({n.domain for n in model_proto.graph.node})
|
| 354 |
+
print(f" saved {out_path} (+ {sidecar_name}) node-domains={domains}")
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
# ---------------------------------------------------------------------------
|
| 358 |
+
# Parity test.
|
| 359 |
+
# ---------------------------------------------------------------------------
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
def run_parity(
|
| 363 |
+
model: nn.Module,
|
| 364 |
+
flat_embeds: torch.Tensor,
|
| 365 |
+
flat_position_ids: torch.Tensor,
|
| 366 |
+
projected_lengths: list[int],
|
| 367 |
+
text_lengths: list[int],
|
| 368 |
+
text_ctc_preds: list[str],
|
| 369 |
+
onnx_path: Path,
|
| 370 |
+
parity_json: Path,
|
| 371 |
+
baseline_json: Path,
|
| 372 |
+
abs_tol: float = 1e-3,
|
| 373 |
+
argmax_only: bool = False,
|
| 374 |
+
) -> bool:
|
| 375 |
+
import onnxruntime as ort
|
| 376 |
+
|
| 377 |
+
print("\n=== parity check ===")
|
| 378 |
+
N = flat_embeds.shape[1]
|
| 379 |
+
attention_mask = torch.zeros(1, 1, N, N, dtype=flat_embeds.dtype)
|
| 380 |
+
|
| 381 |
+
print(" running PyTorch editor (llm.model + lm_head with 4-D zeros mask)")
|
| 382 |
+
t0 = time.time()
|
| 383 |
+
with torch.inference_mode():
|
| 384 |
+
out = model.llm.model(
|
| 385 |
+
inputs_embeds=flat_embeds,
|
| 386 |
+
position_ids=flat_position_ids,
|
| 387 |
+
attention_mask=attention_mask,
|
| 388 |
+
use_cache=False,
|
| 389 |
+
)
|
| 390 |
+
logits_pt = model.llm.lm_head(out.last_hidden_state)
|
| 391 |
+
print(f" pytorch forward: {time.time() - t0:.2f}s")
|
| 392 |
+
print(f" logits_pt shape={tuple(logits_pt.shape)}")
|
| 393 |
+
|
| 394 |
+
print(f" running ONNX inference: {onnx_path}")
|
| 395 |
+
sess = ort.InferenceSession(str(onnx_path), providers=["CPUExecutionProvider"])
|
| 396 |
+
t0 = time.time()
|
| 397 |
+
ort_outputs = sess.run(
|
| 398 |
+
["logits"],
|
| 399 |
+
{
|
| 400 |
+
"inputs_embeds": flat_embeds.detach().numpy().astype(np.float32),
|
| 401 |
+
"position_ids": flat_position_ids.detach().numpy().astype(np.int64),
|
| 402 |
+
"attention_mask": attention_mask.detach().numpy().astype(np.float32),
|
| 403 |
+
},
|
| 404 |
+
)
|
| 405 |
+
print(f" onnx forward: {time.time() - t0:.2f}s")
|
| 406 |
+
logits_ort = ort_outputs[0]
|
| 407 |
+
print(f" logits_ort shape={logits_ort.shape}")
|
| 408 |
+
|
| 409 |
+
pt_np = logits_pt.detach().float().cpu().numpy()
|
| 410 |
+
abs_err = np.abs(pt_np - logits_ort)
|
| 411 |
+
max_err = float(abs_err.max())
|
| 412 |
+
mean_err = float(abs_err.mean())
|
| 413 |
+
p99 = float(np.percentile(abs_err, 99))
|
| 414 |
+
|
| 415 |
+
am_pt = pt_np.argmax(-1)
|
| 416 |
+
am_ort = logits_ort.argmax(-1)
|
| 417 |
+
argmax_mismatches = int((am_pt != am_ort).sum())
|
| 418 |
+
argmax_total = int(am_pt.size)
|
| 419 |
+
|
| 420 |
+
# Per-segment argmax: only the text positions feed the transcript decode
|
| 421 |
+
# (audio positions are read-only inputs to attention). Track separately so
|
| 422 |
+
# INT8 mode can ship on transcript correctness even when audio-position
|
| 423 |
+
# logits drift under weight quantisation.
|
| 424 |
+
text_argmax_mismatches = 0
|
| 425 |
+
text_argmax_total = 0
|
| 426 |
+
audio_argmax_mismatches = 0
|
| 427 |
+
audio_argmax_total = 0
|
| 428 |
+
seg_offset = 0
|
| 429 |
+
for i in range(len(projected_lengths)):
|
| 430 |
+
a_lo, a_hi = seg_offset, seg_offset + projected_lengths[i]
|
| 431 |
+
t_lo, t_hi = a_hi, a_hi + text_lengths[i]
|
| 432 |
+
seg_offset = t_hi
|
| 433 |
+
audio_argmax_mismatches += int((am_pt[0, a_lo:a_hi] != am_ort[0, a_lo:a_hi]).sum())
|
| 434 |
+
audio_argmax_total += a_hi - a_lo
|
| 435 |
+
text_argmax_mismatches += int((am_pt[0, t_lo:t_hi] != am_ort[0, t_lo:t_hi]).sum())
|
| 436 |
+
text_argmax_total += t_hi - t_lo
|
| 437 |
+
|
| 438 |
+
# Top-5 stability check.
|
| 439 |
+
topk_pt = np.argsort(-pt_np, axis=-1)[..., :5]
|
| 440 |
+
topk_ort = np.argsort(-logits_ort, axis=-1)[..., :5]
|
| 441 |
+
top1_match = int((topk_pt[..., 0] == topk_ort[..., 0]).sum())
|
| 442 |
+
top5_set_match = int(
|
| 443 |
+
(
|
| 444 |
+
np.sort(topk_pt, axis=-1) == np.sort(topk_ort, axis=-1)
|
| 445 |
+
).all(axis=-1).sum()
|
| 446 |
+
)
|
| 447 |
+
|
| 448 |
+
print("\n--- logits diff ---")
|
| 449 |
+
print(f" shape pt={pt_np.shape} ort={logits_ort.shape}")
|
| 450 |
+
print(f" max_abs_err={max_err:.3e} mean_abs_err={mean_err:.3e} p99={p99:.3e}")
|
| 451 |
+
print(f" argmax mismatches: {argmax_mismatches}/{argmax_total}")
|
| 452 |
+
print(
|
| 453 |
+
f" text-segment argmax mismatches: {text_argmax_mismatches}/{text_argmax_total}; "
|
| 454 |
+
f"audio-segment argmax mismatches: {audio_argmax_mismatches}/{audio_argmax_total}"
|
| 455 |
+
)
|
| 456 |
+
print(f" top1 match: {top1_match}/{argmax_total} top5-set match: {top5_set_match}/{argmax_total}")
|
| 457 |
+
|
| 458 |
+
# End-to-end transcript check: slice text positions from ONNX logits and run
|
| 459 |
+
# the upstream argmax + unique_consecutive + EOS removal.
|
| 460 |
+
eos_id = int(model.llm.config.eos_token_id)
|
| 461 |
+
offset = 0
|
| 462 |
+
decoded_segments = []
|
| 463 |
+
for i in range(len(projected_lengths)):
|
| 464 |
+
offset += projected_lengths[i]
|
| 465 |
+
seg = logits_ort[0, offset:offset + text_lengths[i]]
|
| 466 |
+
offset += text_lengths[i]
|
| 467 |
+
pred = seg.argmax(-1)
|
| 468 |
+
# Use torch.unique_consecutive to mirror upstream exactly.
|
| 469 |
+
collapsed = torch.unique_consecutive(torch.from_numpy(pred)).tolist()
|
| 470 |
+
collapsed = [t for t in collapsed if t != eos_id]
|
| 471 |
+
text = model.llm_tokenizer.decode(collapsed, skip_special_tokens=True)
|
| 472 |
+
decoded_segments.append(text)
|
| 473 |
+
|
| 474 |
+
onnx_transcript = decoded_segments[0] if decoded_segments else ""
|
| 475 |
+
print(f"\n ONNX transcript: {onnx_transcript!r}")
|
| 476 |
+
|
| 477 |
+
baseline_transcript = None
|
| 478 |
+
if baseline_json.exists():
|
| 479 |
+
baseline = json.loads(baseline_json.read_text())
|
| 480 |
+
baseline_transcript = baseline.get("transcript")
|
| 481 |
+
print(f" baseline transcript: {baseline_transcript!r}")
|
| 482 |
+
transcript_match = bool(
|
| 483 |
+
baseline_transcript is not None and onnx_transcript == baseline_transcript
|
| 484 |
+
)
|
| 485 |
+
|
| 486 |
+
# Pass criteria: zero argmax mismatches AND transcript matches baseline.
|
| 487 |
+
# The max-abs threshold is informational; spec mandates argmax stability.
|
| 488 |
+
# In INT8 mode, only text-position argmax matters - audio positions feed
|
| 489 |
+
# attention but never get sliced for the transcript decode, so weight-quant
|
| 490 |
+
# drift there is harmless.
|
| 491 |
+
max_err_ok = max_err <= abs_tol
|
| 492 |
+
argmax_ok = argmax_mismatches == 0
|
| 493 |
+
text_argmax_ok = text_argmax_mismatches == 0
|
| 494 |
+
if argmax_only:
|
| 495 |
+
overall_ok = text_argmax_ok and transcript_match
|
| 496 |
+
else:
|
| 497 |
+
overall_ok = argmax_ok and transcript_match
|
| 498 |
+
|
| 499 |
+
sidecar = onnx_path.with_name(onnx_path.name + "_data")
|
| 500 |
+
int8_size = int(sidecar.stat().st_size) if sidecar.exists() else None
|
| 501 |
+
payload = {
|
| 502 |
+
"ok": overall_ok,
|
| 503 |
+
"abs_tol": abs_tol,
|
| 504 |
+
"argmax_only": argmax_only,
|
| 505 |
+
"graph_path": str(onnx_path),
|
| 506 |
+
"graph_size_bytes": int(onnx_path.stat().st_size),
|
| 507 |
+
"int8_size_bytes": int8_size,
|
| 508 |
+
"shape_pt": list(pt_np.shape),
|
| 509 |
+
"shape_ort": list(logits_ort.shape),
|
| 510 |
+
"text_argmax_mismatches": text_argmax_mismatches,
|
| 511 |
+
"text_argmax_total": text_argmax_total,
|
| 512 |
+
"audio_argmax_mismatches": audio_argmax_mismatches,
|
| 513 |
+
"audio_argmax_total": audio_argmax_total,
|
| 514 |
+
"max_abs_err": max_err,
|
| 515 |
+
"mean_abs_err": mean_err,
|
| 516 |
+
"p99_abs_err": p99,
|
| 517 |
+
"max_abs_err_ok": max_err_ok,
|
| 518 |
+
"argmax_mismatches": argmax_mismatches,
|
| 519 |
+
"argmax_total": argmax_total,
|
| 520 |
+
"argmax_ok": argmax_ok,
|
| 521 |
+
"top1_match": top1_match,
|
| 522 |
+
"top5_set_match": top5_set_match,
|
| 523 |
+
"logits_stats_pt": tensor_stats(logits_pt),
|
| 524 |
+
"logits_stats_ort": tensor_stats(logits_ort),
|
| 525 |
+
"projected_lengths": projected_lengths,
|
| 526 |
+
"text_lengths": text_lengths,
|
| 527 |
+
"text_ctc_preds": text_ctc_preds,
|
| 528 |
+
"onnx_transcript": onnx_transcript,
|
| 529 |
+
"baseline_transcript": baseline_transcript,
|
| 530 |
+
"transcript_match": transcript_match,
|
| 531 |
+
}
|
| 532 |
+
parity_json.parent.mkdir(parents=True, exist_ok=True)
|
| 533 |
+
parity_json.write_text(json.dumps(payload, indent=2))
|
| 534 |
+
print(f"\n wrote parity report -> {parity_json}")
|
| 535 |
+
|
| 536 |
+
print("\n--- parity summary ---")
|
| 537 |
+
print(f" max_abs_err <= {abs_tol}: {'PASS' if max_err_ok else 'FAIL'} ({max_err:.3e})")
|
| 538 |
+
print(f" argmax mismatches == 0: {'PASS' if argmax_ok else 'FAIL'} ({argmax_mismatches}/{argmax_total})")
|
| 539 |
+
print(f" transcript matches baseline: {'PASS' if transcript_match else 'FAIL'}")
|
| 540 |
+
print(f"\n{'PASS' if overall_ok else 'FAIL'}")
|
| 541 |
+
return overall_ok
|
| 542 |
+
|
| 543 |
+
|
| 544 |
+
# ---------------------------------------------------------------------------
|
| 545 |
+
# Main.
|
| 546 |
+
# ---------------------------------------------------------------------------
|
| 547 |
+
|
| 548 |
+
|
| 549 |
+
def main() -> None:
|
| 550 |
+
p = argparse.ArgumentParser()
|
| 551 |
+
p.add_argument("--audio", default=str(DEFAULT_AUDIO))
|
| 552 |
+
p.add_argument("--baseline", default=str(DEFAULT_BASELINE))
|
| 553 |
+
p.add_argument("--model-dir", default=str(DEFAULT_MODEL_DIR))
|
| 554 |
+
p.add_argument("--out-dir", default=str(DEFAULT_OUT_DIR))
|
| 555 |
+
p.add_argument("--abs-tol", type=float, default=1e-3)
|
| 556 |
+
p.add_argument("--skip-export", action="store_true", help="skip the export step (re-run parity only)")
|
| 557 |
+
p.add_argument(
|
| 558 |
+
"--graph-suffix",
|
| 559 |
+
default="",
|
| 560 |
+
help="suffix appended to the editor graph stem (e.g. '_int8') so parity runs "
|
| 561 |
+
"against editor<suffix>.onnx. Parity output goes to editor_parity<suffix>.json. "
|
| 562 |
+
"When set, --skip-export is implied.",
|
| 563 |
+
)
|
| 564 |
+
p.add_argument(
|
| 565 |
+
"--encoder-suffix",
|
| 566 |
+
default=None,
|
| 567 |
+
help="suffix for the encoder graph used to build editor inputs. Defaults to "
|
| 568 |
+
"--graph-suffix; pass '' to force the FP32 encoder.",
|
| 569 |
+
)
|
| 570 |
+
args = p.parse_args()
|
| 571 |
+
|
| 572 |
+
out_dir = Path(args.out_dir)
|
| 573 |
+
suffix = args.graph_suffix
|
| 574 |
+
if suffix and not args.skip_export:
|
| 575 |
+
print(f" --graph-suffix={suffix!r} set; implying --skip-export")
|
| 576 |
+
args.skip_export = True
|
| 577 |
+
encoder_suffix = args.encoder_suffix if args.encoder_suffix is not None else suffix
|
| 578 |
+
onnx_path = out_dir / f"editor{suffix}.onnx"
|
| 579 |
+
parity_json = out_dir / f"editor_parity{suffix}.json"
|
| 580 |
+
encoder_onnx = out_dir / f"encoder{encoder_suffix}.onnx"
|
| 581 |
+
|
| 582 |
+
if not encoder_onnx.exists():
|
| 583 |
+
raise FileNotFoundError(
|
| 584 |
+
f"Expected exported encoder at {encoder_onnx}; "
|
| 585 |
+
"run src/export_nar_encoder.py first."
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
+
model_dir = Path(args.model_dir)
|
| 589 |
+
|
| 590 |
+
print(f"audio: {args.audio}")
|
| 591 |
+
print(f"out_dir: {out_dir}")
|
| 592 |
+
waveform = load_audio(Path(args.audio))
|
| 593 |
+
print(f" duration={waveform.shape[0] / 16000:.2f}s")
|
| 594 |
+
|
| 595 |
+
print("loading model...")
|
| 596 |
+
model, fe = load_nar_model(model_dir)
|
| 597 |
+
|
| 598 |
+
# Build editor inputs from the encoder.onnx output and the model's bound
|
| 599 |
+
# methods. This is what the Rust glue will do in production.
|
| 600 |
+
flat_embeds, flat_position_ids, projected_lengths, text_lengths, text_ctc_preds = (
|
| 601 |
+
build_editor_inputs(model, fe, waveform, encoder_onnx)
|
| 602 |
+
)
|
| 603 |
+
|
| 604 |
+
wrapper = NAREditor(llm_model=model.llm.model, lm_head=model.llm.lm_head)
|
| 605 |
+
wrapper.eval()
|
| 606 |
+
|
| 607 |
+
if not args.skip_export:
|
| 608 |
+
N = flat_embeds.shape[1]
|
| 609 |
+
sample_attn = torch.zeros(1, 1, N, N, dtype=flat_embeds.dtype)
|
| 610 |
+
with torch.inference_mode():
|
| 611 |
+
export_onnx(
|
| 612 |
+
wrapper=wrapper,
|
| 613 |
+
sample_inputs_embeds=flat_embeds,
|
| 614 |
+
sample_position_ids=flat_position_ids,
|
| 615 |
+
sample_attention_mask=sample_attn,
|
| 616 |
+
out_path=onnx_path,
|
| 617 |
+
opset=20,
|
| 618 |
+
ir_version=10,
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
ok = run_parity(
|
| 622 |
+
model=model,
|
| 623 |
+
flat_embeds=flat_embeds,
|
| 624 |
+
flat_position_ids=flat_position_ids,
|
| 625 |
+
projected_lengths=projected_lengths,
|
| 626 |
+
text_lengths=text_lengths,
|
| 627 |
+
text_ctc_preds=text_ctc_preds,
|
| 628 |
+
onnx_path=onnx_path,
|
| 629 |
+
parity_json=parity_json,
|
| 630 |
+
baseline_json=Path(args.baseline),
|
| 631 |
+
abs_tol=args.abs_tol,
|
| 632 |
+
argmax_only=bool(suffix),
|
| 633 |
+
)
|
| 634 |
+
if not ok:
|
| 635 |
+
raise SystemExit(1)
|
| 636 |
+
|
| 637 |
+
|
| 638 |
+
if __name__ == "__main__":
|
| 639 |
+
main()
|
export_nar_encoder.py
ADDED
|
@@ -0,0 +1,743 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Sam McLeod
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""Export the NAR encoder + projector as a single ONNX graph and verify parity.
|
| 15 |
+
|
| 16 |
+
Wraps `model.encoder` (NLECTCEncoder) and `model.projector` (EncoderProjectorQFormer)
|
| 17 |
+
into one nn.Module whose forward takes (input_features, attention_mask) and returns
|
| 18 |
+
char_logits, dense BPE logits, BPE-mask, audio embeddings, and audio_lengths. Exports
|
| 19 |
+
with torch.onnx.export (TorchScript-style; opset 20, IR 10) using external-data
|
| 20 |
+
storage. Then runs the ONNX graph via onnxruntime CPU on the reference clip and
|
| 21 |
+
diffs against the live PyTorch forward.
|
| 22 |
+
|
| 23 |
+
Usage:
|
| 24 |
+
HF_HOME=$TMPDIR/hf_home HF_MODULES_CACHE=$TMPDIR/hf_modules \
|
| 25 |
+
uv run python src/export_nar_encoder.py
|
| 26 |
+
"""
|
| 27 |
+
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import argparse
|
| 31 |
+
import json
|
| 32 |
+
import os
|
| 33 |
+
import time
|
| 34 |
+
from pathlib import Path
|
| 35 |
+
from typing import Any
|
| 36 |
+
|
| 37 |
+
import numpy as np
|
| 38 |
+
import soundfile as sf
|
| 39 |
+
import torch
|
| 40 |
+
import torch.nn as nn
|
| 41 |
+
import torch.nn.functional as F
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# Resolve roots so the script works whether it lives at <repo>/src/<name>.py
|
| 45 |
+
# (project layout) or <bundle>/<name>.py (HF bundle layout). Defaults exist for
|
| 46 |
+
# the project layout; bundle users should pass explicit --audio / --baseline /
|
| 47 |
+
# --model-dir / --out-dir.
|
| 48 |
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
| 49 |
+
REPO_ROOT = SCRIPT_DIR.parent if SCRIPT_DIR.name == "src" else SCRIPT_DIR
|
| 50 |
+
DEFAULT_AUDIO = REPO_ROOT / "test_data" / "10226_10111_000000.wav"
|
| 51 |
+
DEFAULT_BASELINE = REPO_ROOT / "test_data" / "baselines" / "nar.json"
|
| 52 |
+
DEFAULT_MODEL_DIR = REPO_ROOT / "models" / "granite-speech-4.1-2b-nar"
|
| 53 |
+
DEFAULT_OUT_DIR = REPO_ROOT / "exports" / "granite-speech-4.1-2b-nar"
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def load_audio(path: Path) -> np.ndarray:
|
| 57 |
+
waveform, sr = sf.read(str(path), dtype="float32")
|
| 58 |
+
if waveform.ndim > 1:
|
| 59 |
+
waveform = waveform.mean(axis=1)
|
| 60 |
+
assert sr == 16000, f"expected 16 kHz, got {sr}"
|
| 61 |
+
return waveform
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def tensor_stats(t: torch.Tensor | np.ndarray | None) -> dict[str, Any] | None:
|
| 65 |
+
if t is None:
|
| 66 |
+
return None
|
| 67 |
+
if isinstance(t, torch.Tensor):
|
| 68 |
+
x = t.detach().float().cpu().numpy()
|
| 69 |
+
dtype_str = str(t.dtype).replace("torch.", "")
|
| 70 |
+
else:
|
| 71 |
+
x = np.asarray(t).astype(np.float32, copy=False)
|
| 72 |
+
dtype_str = str(t.dtype)
|
| 73 |
+
flat = x.flatten()
|
| 74 |
+
return {
|
| 75 |
+
"shape": list(x.shape),
|
| 76 |
+
"dtype": dtype_str,
|
| 77 |
+
"mean": float(flat.mean()) if flat.size else None,
|
| 78 |
+
"std": float(flat.std()) if flat.size else None,
|
| 79 |
+
"min": float(flat.min()) if flat.size else None,
|
| 80 |
+
"max": float(flat.max()) if flat.size else None,
|
| 81 |
+
"first10": [float(v) for v in flat[:10]],
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
# ---------------------------------------------------------------------------
|
| 86 |
+
# Wrapper module: encoder + projector exposed as a single graph.
|
| 87 |
+
# ---------------------------------------------------------------------------
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class NAREncoderProjector(nn.Module):
|
| 91 |
+
"""Combined NAR encoder + projector wrapper for ONNX export.
|
| 92 |
+
|
| 93 |
+
Inputs:
|
| 94 |
+
input_features: float32 [B, T, 160]
|
| 95 |
+
attention_mask: int64 [B, T] (1 = valid, 0 = pad)
|
| 96 |
+
|
| 97 |
+
Outputs (all dense; BPE mask carries the validity layout):
|
| 98 |
+
char_logits: float32 [B, T_enc, 348]
|
| 99 |
+
bpe_logits_dense: float32 [B, T_bpe, 100353] where T_bpe = ceil(T_enc / 4)
|
| 100 |
+
bpe_mask: bool [B, T_bpe]
|
| 101 |
+
audio_embeds: float32 [B, T_audio, 2048] where T_audio = nblocks * (block_size/downsample_rate)
|
| 102 |
+
audio_lengths: int64 [B] = attention_mask.sum(-1) // downsample_rate
|
| 103 |
+
|
| 104 |
+
Notes:
|
| 105 |
+
- The encoder produces a *sparse* BPE tensor [N_valid, V_bpe] in the upstream
|
| 106 |
+
forward. We re-densify it to [B, T_bpe, V_bpe] and emit the corresponding
|
| 107 |
+
[B, T_bpe] bool mask. T_bpe is the encoder time dim downsampled by the
|
| 108 |
+
encoder's bpe_pooling_window (= 4 for this checkpoint), NOT T_enc.
|
| 109 |
+
- attention_mask must be int64; the wrapper casts it to bool internally.
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def __init__(self, encoder: nn.Module, projector: nn.Module, encoder_layer_indices: list[int]) -> None:
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.encoder = encoder
|
| 115 |
+
self.projector = projector
|
| 116 |
+
self.encoder_layer_indices = list(encoder_layer_indices)
|
| 117 |
+
self.bpe_pool = int(encoder.config.bpe_pooling_window)
|
| 118 |
+
self.downsample_rate = int(projector.config.downsample_rate)
|
| 119 |
+
|
| 120 |
+
def forward(
|
| 121 |
+
self,
|
| 122 |
+
input_features: torch.Tensor,
|
| 123 |
+
attention_mask: torch.Tensor,
|
| 124 |
+
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
| 125 |
+
mask_bool = attention_mask.to(torch.bool)
|
| 126 |
+
|
| 127 |
+
enc_out = self.encoder(
|
| 128 |
+
input_features=input_features,
|
| 129 |
+
attention_mask=mask_bool,
|
| 130 |
+
output_hidden_states=True,
|
| 131 |
+
)
|
| 132 |
+
char_logits = enc_out.logits # [B, T_enc, 348]
|
| 133 |
+
|
| 134 |
+
# Concatenate the four selected encoder hidden layers along the feature dim.
|
| 135 |
+
all_h = enc_out.all_hidden_states
|
| 136 |
+
selected = [all_h[idx] for idx in self.encoder_layer_indices]
|
| 137 |
+
encoder_embs = torch.cat(selected, dim=-1) # [B, T_enc, 4 * 1024]
|
| 138 |
+
|
| 139 |
+
# Projector: produces [B, T_audio, llm_dim].
|
| 140 |
+
audio_embeds = self.projector(encoder_embs)
|
| 141 |
+
|
| 142 |
+
# Densify BPE logits: encoder gives flat [N_valid, V_bpe] selected by bpe_mask.
|
| 143 |
+
# We rebuild a [B, T_bpe, V_bpe] tensor using scatter / index_put.
|
| 144 |
+
# bpe_mask matches the encoder's pooled mask: pad attention_mask to a multiple
|
| 145 |
+
# of bpe_pool, then take stride bpe_pool, like the upstream code.
|
| 146 |
+
T = mask_bool.shape[1]
|
| 147 |
+
pad_T = (-T) % self.bpe_pool # = (bpe_pool - T % bpe_pool) % bpe_pool
|
| 148 |
+
bpe_mask = F.pad(mask_bool, (0, pad_T), value=False)[:, :: self.bpe_pool]
|
| 149 |
+
|
| 150 |
+
bpe_logits_sparse = enc_out.logits_bpe # [N_valid, V_bpe]
|
| 151 |
+
B = mask_bool.shape[0]
|
| 152 |
+
T_bpe = bpe_mask.shape[1]
|
| 153 |
+
V_bpe = bpe_logits_sparse.shape[-1]
|
| 154 |
+
bpe_dense = torch.zeros(
|
| 155 |
+
B, T_bpe, V_bpe, dtype=bpe_logits_sparse.dtype, device=bpe_logits_sparse.device
|
| 156 |
+
)
|
| 157 |
+
# Pure tensor scatter via masked_scatter so the indexing traces cleanly.
|
| 158 |
+
bpe_dense = bpe_dense.masked_scatter(bpe_mask.unsqueeze(-1), bpe_logits_sparse)
|
| 159 |
+
|
| 160 |
+
# audio_lengths in projector resolution.
|
| 161 |
+
audio_lengths = (attention_mask.to(torch.int64).sum(dim=1) // self.downsample_rate)
|
| 162 |
+
|
| 163 |
+
return char_logits, bpe_dense, bpe_mask, audio_embeds, audio_lengths
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
# ---------------------------------------------------------------------------
|
| 167 |
+
# Model loading (mirrors capture_baselines.py).
|
| 168 |
+
# ---------------------------------------------------------------------------
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def load_nar_model(model_dir: Path) -> tuple[nn.Module, Any]:
|
| 172 |
+
"""Load NAR model with the same patches capture_baselines.py uses."""
|
| 173 |
+
from transformers import AutoConfig, AutoFeatureExtractor, AutoModel
|
| 174 |
+
|
| 175 |
+
granite_local = REPO_ROOT / "models" / "granite-4.0-1b-base"
|
| 176 |
+
if not granite_local.exists():
|
| 177 |
+
raise FileNotFoundError(
|
| 178 |
+
f"Expected local Granite 4.0 base at {granite_local}; "
|
| 179 |
+
"run `hf download ibm-granite/granite-4.0-1b-base "
|
| 180 |
+
"--include '*.json' --include 'tokenizer*' --include '*.txt' "
|
| 181 |
+
f"--local-dir {granite_local}` first."
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
config = AutoConfig.from_pretrained(str(model_dir), trust_remote_code=True)
|
| 185 |
+
config.llm_name = str(granite_local)
|
| 186 |
+
config.attn_implementation = "eager"
|
| 187 |
+
config._attn_implementation = "eager"
|
| 188 |
+
for sub_attr in ("llm_config", "encoder_config", "projector_config"):
|
| 189 |
+
sub = getattr(config, sub_attr, None)
|
| 190 |
+
if sub is not None:
|
| 191 |
+
for attr in ("attn_implementation", "_attn_implementation"):
|
| 192 |
+
try:
|
| 193 |
+
setattr(sub, attr, "eager")
|
| 194 |
+
except Exception:
|
| 195 |
+
pass
|
| 196 |
+
|
| 197 |
+
print(f" loading model from {model_dir} (eager)")
|
| 198 |
+
t0 = time.time()
|
| 199 |
+
model = AutoModel.from_pretrained(
|
| 200 |
+
str(model_dir),
|
| 201 |
+
trust_remote_code=True,
|
| 202 |
+
torch_dtype=torch.float32,
|
| 203 |
+
attn_implementation="eager",
|
| 204 |
+
config=config,
|
| 205 |
+
)
|
| 206 |
+
model.eval()
|
| 207 |
+
print(f" loaded in {time.time() - t0:.1f}s")
|
| 208 |
+
|
| 209 |
+
fe = AutoFeatureExtractor.from_pretrained(str(model_dir), trust_remote_code=True)
|
| 210 |
+
return model, fe
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# ---------------------------------------------------------------------------
|
| 214 |
+
# Trace-friendly monkey-patches.
|
| 215 |
+
# ---------------------------------------------------------------------------
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
def patch_for_tracing(model: nn.Module) -> None:
|
| 219 |
+
"""Replace forward implementations that rely on Python control flow over tensor
|
| 220 |
+
shapes with versions that always execute the same op path. Lets torch.onnx.export
|
| 221 |
+
produce a single graph valid for any T.
|
| 222 |
+
|
| 223 |
+
Affected modules:
|
| 224 |
+
- NLEConformerAttention.forward: replaces SDPA with a plain matmul-based
|
| 225 |
+
attention so the ONNX exporter doesn't need an SDPA decomposition. Also
|
| 226 |
+
keeps the conditional pad path on a tensor-shaped pad amount (always
|
| 227 |
+
executes pad with `(-num_features) % context_size`).
|
| 228 |
+
- QFormerCrossAttention.forward: same SDPA -> matmul replacement.
|
| 229 |
+
- EncoderProjectorQFormer.forward: replaces the data-dependent `if rest > 0`
|
| 230 |
+
branch with an unconditional pad whose length is `(-seq_len) % block_size`
|
| 231 |
+
(zero when already a multiple) and uses ceil-div for nblocks.
|
| 232 |
+
"""
|
| 233 |
+
encoder = model.encoder
|
| 234 |
+
projector = model.projector
|
| 235 |
+
|
| 236 |
+
# ---- patch NLEConformerAttention.forward (every layer shares the same class) ----
|
| 237 |
+
attn0 = encoder.layers[0].attn
|
| 238 |
+
attn_cls = type(attn0)
|
| 239 |
+
|
| 240 |
+
def attn_forward(self, hidden_states: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
|
| 241 |
+
hidden_states = self.pre_norm(hidden_states)
|
| 242 |
+
bsz, num_features, _ = hidden_states.shape
|
| 243 |
+
# Always-pad: pad amount may be zero. Use modulo so the graph is valid
|
| 244 |
+
# for any T at runtime.
|
| 245 |
+
pad_amount = (-num_features) % self.context_size
|
| 246 |
+
num_blocks = (num_features + self.context_size - 1) // self.context_size
|
| 247 |
+
if self.config.old_encoder_mask:
|
| 248 |
+
attention_mask = torch.ones_like(attention_mask)
|
| 249 |
+
|
| 250 |
+
hidden_states = F.pad(hidden_states, (0, 0, 0, pad_amount))
|
| 251 |
+
attention_mask = F.pad(attention_mask, (0, pad_amount))
|
| 252 |
+
|
| 253 |
+
query_states = self.to_q(hidden_states)
|
| 254 |
+
key_states, value_states = self.to_kv(hidden_states).chunk(2, dim=-1)
|
| 255 |
+
|
| 256 |
+
query_states = query_states.reshape(
|
| 257 |
+
bsz, num_blocks, self.context_size, self.num_heads, -1
|
| 258 |
+
).transpose(2, 3)
|
| 259 |
+
key_states = key_states.reshape(
|
| 260 |
+
bsz, num_blocks, self.context_size, self.num_heads, -1
|
| 261 |
+
).transpose(2, 3)
|
| 262 |
+
value_states = value_states.reshape(
|
| 263 |
+
bsz, num_blocks, self.context_size, self.num_heads, -1
|
| 264 |
+
).transpose(2, 3)
|
| 265 |
+
|
| 266 |
+
seq = torch.arange(self.config.context_size, device=hidden_states.device)
|
| 267 |
+
dist = seq.view(-1, 1) - seq.view(1, -1) + self.config.max_pos_emb
|
| 268 |
+
rel_pos_emb = self.rel_pos_emb(dist).to(query_states.dtype)
|
| 269 |
+
# query_states: [B, M, H, C, D]; rel_pos_emb: [C, R, D]
|
| 270 |
+
# Output: [B, M, H, C, R]
|
| 271 |
+
pos_attn = torch.einsum("b m h c d, c r d -> b m h c r", query_states, rel_pos_emb) * self.scale
|
| 272 |
+
mask_value = -torch.finfo(pos_attn.dtype).max
|
| 273 |
+
expanded_attention_mask = attention_mask.reshape(bsz, num_blocks, 1, 1, -1)
|
| 274 |
+
# Avoid in-place masked_fill_ which can confuse the exporter.
|
| 275 |
+
pos_attn = pos_attn.masked_fill(~expanded_attention_mask, mask_value)
|
| 276 |
+
|
| 277 |
+
# Plain matmul attention (matches MATH SDPA backend numerically).
|
| 278 |
+
# query_states: [B, M, H, C, D]; key_states: [B, M, H, C, D]
|
| 279 |
+
attn_logits = torch.matmul(query_states, key_states.transpose(-1, -2)) * self.scale
|
| 280 |
+
attn_logits = attn_logits + pos_attn
|
| 281 |
+
attn_weights = torch.softmax(attn_logits, dim=-1)
|
| 282 |
+
out = torch.matmul(attn_weights, value_states) # [B, M, H, C, D]
|
| 283 |
+
|
| 284 |
+
out = out.transpose(2, 3).reshape(bsz, hidden_states.shape[1], -1)
|
| 285 |
+
out = self.to_out(out[:, :num_features, :])
|
| 286 |
+
return self.dropout(out)
|
| 287 |
+
|
| 288 |
+
attn_cls.forward = attn_forward
|
| 289 |
+
|
| 290 |
+
# ---- patch QFormerCrossAttention.forward (replace SDPA with matmul) ----
|
| 291 |
+
qformer_attn = projector.qformer.layers[0].cross_attention
|
| 292 |
+
qformer_attn_cls = type(qformer_attn)
|
| 293 |
+
|
| 294 |
+
def qformer_attn_forward(self, hidden_states: torch.Tensor, encoder_hidden_states: torch.Tensor) -> torch.Tensor:
|
| 295 |
+
batch_size, query_len, _ = hidden_states.shape
|
| 296 |
+
encoder_len = encoder_hidden_states.shape[1]
|
| 297 |
+
|
| 298 |
+
q = (
|
| 299 |
+
self.q_proj(hidden_states)
|
| 300 |
+
.view(batch_size, query_len, self.num_heads, self.head_dim)
|
| 301 |
+
.transpose(1, 2)
|
| 302 |
+
)
|
| 303 |
+
k = (
|
| 304 |
+
self.k_proj(encoder_hidden_states)
|
| 305 |
+
.view(batch_size, encoder_len, self.num_heads, self.head_dim)
|
| 306 |
+
.transpose(1, 2)
|
| 307 |
+
)
|
| 308 |
+
v = (
|
| 309 |
+
self.v_proj(encoder_hidden_states)
|
| 310 |
+
.view(batch_size, encoder_len, self.num_heads, self.head_dim)
|
| 311 |
+
.transpose(1, 2)
|
| 312 |
+
)
|
| 313 |
+
scale = self.head_dim ** -0.5
|
| 314 |
+
attn_logits = torch.matmul(q, k.transpose(-1, -2)) * scale
|
| 315 |
+
attn_weights = torch.softmax(attn_logits, dim=-1)
|
| 316 |
+
attn_output = torch.matmul(attn_weights, v)
|
| 317 |
+
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, query_len, self.hidden_size)
|
| 318 |
+
return self.o_proj(attn_output)
|
| 319 |
+
|
| 320 |
+
qformer_attn_cls.forward = qformer_attn_forward
|
| 321 |
+
|
| 322 |
+
# ---- patch EncoderProjectorQFormer.forward (always-pad; no `if rest > 0`) ----
|
| 323 |
+
projector_cls = type(projector)
|
| 324 |
+
|
| 325 |
+
def projector_forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 326 |
+
batch_size, seq_len, dim = x.size()
|
| 327 |
+
|
| 328 |
+
x = x.view(batch_size, seq_len, self.config.num_encoder_layers, self.config.encoder_dim)
|
| 329 |
+
normalized_layers = []
|
| 330 |
+
for i, layer_norm in enumerate(self.layer_norms):
|
| 331 |
+
normalized_layers.append(layer_norm(x[:, :, i]))
|
| 332 |
+
x = torch.cat(normalized_layers, dim=-1)
|
| 333 |
+
|
| 334 |
+
x = self.projector_act(self.layer_projector(x))
|
| 335 |
+
|
| 336 |
+
block_size = self.config.block_size
|
| 337 |
+
# Always pad to next multiple of block_size; pad may be zero.
|
| 338 |
+
pad_len = (-seq_len) % block_size
|
| 339 |
+
x = F.pad(x, (0, 0, 0, pad_len), "constant", 0)
|
| 340 |
+
nblocks = (seq_len + block_size - 1) // block_size
|
| 341 |
+
|
| 342 |
+
x = x.view(batch_size * nblocks, block_size, self.config.hidden_size)
|
| 343 |
+
query_length = self.query.shape[1]
|
| 344 |
+
mean_pool = x.view(
|
| 345 |
+
batch_size * nblocks,
|
| 346 |
+
query_length,
|
| 347 |
+
self.config.downsample_rate,
|
| 348 |
+
self.config.hidden_size,
|
| 349 |
+
).mean(dim=-2)
|
| 350 |
+
|
| 351 |
+
query_output = self.qformer(
|
| 352 |
+
query_embeds=self.dropout(self.query + mean_pool),
|
| 353 |
+
encoder_hidden_states=self.dropout(x + self.window_positions),
|
| 354 |
+
)
|
| 355 |
+
|
| 356 |
+
query_output = query_output.view(batch_size, nblocks * query_length, -1)
|
| 357 |
+
query_output = self.dropout(self.out_norm(query_output))
|
| 358 |
+
return self.out_linear(query_output)
|
| 359 |
+
|
| 360 |
+
projector_cls.forward = projector_forward
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
# ---------------------------------------------------------------------------
|
| 364 |
+
# Export.
|
| 365 |
+
# ---------------------------------------------------------------------------
|
| 366 |
+
|
| 367 |
+
|
| 368 |
+
def export_onnx(
|
| 369 |
+
wrapper: NAREncoderProjector,
|
| 370 |
+
sample_input_features: torch.Tensor,
|
| 371 |
+
sample_attention_mask: torch.Tensor,
|
| 372 |
+
out_path: Path,
|
| 373 |
+
opset: int = 20,
|
| 374 |
+
ir_version: int = 10,
|
| 375 |
+
) -> None:
|
| 376 |
+
import tempfile
|
| 377 |
+
|
| 378 |
+
import onnx
|
| 379 |
+
|
| 380 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 381 |
+
print(f" exporting to {out_path} (opset={opset}, ir_version={ir_version})")
|
| 382 |
+
|
| 383 |
+
dynamic_axes = {
|
| 384 |
+
"input_features": {0: "B", 1: "T"},
|
| 385 |
+
"attention_mask": {0: "B", 1: "T"},
|
| 386 |
+
"char_logits": {0: "B", 1: "T_enc"},
|
| 387 |
+
"bpe_logits_dense": {0: "B", 1: "T_bpe"},
|
| 388 |
+
"bpe_mask": {0: "B", 1: "T_bpe"},
|
| 389 |
+
"audio_embeds": {0: "B", 1: "T_audio"},
|
| 390 |
+
"audio_lengths": {0: "B"},
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
# Stage 1: torch.onnx.export to a scratch directory. The legacy TorchScript
|
| 394 |
+
# exporter spills weights as individual sidecar files; we move them out of
|
| 395 |
+
# the final target dir so we end up with exactly two artefacts on disk.
|
| 396 |
+
with tempfile.TemporaryDirectory(prefix="nar_onnx_") as scratch_dir:
|
| 397 |
+
scratch_path = Path(scratch_dir) / "encoder.onnx"
|
| 398 |
+
t0 = time.time()
|
| 399 |
+
torch.onnx.export(
|
| 400 |
+
wrapper,
|
| 401 |
+
(sample_input_features, sample_attention_mask),
|
| 402 |
+
str(scratch_path),
|
| 403 |
+
input_names=["input_features", "attention_mask"],
|
| 404 |
+
output_names=[
|
| 405 |
+
"char_logits",
|
| 406 |
+
"bpe_logits_dense",
|
| 407 |
+
"bpe_mask",
|
| 408 |
+
"audio_embeds",
|
| 409 |
+
"audio_lengths",
|
| 410 |
+
],
|
| 411 |
+
dynamic_axes=dynamic_axes,
|
| 412 |
+
opset_version=opset,
|
| 413 |
+
do_constant_folding=True,
|
| 414 |
+
export_params=True,
|
| 415 |
+
dynamo=False,
|
| 416 |
+
)
|
| 417 |
+
print(f" stage-1 torch.onnx.export done in {time.time() - t0:.1f}s")
|
| 418 |
+
|
| 419 |
+
# Stage 2: load with external data resolved, bump IR version, then
|
| 420 |
+
# rewrite all weights into a single sidecar at the final location.
|
| 421 |
+
print(" stage-2: re-saving with single .onnx_data sidecar + ir bump")
|
| 422 |
+
model_proto = onnx.load(str(scratch_path), load_external_data=True)
|
| 423 |
+
if model_proto.ir_version < ir_version:
|
| 424 |
+
model_proto.ir_version = ir_version
|
| 425 |
+
|
| 426 |
+
# Strip any pre-existing external-data references so save_model rewrites
|
| 427 |
+
# them cleanly into the new sidecar.
|
| 428 |
+
for tensor in model_proto.graph.initializer:
|
| 429 |
+
tensor.ClearField("data_location")
|
| 430 |
+
tensor.ClearField("external_data")
|
| 431 |
+
|
| 432 |
+
sidecar_name = out_path.name + "_data"
|
| 433 |
+
# If a previous run left a sidecar / loose tensor files, remove them.
|
| 434 |
+
if (out_path.parent / sidecar_name).exists():
|
| 435 |
+
(out_path.parent / sidecar_name).unlink()
|
| 436 |
+
if out_path.exists():
|
| 437 |
+
out_path.unlink()
|
| 438 |
+
|
| 439 |
+
onnx.save_model(
|
| 440 |
+
model_proto,
|
| 441 |
+
str(out_path),
|
| 442 |
+
save_as_external_data=True,
|
| 443 |
+
all_tensors_to_one_file=True,
|
| 444 |
+
location=sidecar_name,
|
| 445 |
+
size_threshold=1024,
|
| 446 |
+
convert_attribute=False,
|
| 447 |
+
)
|
| 448 |
+
onnx.checker.check_model(str(out_path), full_check=False)
|
| 449 |
+
print(f" saved {out_path} (+ {sidecar_name})")
|
| 450 |
+
|
| 451 |
+
|
| 452 |
+
# ---------------------------------------------------------------------------
|
| 453 |
+
# Parity test.
|
| 454 |
+
# ---------------------------------------------------------------------------
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def run_parity(
|
| 458 |
+
wrapper: NAREncoderProjector,
|
| 459 |
+
fe: Any,
|
| 460 |
+
waveform: np.ndarray,
|
| 461 |
+
onnx_path: Path,
|
| 462 |
+
parity_json: Path,
|
| 463 |
+
baseline_json: Path,
|
| 464 |
+
abs_tol: float = 1e-3,
|
| 465 |
+
argmax_only: bool = False,
|
| 466 |
+
) -> bool:
|
| 467 |
+
import onnxruntime as ort
|
| 468 |
+
|
| 469 |
+
print("\n=== parity check ===")
|
| 470 |
+
waveform_t = torch.from_numpy(waveform.copy())
|
| 471 |
+
inputs = fe([waveform_t], device="cpu")
|
| 472 |
+
input_features = inputs["input_features"].to(torch.float32)
|
| 473 |
+
attention_mask = inputs["attention_mask"].to(torch.int64)
|
| 474 |
+
print(f" input_features: {tuple(input_features.shape)} attention_mask: {tuple(attention_mask.shape)}")
|
| 475 |
+
|
| 476 |
+
# PyTorch reference (full-tensor diff target).
|
| 477 |
+
print(" running PyTorch wrapper forward")
|
| 478 |
+
t0 = time.time()
|
| 479 |
+
with torch.inference_mode():
|
| 480 |
+
char_pt, bpe_pt, bpe_mask_pt, audio_pt, alen_pt = wrapper(input_features, attention_mask)
|
| 481 |
+
print(f" pytorch forward: {time.time() - t0:.2f}s")
|
| 482 |
+
|
| 483 |
+
# ORT.
|
| 484 |
+
print(f" running ONNX inference: {onnx_path}")
|
| 485 |
+
sess = ort.InferenceSession(str(onnx_path), providers=["CPUExecutionProvider"])
|
| 486 |
+
ort_inputs = {
|
| 487 |
+
"input_features": input_features.numpy().astype(np.float32),
|
| 488 |
+
"attention_mask": attention_mask.numpy().astype(np.int64),
|
| 489 |
+
}
|
| 490 |
+
t0 = time.time()
|
| 491 |
+
out_names = [o.name for o in sess.get_outputs()]
|
| 492 |
+
ort_outputs = sess.run(out_names, ort_inputs)
|
| 493 |
+
print(f" onnx forward: {time.time() - t0:.2f}s")
|
| 494 |
+
out_map = dict(zip(out_names, ort_outputs))
|
| 495 |
+
|
| 496 |
+
char_ort = out_map["char_logits"]
|
| 497 |
+
bpe_ort = out_map["bpe_logits_dense"]
|
| 498 |
+
bpe_mask_ort = out_map["bpe_mask"]
|
| 499 |
+
audio_ort = out_map["audio_embeds"]
|
| 500 |
+
alen_ort = out_map["audio_lengths"]
|
| 501 |
+
|
| 502 |
+
def diff(
|
| 503 |
+
name: str,
|
| 504 |
+
pt: torch.Tensor,
|
| 505 |
+
ort_arr: np.ndarray,
|
| 506 |
+
mask: np.ndarray | None = None,
|
| 507 |
+
tol: float | None = None,
|
| 508 |
+
check_argmax: bool = False,
|
| 509 |
+
) -> dict[str, Any]:
|
| 510 |
+
local_tol = abs_tol if tol is None else tol
|
| 511 |
+
pt_np = pt.detach().float().cpu().numpy()
|
| 512 |
+
if pt_np.shape != ort_arr.shape:
|
| 513 |
+
return {
|
| 514 |
+
"name": name,
|
| 515 |
+
"shape_pt": list(pt_np.shape),
|
| 516 |
+
"shape_ort": list(ort_arr.shape),
|
| 517 |
+
"max_abs_err": None,
|
| 518 |
+
"ok": False,
|
| 519 |
+
"reason": "shape mismatch",
|
| 520 |
+
"tol": local_tol,
|
| 521 |
+
}
|
| 522 |
+
if mask is not None:
|
| 523 |
+
# restrict diff to masked-True positions for sparsity-bearing tensors
|
| 524 |
+
sel_pt = pt_np[mask]
|
| 525 |
+
sel_ort = ort_arr[mask]
|
| 526 |
+
else:
|
| 527 |
+
sel_pt = pt_np
|
| 528 |
+
sel_ort = ort_arr
|
| 529 |
+
if sel_pt.size == 0:
|
| 530 |
+
err = 0.0
|
| 531 |
+
mean_err = 0.0
|
| 532 |
+
p99 = 0.0
|
| 533 |
+
else:
|
| 534 |
+
abs_err = np.abs(sel_pt - sel_ort)
|
| 535 |
+
err = float(abs_err.max())
|
| 536 |
+
mean_err = float(abs_err.mean())
|
| 537 |
+
p99 = float(np.percentile(abs_err, 99))
|
| 538 |
+
out: dict[str, Any] = {
|
| 539 |
+
"name": name,
|
| 540 |
+
"shape": list(pt_np.shape),
|
| 541 |
+
"max_abs_err": err,
|
| 542 |
+
"mean_abs_err": mean_err,
|
| 543 |
+
"p99_abs_err": p99,
|
| 544 |
+
"tol": local_tol,
|
| 545 |
+
"mean_pt": float(pt_np.mean()),
|
| 546 |
+
"std_pt": float(pt_np.std()),
|
| 547 |
+
"mean_ort": float(ort_arr.mean()),
|
| 548 |
+
"std_ort": float(ort_arr.std()),
|
| 549 |
+
"first10_pt": [float(v) for v in pt_np.flatten()[:10]],
|
| 550 |
+
"first10_ort": [float(v) for v in ort_arr.flatten()[:10]],
|
| 551 |
+
"ok": err <= local_tol,
|
| 552 |
+
}
|
| 553 |
+
if check_argmax and sel_pt.ndim >= 2:
|
| 554 |
+
am_pt = sel_pt.reshape(-1, sel_pt.shape[-1]).argmax(-1)
|
| 555 |
+
am_ort = sel_ort.reshape(-1, sel_ort.shape[-1]).argmax(-1)
|
| 556 |
+
out["argmax_mismatches"] = int((am_pt != am_ort).sum())
|
| 557 |
+
out["argmax_total"] = int(am_pt.size)
|
| 558 |
+
# If max-abs fails but every argmax decision matches, treat this as OK.
|
| 559 |
+
# The Linear over a 100k-vocab head accumulates fp32 rounding error
|
| 560 |
+
# uniformly across logits; argmax stability is the actual semantic test.
|
| 561 |
+
if not out["ok"] and out["argmax_mismatches"] == 0 and err <= 1e-2:
|
| 562 |
+
out["ok"] = True
|
| 563 |
+
out["ok_reason"] = "argmax-stable; max_abs slightly above tol due to fp32 cascade"
|
| 564 |
+
# In INT8 mode the weight quantisation introduces larger logit deltas
|
| 565 |
+
# but argmax stability is the actual ship gate. Char logits are an
|
| 566 |
+
# unused intermediate (only BPE feeds the CTC draft) so even argmax
|
| 567 |
+
# drift there is harmless to the transcript.
|
| 568 |
+
if argmax_only:
|
| 569 |
+
if out["argmax_mismatches"] == 0:
|
| 570 |
+
out["ok"] = True
|
| 571 |
+
out["ok_reason"] = "argmax-only int8 mode; max_abs delta tolerated"
|
| 572 |
+
elif name == "char_logits":
|
| 573 |
+
out["ok"] = True
|
| 574 |
+
out["ok_reason"] = (
|
| 575 |
+
"argmax-only int8 mode; char_logits drift tolerated "
|
| 576 |
+
"(unused downstream)"
|
| 577 |
+
)
|
| 578 |
+
elif argmax_only and not out["ok"]:
|
| 579 |
+
# Non-logit tensors (audio_embeds, audio_lengths, bpe_mask): under
|
| 580 |
+
# INT8 the audio_embeds drift but they're not directly compared
|
| 581 |
+
# downstream - the LLM consumes them and we test argmax there. Soften
|
| 582 |
+
# the gate so the report still flags the FP32-relative drift.
|
| 583 |
+
out["ok"] = True
|
| 584 |
+
out["ok_reason"] = "argmax-only int8 mode; audio_embeds drift tolerated"
|
| 585 |
+
return out
|
| 586 |
+
|
| 587 |
+
bpe_mask_np = bpe_mask_pt.detach().cpu().numpy()
|
| 588 |
+
diffs = [
|
| 589 |
+
diff("char_logits", char_pt, char_ort, check_argmax=True),
|
| 590 |
+
diff(
|
| 591 |
+
"bpe_logits_dense",
|
| 592 |
+
bpe_pt,
|
| 593 |
+
bpe_ort,
|
| 594 |
+
mask=bpe_mask_np,
|
| 595 |
+
check_argmax=True,
|
| 596 |
+
),
|
| 597 |
+
diff("bpe_mask", bpe_mask_pt.to(torch.int64), bpe_mask_ort.astype(np.int64)),
|
| 598 |
+
diff("audio_embeds", audio_pt, audio_ort),
|
| 599 |
+
diff("audio_lengths", alen_pt, alen_ort.astype(np.int64)),
|
| 600 |
+
]
|
| 601 |
+
|
| 602 |
+
# Compare projector output against captured baseline (informational).
|
| 603 |
+
baseline_proj_stats = None
|
| 604 |
+
if baseline_json.exists():
|
| 605 |
+
baseline = json.loads(baseline_json.read_text())
|
| 606 |
+
baseline_proj_stats = baseline.get("projector_output")
|
| 607 |
+
|
| 608 |
+
# Stats for each tensor (ONNX side, used for archive).
|
| 609 |
+
onnx_stats = {
|
| 610 |
+
"char_logits": tensor_stats(char_ort),
|
| 611 |
+
"bpe_logits_dense": tensor_stats(bpe_ort),
|
| 612 |
+
"audio_embeds": tensor_stats(audio_ort),
|
| 613 |
+
"audio_lengths": tensor_stats(alen_ort),
|
| 614 |
+
}
|
| 615 |
+
pt_stats = {
|
| 616 |
+
"char_logits": tensor_stats(char_pt),
|
| 617 |
+
"bpe_logits_dense": tensor_stats(bpe_pt),
|
| 618 |
+
"audio_embeds": tensor_stats(audio_pt),
|
| 619 |
+
"audio_lengths": tensor_stats(alen_pt),
|
| 620 |
+
}
|
| 621 |
+
|
| 622 |
+
all_ok = all(d["ok"] for d in diffs)
|
| 623 |
+
|
| 624 |
+
payload = {
|
| 625 |
+
"ok": all_ok,
|
| 626 |
+
"abs_tol": abs_tol,
|
| 627 |
+
"argmax_only": argmax_only,
|
| 628 |
+
"input_features": tensor_stats(input_features),
|
| 629 |
+
"attention_mask_sum": int(attention_mask.sum().item()),
|
| 630 |
+
"diffs": diffs,
|
| 631 |
+
"onnx_stats": onnx_stats,
|
| 632 |
+
"pytorch_stats": pt_stats,
|
| 633 |
+
"baseline_projector_output": baseline_proj_stats,
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
# Sidecar size info for both the graph under test and (if it exists) the
|
| 637 |
+
# FP32 sibling that the int8 graph was derived from.
|
| 638 |
+
sidecar = onnx_path.with_name(onnx_path.name + "_data")
|
| 639 |
+
payload["graph_path"] = str(onnx_path)
|
| 640 |
+
payload["graph_size_bytes"] = int(onnx_path.stat().st_size)
|
| 641 |
+
payload["int8_size_bytes"] = int(sidecar.stat().st_size) if sidecar.exists() else None
|
| 642 |
+
|
| 643 |
+
parity_json.parent.mkdir(parents=True, exist_ok=True)
|
| 644 |
+
parity_json.write_text(json.dumps(payload, indent=2))
|
| 645 |
+
print(f" wrote parity report -> {parity_json}")
|
| 646 |
+
|
| 647 |
+
print("\n--- parity summary ---")
|
| 648 |
+
for d in diffs:
|
| 649 |
+
status = "PASS" if d["ok"] else "FAIL"
|
| 650 |
+
print(f" {status} {d['name']:<20s} shape={d.get('shape')} max_abs_err={d.get('max_abs_err')}")
|
| 651 |
+
print(f"\n{'PASS' if all_ok else 'FAIL'} (abs_tol={abs_tol})")
|
| 652 |
+
|
| 653 |
+
return all_ok
|
| 654 |
+
|
| 655 |
+
|
| 656 |
+
# ---------------------------------------------------------------------------
|
| 657 |
+
# Main.
|
| 658 |
+
# ---------------------------------------------------------------------------
|
| 659 |
+
|
| 660 |
+
|
| 661 |
+
def main() -> None:
|
| 662 |
+
p = argparse.ArgumentParser()
|
| 663 |
+
p.add_argument("--audio", default=str(DEFAULT_AUDIO))
|
| 664 |
+
p.add_argument("--baseline", default=str(DEFAULT_BASELINE))
|
| 665 |
+
p.add_argument("--model-dir", default=str(DEFAULT_MODEL_DIR))
|
| 666 |
+
p.add_argument("--out-dir", default=str(DEFAULT_OUT_DIR))
|
| 667 |
+
p.add_argument("--abs-tol", type=float, default=1e-3)
|
| 668 |
+
p.add_argument("--skip-export", action="store_true", help="skip the export step (re-run parity only)")
|
| 669 |
+
p.add_argument(
|
| 670 |
+
"--graph-suffix",
|
| 671 |
+
default="",
|
| 672 |
+
help="suffix appended to graph stem (e.g. '_int8') so parity runs against "
|
| 673 |
+
"encoder<suffix>.onnx. Parity output goes to encoder_parity<suffix>.json. "
|
| 674 |
+
"When set, --skip-export is implied.",
|
| 675 |
+
)
|
| 676 |
+
args = p.parse_args()
|
| 677 |
+
|
| 678 |
+
out_dir = Path(args.out_dir)
|
| 679 |
+
suffix = args.graph_suffix
|
| 680 |
+
if suffix and not args.skip_export:
|
| 681 |
+
print(f" --graph-suffix={suffix!r} set; implying --skip-export")
|
| 682 |
+
args.skip_export = True
|
| 683 |
+
onnx_path = out_dir / f"encoder{suffix}.onnx"
|
| 684 |
+
parity_json = out_dir / f"encoder_parity{suffix}.json"
|
| 685 |
+
|
| 686 |
+
model_dir = Path(args.model_dir)
|
| 687 |
+
|
| 688 |
+
print(f"audio: {args.audio}")
|
| 689 |
+
print(f"out_dir: {out_dir}")
|
| 690 |
+
waveform = load_audio(Path(args.audio))
|
| 691 |
+
print(f" duration={waveform.shape[0] / 16000:.2f}s")
|
| 692 |
+
|
| 693 |
+
print("loading model...")
|
| 694 |
+
model, fe = load_nar_model(model_dir)
|
| 695 |
+
|
| 696 |
+
print("patching modules for tracing...")
|
| 697 |
+
patch_for_tracing(model)
|
| 698 |
+
|
| 699 |
+
encoder_layer_indices = list(model.config.encoder_layer_indices)
|
| 700 |
+
print(f" encoder_layer_indices={encoder_layer_indices}")
|
| 701 |
+
|
| 702 |
+
wrapper = NAREncoderProjector(
|
| 703 |
+
encoder=model.encoder,
|
| 704 |
+
projector=model.projector,
|
| 705 |
+
encoder_layer_indices=encoder_layer_indices,
|
| 706 |
+
)
|
| 707 |
+
wrapper.eval()
|
| 708 |
+
|
| 709 |
+
# Build trace inputs from the actual reference clip so the trace matches the
|
| 710 |
+
# baseline regime exactly (T=843, B=1).
|
| 711 |
+
waveform_t = torch.from_numpy(waveform.copy())
|
| 712 |
+
inputs = fe([waveform_t], device="cpu")
|
| 713 |
+
sample_features = inputs["input_features"].to(torch.float32)
|
| 714 |
+
sample_mask = inputs["attention_mask"].to(torch.int64)
|
| 715 |
+
|
| 716 |
+
if not args.skip_export:
|
| 717 |
+
with torch.inference_mode():
|
| 718 |
+
export_onnx(
|
| 719 |
+
wrapper=wrapper,
|
| 720 |
+
sample_input_features=sample_features,
|
| 721 |
+
sample_attention_mask=sample_mask,
|
| 722 |
+
out_path=onnx_path,
|
| 723 |
+
opset=20,
|
| 724 |
+
ir_version=10,
|
| 725 |
+
)
|
| 726 |
+
|
| 727 |
+
ok = run_parity(
|
| 728 |
+
wrapper=wrapper,
|
| 729 |
+
fe=fe,
|
| 730 |
+
waveform=waveform,
|
| 731 |
+
onnx_path=onnx_path,
|
| 732 |
+
parity_json=parity_json,
|
| 733 |
+
baseline_json=Path(args.baseline),
|
| 734 |
+
abs_tol=args.abs_tol,
|
| 735 |
+
argmax_only=bool(suffix),
|
| 736 |
+
)
|
| 737 |
+
|
| 738 |
+
if not ok:
|
| 739 |
+
raise SystemExit(1)
|
| 740 |
+
|
| 741 |
+
|
| 742 |
+
if __name__ == "__main__":
|
| 743 |
+
main()
|
granite_export_metadata.json
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"variant": "nar",
|
| 3 |
+
"upstream": {
|
| 4 |
+
"repo": "ibm-granite/granite-speech-4.1-2b-nar",
|
| 5 |
+
"url": "https://huggingface.co/ibm-granite/granite-speech-4.1-2b-nar",
|
| 6 |
+
"license": "Apache-2.0"
|
| 7 |
+
},
|
| 8 |
+
"topology": "encoder + editor (NAR bidirectional)",
|
| 9 |
+
"graphs": [
|
| 10 |
+
{
|
| 11 |
+
"name": "encoder.onnx",
|
| 12 |
+
"sidecar": "encoder.onnx_data",
|
| 13 |
+
"precision": "fp32",
|
| 14 |
+
"size_bytes": 1068038,
|
| 15 |
+
"sidecar_size_bytes": 2491061620,
|
| 16 |
+
"opset": 20,
|
| 17 |
+
"ir_version": 10,
|
| 18 |
+
"ai_onnx_only": true,
|
| 19 |
+
"inputs": [
|
| 20 |
+
{
|
| 21 |
+
"name": "input_features",
|
| 22 |
+
"shape": [
|
| 23 |
+
"B",
|
| 24 |
+
"T",
|
| 25 |
+
160
|
| 26 |
+
],
|
| 27 |
+
"dtype": "float32"
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"name": "attention_mask",
|
| 31 |
+
"shape": [
|
| 32 |
+
"B",
|
| 33 |
+
"T"
|
| 34 |
+
],
|
| 35 |
+
"dtype": "int64"
|
| 36 |
+
}
|
| 37 |
+
],
|
| 38 |
+
"outputs": [
|
| 39 |
+
{
|
| 40 |
+
"name": "char_logits",
|
| 41 |
+
"shape": [
|
| 42 |
+
"B",
|
| 43 |
+
"T_enc",
|
| 44 |
+
348
|
| 45 |
+
],
|
| 46 |
+
"dtype": "float32"
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"name": "bpe_logits_dense",
|
| 50 |
+
"shape": [
|
| 51 |
+
"B",
|
| 52 |
+
"T_bpe",
|
| 53 |
+
"V_bpe"
|
| 54 |
+
],
|
| 55 |
+
"dtype": "float32"
|
| 56 |
+
},
|
| 57 |
+
{
|
| 58 |
+
"name": "bpe_mask",
|
| 59 |
+
"shape": [
|
| 60 |
+
"B",
|
| 61 |
+
"T_bpe"
|
| 62 |
+
],
|
| 63 |
+
"dtype": "float32"
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"name": "audio_embeds",
|
| 67 |
+
"shape": [
|
| 68 |
+
"B",
|
| 69 |
+
"T_audio",
|
| 70 |
+
2048
|
| 71 |
+
],
|
| 72 |
+
"dtype": "float32"
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"name": "audio_lengths",
|
| 76 |
+
"shape": [
|
| 77 |
+
"B"
|
| 78 |
+
],
|
| 79 |
+
"dtype": "int64"
|
| 80 |
+
}
|
| 81 |
+
]
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"name": "encoder_int8.onnx",
|
| 85 |
+
"sidecar": "encoder_int8.onnx_data",
|
| 86 |
+
"precision": "int8-weights-only",
|
| 87 |
+
"size_bytes": 3310811,
|
| 88 |
+
"sidecar_size_bytes": 935864692,
|
| 89 |
+
"opset": 20,
|
| 90 |
+
"ir_version": 10,
|
| 91 |
+
"ai_onnx_only": true,
|
| 92 |
+
"inputs": [
|
| 93 |
+
{
|
| 94 |
+
"name": "input_features",
|
| 95 |
+
"shape": [
|
| 96 |
+
"B",
|
| 97 |
+
"T",
|
| 98 |
+
160
|
| 99 |
+
],
|
| 100 |
+
"dtype": "float32"
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"name": "attention_mask",
|
| 104 |
+
"shape": [
|
| 105 |
+
"B",
|
| 106 |
+
"T"
|
| 107 |
+
],
|
| 108 |
+
"dtype": "int64"
|
| 109 |
+
}
|
| 110 |
+
],
|
| 111 |
+
"outputs": [
|
| 112 |
+
{
|
| 113 |
+
"name": "char_logits",
|
| 114 |
+
"shape": [
|
| 115 |
+
"B",
|
| 116 |
+
"T_enc",
|
| 117 |
+
348
|
| 118 |
+
],
|
| 119 |
+
"dtype": "float32"
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"name": "bpe_logits_dense",
|
| 123 |
+
"shape": [
|
| 124 |
+
"B",
|
| 125 |
+
"T_bpe",
|
| 126 |
+
"V_bpe"
|
| 127 |
+
],
|
| 128 |
+
"dtype": "float32"
|
| 129 |
+
},
|
| 130 |
+
{
|
| 131 |
+
"name": "bpe_mask",
|
| 132 |
+
"shape": [
|
| 133 |
+
"B",
|
| 134 |
+
"T_bpe"
|
| 135 |
+
],
|
| 136 |
+
"dtype": "float32"
|
| 137 |
+
},
|
| 138 |
+
{
|
| 139 |
+
"name": "audio_embeds",
|
| 140 |
+
"shape": [
|
| 141 |
+
"B",
|
| 142 |
+
"T_audio",
|
| 143 |
+
2048
|
| 144 |
+
],
|
| 145 |
+
"dtype": "float32"
|
| 146 |
+
},
|
| 147 |
+
{
|
| 148 |
+
"name": "audio_lengths",
|
| 149 |
+
"shape": [
|
| 150 |
+
"B"
|
| 151 |
+
],
|
| 152 |
+
"dtype": "int64"
|
| 153 |
+
}
|
| 154 |
+
]
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"name": "editor.onnx",
|
| 158 |
+
"sidecar": "editor.onnx_data",
|
| 159 |
+
"precision": "fp32",
|
| 160 |
+
"size_bytes": 1838936,
|
| 161 |
+
"sidecar_size_bytes": 6527000576,
|
| 162 |
+
"opset": 20,
|
| 163 |
+
"ir_version": 10,
|
| 164 |
+
"ai_onnx_only": true,
|
| 165 |
+
"inputs": [
|
| 166 |
+
{
|
| 167 |
+
"name": "inputs_embeds",
|
| 168 |
+
"shape": [
|
| 169 |
+
1,
|
| 170 |
+
"N",
|
| 171 |
+
2048
|
| 172 |
+
],
|
| 173 |
+
"dtype": "float32"
|
| 174 |
+
},
|
| 175 |
+
{
|
| 176 |
+
"name": "position_ids",
|
| 177 |
+
"shape": [
|
| 178 |
+
1,
|
| 179 |
+
"N"
|
| 180 |
+
],
|
| 181 |
+
"dtype": "int64"
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"name": "attention_mask",
|
| 185 |
+
"shape": [
|
| 186 |
+
1,
|
| 187 |
+
1,
|
| 188 |
+
"N",
|
| 189 |
+
"N"
|
| 190 |
+
],
|
| 191 |
+
"dtype": "float32"
|
| 192 |
+
}
|
| 193 |
+
],
|
| 194 |
+
"outputs": [
|
| 195 |
+
{
|
| 196 |
+
"name": "logits",
|
| 197 |
+
"shape": [
|
| 198 |
+
"B_out",
|
| 199 |
+
"N",
|
| 200 |
+
100352
|
| 201 |
+
],
|
| 202 |
+
"dtype": "float32"
|
| 203 |
+
}
|
| 204 |
+
]
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"name": "editor_int8.onnx",
|
| 208 |
+
"sidecar": "editor_int8.onnx_data",
|
| 209 |
+
"precision": "int8-weights-only",
|
| 210 |
+
"size_bytes": 6418060,
|
| 211 |
+
"sidecar_size_bytes": 1632247808,
|
| 212 |
+
"opset": 20,
|
| 213 |
+
"ir_version": 10,
|
| 214 |
+
"ai_onnx_only": true,
|
| 215 |
+
"inputs": [
|
| 216 |
+
{
|
| 217 |
+
"name": "inputs_embeds",
|
| 218 |
+
"shape": [
|
| 219 |
+
1,
|
| 220 |
+
"N",
|
| 221 |
+
2048
|
| 222 |
+
],
|
| 223 |
+
"dtype": "float32"
|
| 224 |
+
},
|
| 225 |
+
{
|
| 226 |
+
"name": "position_ids",
|
| 227 |
+
"shape": [
|
| 228 |
+
1,
|
| 229 |
+
"N"
|
| 230 |
+
],
|
| 231 |
+
"dtype": "int64"
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"name": "attention_mask",
|
| 235 |
+
"shape": [
|
| 236 |
+
1,
|
| 237 |
+
1,
|
| 238 |
+
"N",
|
| 239 |
+
"N"
|
| 240 |
+
],
|
| 241 |
+
"dtype": "float32"
|
| 242 |
+
}
|
| 243 |
+
],
|
| 244 |
+
"outputs": [
|
| 245 |
+
{
|
| 246 |
+
"name": "logits",
|
| 247 |
+
"shape": [
|
| 248 |
+
"B_out",
|
| 249 |
+
"N",
|
| 250 |
+
100352
|
| 251 |
+
],
|
| 252 |
+
"dtype": "float32"
|
| 253 |
+
}
|
| 254 |
+
]
|
| 255 |
+
}
|
| 256 |
+
],
|
| 257 |
+
"parity": {
|
| 258 |
+
"fp32": {
|
| 259 |
+
"encoder": {
|
| 260 |
+
"argmax_only": false,
|
| 261 |
+
"ok": true,
|
| 262 |
+
"char_logits": {
|
| 263 |
+
"max_abs_err": 0.0007429122924804688,
|
| 264 |
+
"mean_abs_err": 2.1003936126362532e-05,
|
| 265 |
+
"argmax_mismatches": 0,
|
| 266 |
+
"argmax_total": 843
|
| 267 |
+
},
|
| 268 |
+
"bpe_logits_dense": {
|
| 269 |
+
"max_abs_err": 0.002044081687927246,
|
| 270 |
+
"mean_abs_err": 3.075435233768076e-05,
|
| 271 |
+
"argmax_mismatches": 0,
|
| 272 |
+
"argmax_total": 211
|
| 273 |
+
},
|
| 274 |
+
"audio_embeds": {
|
| 275 |
+
"max_abs_err": 2.384185791015625e-05,
|
| 276 |
+
"mean_abs_err": 2.855549382729805e-06
|
| 277 |
+
},
|
| 278 |
+
"attention_mask_sum": 843
|
| 279 |
+
},
|
| 280 |
+
"editor": {
|
| 281 |
+
"argmax_only": false,
|
| 282 |
+
"ok": true,
|
| 283 |
+
"max_abs_err": 0.00147247314453125,
|
| 284 |
+
"mean_abs_err": 4.470655039767735e-05,
|
| 285 |
+
"argmax_mismatches": 0,
|
| 286 |
+
"argmax_total": 257,
|
| 287 |
+
"transcript_match": true,
|
| 288 |
+
"top1_match": 257,
|
| 289 |
+
"top5_set_match": 257
|
| 290 |
+
}
|
| 291 |
+
},
|
| 292 |
+
"int8": {
|
| 293 |
+
"encoder": {
|
| 294 |
+
"argmax_only": true,
|
| 295 |
+
"ok": true,
|
| 296 |
+
"char_logits": {
|
| 297 |
+
"max_abs_err": 7.266112327575684,
|
| 298 |
+
"mean_abs_err": 0.5243923664093018,
|
| 299 |
+
"argmax_mismatches": 76,
|
| 300 |
+
"argmax_total": 843
|
| 301 |
+
},
|
| 302 |
+
"bpe_logits_dense": {
|
| 303 |
+
"max_abs_err": 1.8408704996109009,
|
| 304 |
+
"mean_abs_err": 0.20938457548618317,
|
| 305 |
+
"argmax_mismatches": 0,
|
| 306 |
+
"argmax_total": 211
|
| 307 |
+
},
|
| 308 |
+
"audio_embeds": {
|
| 309 |
+
"max_abs_err": 0.7828707695007324,
|
| 310 |
+
"mean_abs_err": 0.06720388680696487
|
| 311 |
+
},
|
| 312 |
+
"attention_mask_sum": 843
|
| 313 |
+
},
|
| 314 |
+
"editor": {
|
| 315 |
+
"argmax_only": true,
|
| 316 |
+
"ok": true,
|
| 317 |
+
"max_abs_err": 94.53038024902344,
|
| 318 |
+
"mean_abs_err": 8.335189819335938,
|
| 319 |
+
"argmax_mismatches": 15,
|
| 320 |
+
"argmax_total": 257,
|
| 321 |
+
"transcript_match": true,
|
| 322 |
+
"top1_match": 242,
|
| 323 |
+
"top5_set_match": 24
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
},
|
| 327 |
+
"multi_clip_parity": {
|
| 328 |
+
"rows": [
|
| 329 |
+
{
|
| 330 |
+
"name": "is-it-more-wood",
|
| 331 |
+
"duration_s": 46.9,
|
| 332 |
+
"fp32_byte_exact_vs_pt": true,
|
| 333 |
+
"int8_byte_exact_vs_pt": false,
|
| 334 |
+
"int8_wer_vs_pt": 0.036,
|
| 335 |
+
"int8_vs_fp32_lev": 13,
|
| 336 |
+
"fp32_transcript": "well, hello, sam. guess who? yeah, it's robert clotworthy, the narrator of your favorite television show, the curse of oak island. yes, i'm the is it possible? could it be? and what else do we say on oak island? a couple of words. they're not coming to me. oh, yeah, more wood. but let's not forget it is an island named after a tree. well, here's the question. why am i reaching out to you? is it possible that i'm reaching out to you because it's your birthday? could it be that emma let the cat out of the bag? well, the answer to those questions is yes. and she said, well, she contacted me. she said, robert, you know, sam is an amazing boyfriend. in fact, she used the word great. she said he is a great boyfriend.",
|
| 337 |
+
"int8_transcript": "well, hello, sam, guess who? yeah, it's robert clotworthy, the narrator of your favorite television show, the curse of oak island. yes, i'm the... is it possible? could it be? and what else do we say on oak island? couple of words. they're not coming to me. oh, yeah, more wood. let's not forget it is an island named after a tree. well, here's the question. why am i reaching out to you? is it possible that i'm reaching out to you because it's your birthday? could it be that emma let the cat out of the bag? well, the answer to those questions is yes. and she said... well, she contacted me. she said, robert, you know, sam is an amazing boyfriend. in fact, she used the word great. she said he is a great boyfriend.",
|
| 338 |
+
"pt_transcript": "well, hello, sam. guess who? yeah, it's robert clotworthy, the narrator of your favorite television show, the curse of oak island. yes, i'm the is it possible? could it be? and what else do we say on oak island? a couple of words. they're not coming to me. oh, yeah, more wood. but let's not forget it is an island named after a tree. well, here's the question. why am i reaching out to you? is it possible that i'm reaching out to you because it's your birthday? could it be that emma let the cat out of the bag? well, the answer to those questions is yes. and she said, well, she contacted me. she said, robert, you know, sam is an amazing boyfriend. in fact, she used the word great. she said he is a great boyfriend."
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
"name": "two-speakers-1",
|
| 342 |
+
"duration_s": 93.8,
|
| 343 |
+
"fp32_byte_exact_vs_pt": false,
|
| 344 |
+
"int8_byte_exact_vs_pt": false,
|
| 345 |
+
"int8_wer_vs_pt": 0.0351,
|
| 346 |
+
"int8_vs_fp32_lev": 26,
|
| 347 |
+
"fp32_transcript": "today, it is a true honor to speak with demisavas, who is the ceo of deepmind. demis, welcome to the podcast. thanks for having me. first question, given your neuroscience background, how do you think about intelligence? specifically, do you think it's like one higher level general reasoning circuit, or do you think it's thousands of independent subkills and heuristics? well, it's interesting because intelligence is so broad and what we use it for is so sort of generally applicable. i think that suggests that you know there must be some sort of high level common things, you know common kind of algorithmic themes, i think, around how the brain processes the world around us. so of course, then there are specialized parts of the brain that do specific things, but i think there are probably some underlying principles that underpin all of that. yeah. how do you make sense of the fact that in these llms, though, when you give them a lot of data in any specific domain, they tend to get asymmetrically better in that domain? wouldn't we expect a sort of like general improvement across all the different areas? well, i think you, first of all, i think you do actually sometimes get surprising improvement in other domains when you improve in a specific domain. so, for example, when these large models sort of improve at coding, that can actually improve their general reasoning. so there is some evidence of some transfer, although i think we would like a lot more evidence of that. but also, you know, that's how the human brain learns too, is if we experience and practice a lot of things like chess or, you know, writing.",
|
| 348 |
+
"int8_transcript": "today, it is a true honor to speak with disesisavas, who is the ceo of deepmind. demis, welcome to the podcast. thanks for having me. first question, given your neuroscience background, how do you think about intelligence? specifically, do you think it's like one higher level general reasoning circuit, or do you think it's thousands of independent subkills and heuristics? well, it's interesting because intelligence is so broad and, you, what we use it for is so sort of generally applicable. i think that suggests that, know there must be some sort of high level-level common things know common kind of algorithmic themes, i think, around how the brain processes the world around us. so of course, then there are specialized parts of the brain that do specific things, but i think there are probably some underlying principles that underpin all of that. yeah. how do you make sense of the fact that in these llms though, when you give them a lot of data in any specific domain, they tend to get asymmetrically better in that domain? wouldn't we expect a sort of like general improvement across all the different areas?? well, i think you, first of all, i think you do actually sometimes get surprising improvement in other domains when you improve in a specific domain. so, for example, when these large models sort of improve at coding, that can actually improve their general reasoning. so there is some evidence of some transfer, although i think we would like a lot more evidence of that. but also, you know, that's how the human brain learns too, is if we experience and practice a lot of things like chess or, you know, writing.",
|
| 349 |
+
"pt_transcript": "today, it is a true honor to speak with demisavas, who is the ceo of deepmind. demis, welcome to the podcast. thanks for having me. first question, given your neuroscience background, how do you think about intelligence? specifically, do you think it's like one higher level general reasoning circuit, or do you think it's thousands of independent subkills and heuristics? well, it's interesting because intelligence is so broad and what we use it for is so sort of generally applicable. i think that suggests that you know there must be some sort of high level common things, you know common kind of algorithmic themes, i think, around how the brain processes the world around us. so of course, then there are specialized parts of the brain that do specific things, but i think there are probably some underlying principles that underpin all of that. yeah. how do you make sense of the fact that in these lms, though, when you give them a lot of data in any specific domain, they tend to get asymmetrically better in that domain? wouldn't we expect a sort of like general improvement across all the different areas? well, i think you, first of all, i think you do actually sometimes get surprising improvement in other domains when you improve in a specific domain. so, for example, when these large models sort of improve at coding, that can actually improve their general reasoning. so there is some evidence of some transfer, although i think we would like a lot more evidence of that. but also, you know, that's how the human brain learns too, is if we experience and practice a lot of things like chess or, you know, writing."
|
| 350 |
+
},
|
| 351 |
+
{
|
| 352 |
+
"name": "two-speakers-2",
|
| 353 |
+
"duration_s": 38.8,
|
| 354 |
+
"fp32_byte_exact_vs_pt": true,
|
| 355 |
+
"int8_byte_exact_vs_pt": false,
|
| 356 |
+
"int8_wer_vs_pt": 0.0202,
|
| 357 |
+
"int8_vs_fp32_lev": 10,
|
| 358 |
+
"fp32_transcript": "for the first time ever, we may have things more intelligent than us. you believe they can understand? yes. you believe they are intelligent? yes. you believe these systems have experiences of their own and can make decisions based on those experiences? in the same sense as people do, yes. are they conscious? i think they probably don't have much self--awareness at present. so in that sense, i don't think they're conscious. will they have self-awareness? yes.. oh, yes, i think they will in time. and so human beings will be the second most intelligent beings on the planet.",
|
| 359 |
+
"int8_transcript": "for the first time ever, we may have things more intelligent than us. you believe they can understand? yes. you believe they are intelligent? yes. you believe these systems have experiences of their own and can make decisions based on those experiences? in the same sense as people do, yes. are they conscious? i think they probably don't have much self--awareness at present. so in that sense, i don't think they're conscious. will they have self-aware?. i oh, yes, i think they will in time. and so human beings will be the second most intelligent beings on the planet.",
|
| 360 |
+
"pt_transcript": "for the first time ever, we may have things more intelligent than us. you believe they can understand? yes. you believe they are intelligent? yes. you believe these systems have experiences of their own and can make decisions based on those experiences? in the same sense as people do, yes. are they conscious? i think they probably don't have much self--awareness at present. so in that sense, i don't think they're conscious. will they have self-awareness? yes.. oh, yes, i think they will in time. and so human beings will be the second most intelligent beings on the planet."
|
| 361 |
+
}
|
| 362 |
+
]
|
| 363 |
+
},
|
| 364 |
+
"toolchain": {
|
| 365 |
+
"transformers": "5.8.0",
|
| 366 |
+
"torch": "2.11.0",
|
| 367 |
+
"onnx": "1.21.0",
|
| 368 |
+
"onnxruntime": "1.25.1",
|
| 369 |
+
"exporter": "torch.onnx.export TorchScript path (dynamo=False)"
|
| 370 |
+
},
|
| 371 |
+
"ort_compatibility": "ort 2.0-rc.x (Rust crate); validated against onnxruntime 1.17 - 1.25",
|
| 372 |
+
"audio_token_id": 100352
|
| 373 |
+
}
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoFeatureExtractor": "feature_extraction_nle.NLEFeatureExtractor"
|
| 4 |
+
},
|
| 5 |
+
"feature_extractor_type": "NLEFeatureExtractor",
|
| 6 |
+
"hop_length": 160,
|
| 7 |
+
"n_fft": 512,
|
| 8 |
+
"n_mels": 80,
|
| 9 |
+
"sampling_rate": 16000,
|
| 10 |
+
"win_length": 400
|
| 11 |
+
}
|
quantise.py
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Sam McLeod
|
| 2 |
+
#
|
| 3 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 4 |
+
# you may not use this file except in compliance with the License.
|
| 5 |
+
# You may obtain a copy of the License at
|
| 6 |
+
#
|
| 7 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 8 |
+
#
|
| 9 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 10 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 11 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 12 |
+
# See the License for the specific language governing permissions and
|
| 13 |
+
# limitations under the License.
|
| 14 |
+
"""Dynamic INT8 (weights-only) quantiser for the Granite Speech 4.1 ONNX
|
| 15 |
+
exports.
|
| 16 |
+
|
| 17 |
+
Wraps `onnxruntime.quantization.quantize_dynamic` with the conventions used
|
| 18 |
+
by the Granite Speech ONNX bundles:
|
| 19 |
+
|
| 20 |
+
- Single external-data sidecar per graph (mirrors the FP32 export layout).
|
| 21 |
+
- Pure `ai.onnx` opset 20 / IR 10. The default operator set is restricted
|
| 22 |
+
to `MatMul` so the dynamic quantiser emits `MatMulInteger` (standard
|
| 23 |
+
`ai.onnx`) rather than the `com.microsoft.Attention` /
|
| 24 |
+
`com.microsoft.EmbedLayerNormalization` quantised variants. Override at
|
| 25 |
+
your own risk - those domain ops are forbidden by the parakeet-rs
|
| 26 |
+
consumer contract.
|
| 27 |
+
- `per_channel=True` and `weight_type=QInt8` by default (better accuracy
|
| 28 |
+
on the LLM weight tensors with no measurable speed cost on
|
| 29 |
+
arm64 / x86 CPU EP).
|
| 30 |
+
|
| 31 |
+
The script is self-contained (no project-internal imports) so it ships
|
| 32 |
+
inside each Hugging Face bundle alongside the export script.
|
| 33 |
+
|
| 34 |
+
Usage:
|
| 35 |
+
python quantise.py --input PATH --output PATH \\
|
| 36 |
+
[--per-channel | --no-per-channel] \\
|
| 37 |
+
[--reduce-range] \\
|
| 38 |
+
[--weight-type qint8|quint8] \\
|
| 39 |
+
[--op-types MatMul,Gemm] \\
|
| 40 |
+
[--exclude-pattern REGEX] \\
|
| 41 |
+
[--exclude-nodes NODE1,NODE2]
|
| 42 |
+
|
| 43 |
+
Examples:
|
| 44 |
+
# Quantise the NAR editor with defaults.
|
| 45 |
+
python quantise.py \\
|
| 46 |
+
--input exports/granite-speech-4.1-2b-nar/editor.onnx \\
|
| 47 |
+
--output exports/granite-speech-4.1-2b-nar/editor_int8.onnx
|
| 48 |
+
|
| 49 |
+
# Skip the lm_head MatMul if it hurts parity.
|
| 50 |
+
python quantise.py \\
|
| 51 |
+
--input exports/granite-speech-4.1-2b-nar/editor.onnx \\
|
| 52 |
+
--output exports/granite-speech-4.1-2b-nar/editor_int8.onnx \\
|
| 53 |
+
--exclude-nodes /lm_head/MatMul
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
from __future__ import annotations
|
| 57 |
+
|
| 58 |
+
import argparse
|
| 59 |
+
import re
|
| 60 |
+
import sys
|
| 61 |
+
import tempfile
|
| 62 |
+
import time
|
| 63 |
+
from pathlib import Path
|
| 64 |
+
|
| 65 |
+
import onnx
|
| 66 |
+
from onnxruntime.quantization import QuantType, quantize_dynamic
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
WEIGHT_TYPE_MAP = {
|
| 70 |
+
"qint8": QuantType.QInt8,
|
| 71 |
+
"quint8": QuantType.QUInt8,
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
| 76 |
+
p = argparse.ArgumentParser(
|
| 77 |
+
description="Dynamic INT8 (weights-only) ONNX quantiser for Granite Speech 4.1 graphs.",
|
| 78 |
+
)
|
| 79 |
+
p.add_argument(
|
| 80 |
+
"--input",
|
| 81 |
+
required=True,
|
| 82 |
+
type=Path,
|
| 83 |
+
help="Path to the FP32 .onnx graph (external sidecar must sit alongside it).",
|
| 84 |
+
)
|
| 85 |
+
p.add_argument(
|
| 86 |
+
"--output",
|
| 87 |
+
required=True,
|
| 88 |
+
type=Path,
|
| 89 |
+
help="Destination .onnx path. A single sidecar named <output>_data is written next to it.",
|
| 90 |
+
)
|
| 91 |
+
p.add_argument(
|
| 92 |
+
"--per-channel",
|
| 93 |
+
dest="per_channel",
|
| 94 |
+
action="store_true",
|
| 95 |
+
default=True,
|
| 96 |
+
help="Quantise weights per output channel (default: on).",
|
| 97 |
+
)
|
| 98 |
+
p.add_argument(
|
| 99 |
+
"--no-per-channel",
|
| 100 |
+
dest="per_channel",
|
| 101 |
+
action="store_false",
|
| 102 |
+
help="Disable per-channel quantisation.",
|
| 103 |
+
)
|
| 104 |
+
p.add_argument(
|
| 105 |
+
"--reduce-range",
|
| 106 |
+
action="store_true",
|
| 107 |
+
default=False,
|
| 108 |
+
help="Quantise to 7 bits instead of 8. Improves accuracy on non-VNNI hardware "
|
| 109 |
+
"but reduces the quantisation gain. Off by default.",
|
| 110 |
+
)
|
| 111 |
+
p.add_argument(
|
| 112 |
+
"--weight-type",
|
| 113 |
+
choices=sorted(WEIGHT_TYPE_MAP.keys()),
|
| 114 |
+
default="qint8",
|
| 115 |
+
help="Weight quantisation dtype (default: qint8).",
|
| 116 |
+
)
|
| 117 |
+
p.add_argument(
|
| 118 |
+
"--op-types",
|
| 119 |
+
default="MatMul",
|
| 120 |
+
help=(
|
| 121 |
+
"Comma-separated op types to quantise. Default: 'MatMul' (emits "
|
| 122 |
+
"MatMulInteger only, all ai.onnx). Adding 'Conv' enables ConvInteger "
|
| 123 |
+
"for the Conformer encoder's depthwise convolutions; this shrinks the "
|
| 124 |
+
"encoder INT8 sidecar by ~40 percent but on this model family feeds "
|
| 125 |
+
"enough weight-quant noise into the LLM head that it flips "
|
| 126 |
+
"capitalisation and drops sentence-final punctuation on short clips - "
|
| 127 |
+
"see task 17 in dev-plan.md. MatMul-only is the validated default. "
|
| 128 |
+
"Adding 'Attention' or 'EmbedLayerNormalization' would introduce "
|
| 129 |
+
"com.microsoft domain ops, which are forbidden by the parakeet-rs "
|
| 130 |
+
"contract."
|
| 131 |
+
),
|
| 132 |
+
)
|
| 133 |
+
p.add_argument(
|
| 134 |
+
"--exclude-pattern",
|
| 135 |
+
default=None,
|
| 136 |
+
help="Regex applied to ONNX node names. Matching nodes are excluded from "
|
| 137 |
+
"quantisation. Useful for skipping e.g. lm_head if its quantisation "
|
| 138 |
+
"breaks parity.",
|
| 139 |
+
)
|
| 140 |
+
p.add_argument(
|
| 141 |
+
"--exclude-nodes",
|
| 142 |
+
default="",
|
| 143 |
+
help="Explicit comma-separated list of node names to exclude from quantisation.",
|
| 144 |
+
)
|
| 145 |
+
p.add_argument(
|
| 146 |
+
"--ir-version",
|
| 147 |
+
type=int,
|
| 148 |
+
default=10,
|
| 149 |
+
help="ONNX IR version to write (default: 10, matches the FP32 exports).",
|
| 150 |
+
)
|
| 151 |
+
return p.parse_args(argv)
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def collect_excluded_nodes(
|
| 155 |
+
input_path: Path,
|
| 156 |
+
exclude_pattern: str | None,
|
| 157 |
+
exclude_nodes: list[str],
|
| 158 |
+
) -> list[str]:
|
| 159 |
+
"""Resolve --exclude-pattern against the FP32 graph's node names and merge
|
| 160 |
+
with the explicit --exclude-nodes list. Loaded without external data so we
|
| 161 |
+
only touch the small graph proto.
|
| 162 |
+
"""
|
| 163 |
+
excluded = set(n for n in exclude_nodes if n)
|
| 164 |
+
if exclude_pattern:
|
| 165 |
+
rx = re.compile(exclude_pattern)
|
| 166 |
+
proto = onnx.load(str(input_path), load_external_data=False)
|
| 167 |
+
for node in proto.graph.node:
|
| 168 |
+
if node.name and rx.search(node.name):
|
| 169 |
+
excluded.add(node.name)
|
| 170 |
+
return sorted(excluded)
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def assert_pure_ai_onnx(model_path: Path) -> list[str]:
|
| 174 |
+
"""Reload the produced graph and verify only `ai.onnx` nodes are present.
|
| 175 |
+
Returns the sorted list of domains for reporting.
|
| 176 |
+
"""
|
| 177 |
+
proto = onnx.load(str(model_path), load_external_data=False)
|
| 178 |
+
domains = sorted({(n.domain or "ai.onnx") for n in proto.graph.node})
|
| 179 |
+
forbidden = [d for d in domains if d not in ("ai.onnx", "")]
|
| 180 |
+
if forbidden:
|
| 181 |
+
raise RuntimeError(
|
| 182 |
+
f"Quantised graph contains forbidden op domains {forbidden}. "
|
| 183 |
+
"Re-run with a narrower --op-types list."
|
| 184 |
+
)
|
| 185 |
+
return domains
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def consolidate_single_sidecar(
|
| 189 |
+
quantised_in: Path,
|
| 190 |
+
final_out: Path,
|
| 191 |
+
ir_version: int,
|
| 192 |
+
) -> None:
|
| 193 |
+
"""The dynamic quantiser may scatter weights across multiple external-data
|
| 194 |
+
files. Reload + resave through a tempdir to land on the single-sidecar
|
| 195 |
+
layout that matches the FP32 exports.
|
| 196 |
+
"""
|
| 197 |
+
print(" consolidating to single .onnx_data sidecar")
|
| 198 |
+
proto = onnx.load(str(quantised_in), load_external_data=True)
|
| 199 |
+
if proto.ir_version < ir_version:
|
| 200 |
+
proto.ir_version = ir_version
|
| 201 |
+
|
| 202 |
+
for tensor in proto.graph.initializer:
|
| 203 |
+
tensor.ClearField("data_location")
|
| 204 |
+
tensor.ClearField("external_data")
|
| 205 |
+
|
| 206 |
+
sidecar_name = final_out.name + "_data"
|
| 207 |
+
if (final_out.parent / sidecar_name).exists():
|
| 208 |
+
(final_out.parent / sidecar_name).unlink()
|
| 209 |
+
if final_out.exists():
|
| 210 |
+
final_out.unlink()
|
| 211 |
+
final_out.parent.mkdir(parents=True, exist_ok=True)
|
| 212 |
+
|
| 213 |
+
onnx.save_model(
|
| 214 |
+
proto,
|
| 215 |
+
str(final_out),
|
| 216 |
+
save_as_external_data=True,
|
| 217 |
+
all_tensors_to_one_file=True,
|
| 218 |
+
location=sidecar_name,
|
| 219 |
+
size_threshold=1024,
|
| 220 |
+
convert_attribute=False,
|
| 221 |
+
)
|
| 222 |
+
onnx.checker.check_model(str(final_out), full_check=False)
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def quantise_graph(args: argparse.Namespace) -> None:
|
| 226 |
+
input_path: Path = args.input.resolve()
|
| 227 |
+
output_path: Path = args.output.resolve()
|
| 228 |
+
if not input_path.exists():
|
| 229 |
+
raise SystemExit(f"input not found: {input_path}")
|
| 230 |
+
op_types = [s.strip() for s in args.op_types.split(",") if s.strip()]
|
| 231 |
+
explicit_excludes = [s.strip() for s in args.exclude_nodes.split(",") if s.strip()]
|
| 232 |
+
|
| 233 |
+
excluded = collect_excluded_nodes(input_path, args.exclude_pattern, explicit_excludes)
|
| 234 |
+
weight_type = WEIGHT_TYPE_MAP[args.weight_type]
|
| 235 |
+
|
| 236 |
+
print(f"input: {input_path}")
|
| 237 |
+
print(f"output: {output_path}")
|
| 238 |
+
print(f"op_types: {op_types}")
|
| 239 |
+
print(f"per_channel: {args.per_channel}")
|
| 240 |
+
print(f"reduce_range: {args.reduce_range}")
|
| 241 |
+
print(f"weight_type: {args.weight_type}")
|
| 242 |
+
if excluded:
|
| 243 |
+
print(f"excluded nodes ({len(excluded)}): {excluded}")
|
| 244 |
+
else:
|
| 245 |
+
print("excluded nodes: (none)")
|
| 246 |
+
|
| 247 |
+
fp32_size = input_path.stat().st_size
|
| 248 |
+
sidecar = input_path.with_name(input_path.name + "_data")
|
| 249 |
+
fp32_data_size = sidecar.stat().st_size if sidecar.exists() else 0
|
| 250 |
+
print(
|
| 251 |
+
f" fp32 graph={fp32_size / 1e6:.2f} MB "
|
| 252 |
+
f"sidecar={fp32_data_size / 1e9:.2f} GB"
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
with tempfile.TemporaryDirectory(prefix="quantise_int8_") as scratch_dir:
|
| 256 |
+
scratch_path = Path(scratch_dir) / output_path.name
|
| 257 |
+
t0 = time.time()
|
| 258 |
+
quantize_dynamic(
|
| 259 |
+
model_input=input_path,
|
| 260 |
+
model_output=scratch_path,
|
| 261 |
+
op_types_to_quantize=op_types,
|
| 262 |
+
per_channel=args.per_channel,
|
| 263 |
+
reduce_range=args.reduce_range,
|
| 264 |
+
weight_type=weight_type,
|
| 265 |
+
nodes_to_exclude=excluded or None,
|
| 266 |
+
use_external_data_format=True,
|
| 267 |
+
)
|
| 268 |
+
print(f" quantize_dynamic done in {time.time() - t0:.1f}s")
|
| 269 |
+
|
| 270 |
+
# Stage 2: consolidate any scattered external-data files into a single
|
| 271 |
+
# sidecar at the final destination.
|
| 272 |
+
consolidate_single_sidecar(scratch_path, output_path, args.ir_version)
|
| 273 |
+
|
| 274 |
+
# Verify pure ai.onnx after the move.
|
| 275 |
+
domains = assert_pure_ai_onnx(output_path)
|
| 276 |
+
int8_size = output_path.stat().st_size
|
| 277 |
+
int8_data = output_path.with_name(output_path.name + "_data")
|
| 278 |
+
int8_data_size = int8_data.stat().st_size if int8_data.exists() else 0
|
| 279 |
+
print(
|
| 280 |
+
f" saved {output_path} (+ {int8_data.name}) "
|
| 281 |
+
f"graph={int8_size / 1e6:.2f} MB sidecar={int8_data_size / 1e9:.2f} GB"
|
| 282 |
+
)
|
| 283 |
+
print(f" node-domains={domains}")
|
| 284 |
+
if fp32_data_size > 0:
|
| 285 |
+
ratio = int8_data_size / fp32_data_size
|
| 286 |
+
print(f" sidecar size ratio (int8 / fp32) = {ratio:.3f}")
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def main(argv: list[str] | None = None) -> None:
|
| 290 |
+
args = parse_args(argv)
|
| 291 |
+
try:
|
| 292 |
+
quantise_graph(args)
|
| 293 |
+
except RuntimeError as exc:
|
| 294 |
+
print(f"FAIL: {exc}", file=sys.stderr)
|
| 295 |
+
raise SystemExit(2) from exc
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
if __name__ == "__main__":
|
| 299 |
+
main()
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token": {
|
| 3 |
+
"content": "<|end_of_text|>",
|
| 4 |
+
"lstrip": false,
|
| 5 |
+
"normalized": false,
|
| 6 |
+
"rstrip": false,
|
| 7 |
+
"single_word": false
|
| 8 |
+
},
|
| 9 |
+
"eos_token": {
|
| 10 |
+
"content": "<|end_of_text|>",
|
| 11 |
+
"lstrip": false,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "<|pad|>",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
},
|
| 23 |
+
"unk_token": {
|
| 24 |
+
"content": "<|unk|>",
|
| 25 |
+
"lstrip": false,
|
| 26 |
+
"normalized": false,
|
| 27 |
+
"rstrip": false,
|
| 28 |
+
"single_word": false
|
| 29 |
+
}
|
| 30 |
+
}
|
tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,783 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"100256": {
|
| 6 |
+
"content": "<|pad|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"100257": {
|
| 14 |
+
"content": "<|end_of_text|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"100258": {
|
| 22 |
+
"content": "<|fim_prefix|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": false
|
| 28 |
+
},
|
| 29 |
+
"100259": {
|
| 30 |
+
"content": "<|fim_middle|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": false
|
| 36 |
+
},
|
| 37 |
+
"100260": {
|
| 38 |
+
"content": "<|fim_suffix|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": false
|
| 44 |
+
},
|
| 45 |
+
"100261": {
|
| 46 |
+
"content": "<|fim_pad|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": false
|
| 52 |
+
},
|
| 53 |
+
"100262": {
|
| 54 |
+
"content": "<|filename|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": false
|
| 60 |
+
},
|
| 61 |
+
"100263": {
|
| 62 |
+
"content": "<|reponame|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": false
|
| 68 |
+
},
|
| 69 |
+
"100264": {
|
| 70 |
+
"content": "<|start_of_role|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"100265": {
|
| 78 |
+
"content": "<|end_of_role|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"100266": {
|
| 86 |
+
"content": "<|unused_1|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"100267": {
|
| 94 |
+
"content": "<|start_of_plugin|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"100268": {
|
| 102 |
+
"content": "<|end_of_plugin|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"100269": {
|
| 110 |
+
"content": "<|unk|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"100270": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"100271": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"100272": {
|
| 134 |
+
"content": "<tool_response>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"100273": {
|
| 142 |
+
"content": "</tool_response>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"100274": {
|
| 150 |
+
"content": "<think>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"100275": {
|
| 158 |
+
"content": "</think>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"100276": {
|
| 166 |
+
"content": "<think_on>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": true
|
| 172 |
+
},
|
| 173 |
+
"100277": {
|
| 174 |
+
"content": "<think_off>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": true
|
| 180 |
+
},
|
| 181 |
+
"100278": {
|
| 182 |
+
"content": "<schema>",
|
| 183 |
+
"lstrip": false,
|
| 184 |
+
"normalized": false,
|
| 185 |
+
"rstrip": false,
|
| 186 |
+
"single_word": false,
|
| 187 |
+
"special": true
|
| 188 |
+
},
|
| 189 |
+
"100279": {
|
| 190 |
+
"content": "</schema>",
|
| 191 |
+
"lstrip": false,
|
| 192 |
+
"normalized": false,
|
| 193 |
+
"rstrip": false,
|
| 194 |
+
"single_word": false,
|
| 195 |
+
"special": true
|
| 196 |
+
},
|
| 197 |
+
"100280": {
|
| 198 |
+
"content": "<tools>",
|
| 199 |
+
"lstrip": false,
|
| 200 |
+
"normalized": false,
|
| 201 |
+
"rstrip": false,
|
| 202 |
+
"single_word": false,
|
| 203 |
+
"special": true
|
| 204 |
+
},
|
| 205 |
+
"100281": {
|
| 206 |
+
"content": "</tools>",
|
| 207 |
+
"lstrip": false,
|
| 208 |
+
"normalized": false,
|
| 209 |
+
"rstrip": false,
|
| 210 |
+
"single_word": false,
|
| 211 |
+
"special": true
|
| 212 |
+
},
|
| 213 |
+
"100282": {
|
| 214 |
+
"content": "<documents>",
|
| 215 |
+
"lstrip": false,
|
| 216 |
+
"normalized": false,
|
| 217 |
+
"rstrip": false,
|
| 218 |
+
"single_word": false,
|
| 219 |
+
"special": true
|
| 220 |
+
},
|
| 221 |
+
"100283": {
|
| 222 |
+
"content": "</documents>",
|
| 223 |
+
"lstrip": false,
|
| 224 |
+
"normalized": false,
|
| 225 |
+
"rstrip": false,
|
| 226 |
+
"single_word": false,
|
| 227 |
+
"special": true
|
| 228 |
+
},
|
| 229 |
+
"100284": {
|
| 230 |
+
"content": "<|unused_15|>",
|
| 231 |
+
"lstrip": false,
|
| 232 |
+
"normalized": false,
|
| 233 |
+
"rstrip": false,
|
| 234 |
+
"single_word": false,
|
| 235 |
+
"special": true
|
| 236 |
+
},
|
| 237 |
+
"100285": {
|
| 238 |
+
"content": "<|unused_16|>",
|
| 239 |
+
"lstrip": false,
|
| 240 |
+
"normalized": false,
|
| 241 |
+
"rstrip": false,
|
| 242 |
+
"single_word": false,
|
| 243 |
+
"special": true
|
| 244 |
+
},
|
| 245 |
+
"100286": {
|
| 246 |
+
"content": "<|unused_17|>",
|
| 247 |
+
"lstrip": false,
|
| 248 |
+
"normalized": false,
|
| 249 |
+
"rstrip": false,
|
| 250 |
+
"single_word": false,
|
| 251 |
+
"special": true
|
| 252 |
+
},
|
| 253 |
+
"100287": {
|
| 254 |
+
"content": "<|unused_18|>",
|
| 255 |
+
"lstrip": false,
|
| 256 |
+
"normalized": false,
|
| 257 |
+
"rstrip": false,
|
| 258 |
+
"single_word": false,
|
| 259 |
+
"special": true
|
| 260 |
+
},
|
| 261 |
+
"100288": {
|
| 262 |
+
"content": "<|unused_19|>",
|
| 263 |
+
"lstrip": false,
|
| 264 |
+
"normalized": false,
|
| 265 |
+
"rstrip": false,
|
| 266 |
+
"single_word": false,
|
| 267 |
+
"special": true
|
| 268 |
+
},
|
| 269 |
+
"100289": {
|
| 270 |
+
"content": "<|unused_20|>",
|
| 271 |
+
"lstrip": false,
|
| 272 |
+
"normalized": false,
|
| 273 |
+
"rstrip": false,
|
| 274 |
+
"single_word": false,
|
| 275 |
+
"special": true
|
| 276 |
+
},
|
| 277 |
+
"100290": {
|
| 278 |
+
"content": "<|unused_21|>",
|
| 279 |
+
"lstrip": false,
|
| 280 |
+
"normalized": false,
|
| 281 |
+
"rstrip": false,
|
| 282 |
+
"single_word": false,
|
| 283 |
+
"special": true
|
| 284 |
+
},
|
| 285 |
+
"100291": {
|
| 286 |
+
"content": "<|unused_22|>",
|
| 287 |
+
"lstrip": false,
|
| 288 |
+
"normalized": false,
|
| 289 |
+
"rstrip": false,
|
| 290 |
+
"single_word": false,
|
| 291 |
+
"special": true
|
| 292 |
+
},
|
| 293 |
+
"100292": {
|
| 294 |
+
"content": "<|unused_23|>",
|
| 295 |
+
"lstrip": false,
|
| 296 |
+
"normalized": false,
|
| 297 |
+
"rstrip": false,
|
| 298 |
+
"single_word": false,
|
| 299 |
+
"special": true
|
| 300 |
+
},
|
| 301 |
+
"100293": {
|
| 302 |
+
"content": "<|unused_24|>",
|
| 303 |
+
"lstrip": false,
|
| 304 |
+
"normalized": false,
|
| 305 |
+
"rstrip": false,
|
| 306 |
+
"single_word": false,
|
| 307 |
+
"special": true
|
| 308 |
+
},
|
| 309 |
+
"100294": {
|
| 310 |
+
"content": "<|unused_25|>",
|
| 311 |
+
"lstrip": false,
|
| 312 |
+
"normalized": false,
|
| 313 |
+
"rstrip": false,
|
| 314 |
+
"single_word": false,
|
| 315 |
+
"special": true
|
| 316 |
+
},
|
| 317 |
+
"100295": {
|
| 318 |
+
"content": "<|unused_26|>",
|
| 319 |
+
"lstrip": false,
|
| 320 |
+
"normalized": false,
|
| 321 |
+
"rstrip": false,
|
| 322 |
+
"single_word": false,
|
| 323 |
+
"special": true
|
| 324 |
+
},
|
| 325 |
+
"100296": {
|
| 326 |
+
"content": "<|unused_27|>",
|
| 327 |
+
"lstrip": false,
|
| 328 |
+
"normalized": false,
|
| 329 |
+
"rstrip": false,
|
| 330 |
+
"single_word": false,
|
| 331 |
+
"special": true
|
| 332 |
+
},
|
| 333 |
+
"100297": {
|
| 334 |
+
"content": "<|unused_28|>",
|
| 335 |
+
"lstrip": false,
|
| 336 |
+
"normalized": false,
|
| 337 |
+
"rstrip": false,
|
| 338 |
+
"single_word": false,
|
| 339 |
+
"special": true
|
| 340 |
+
},
|
| 341 |
+
"100298": {
|
| 342 |
+
"content": "<|unused_29|>",
|
| 343 |
+
"lstrip": false,
|
| 344 |
+
"normalized": false,
|
| 345 |
+
"rstrip": false,
|
| 346 |
+
"single_word": false,
|
| 347 |
+
"special": true
|
| 348 |
+
},
|
| 349 |
+
"100299": {
|
| 350 |
+
"content": "<|unused_30|>",
|
| 351 |
+
"lstrip": false,
|
| 352 |
+
"normalized": false,
|
| 353 |
+
"rstrip": false,
|
| 354 |
+
"single_word": false,
|
| 355 |
+
"special": true
|
| 356 |
+
},
|
| 357 |
+
"100300": {
|
| 358 |
+
"content": "<|unused_31|>",
|
| 359 |
+
"lstrip": false,
|
| 360 |
+
"normalized": false,
|
| 361 |
+
"rstrip": false,
|
| 362 |
+
"single_word": false,
|
| 363 |
+
"special": true
|
| 364 |
+
},
|
| 365 |
+
"100301": {
|
| 366 |
+
"content": "<|unused_32|>",
|
| 367 |
+
"lstrip": false,
|
| 368 |
+
"normalized": false,
|
| 369 |
+
"rstrip": false,
|
| 370 |
+
"single_word": false,
|
| 371 |
+
"special": true
|
| 372 |
+
},
|
| 373 |
+
"100302": {
|
| 374 |
+
"content": "<|unused_33|>",
|
| 375 |
+
"lstrip": false,
|
| 376 |
+
"normalized": false,
|
| 377 |
+
"rstrip": false,
|
| 378 |
+
"single_word": false,
|
| 379 |
+
"special": true
|
| 380 |
+
},
|
| 381 |
+
"100303": {
|
| 382 |
+
"content": "<|unused_34|>",
|
| 383 |
+
"lstrip": false,
|
| 384 |
+
"normalized": false,
|
| 385 |
+
"rstrip": false,
|
| 386 |
+
"single_word": false,
|
| 387 |
+
"special": true
|
| 388 |
+
},
|
| 389 |
+
"100304": {
|
| 390 |
+
"content": "<|unused_35|>",
|
| 391 |
+
"lstrip": false,
|
| 392 |
+
"normalized": false,
|
| 393 |
+
"rstrip": false,
|
| 394 |
+
"single_word": false,
|
| 395 |
+
"special": true
|
| 396 |
+
},
|
| 397 |
+
"100305": {
|
| 398 |
+
"content": "<|unused_36|>",
|
| 399 |
+
"lstrip": false,
|
| 400 |
+
"normalized": false,
|
| 401 |
+
"rstrip": false,
|
| 402 |
+
"single_word": false,
|
| 403 |
+
"special": true
|
| 404 |
+
},
|
| 405 |
+
"100306": {
|
| 406 |
+
"content": "<|unused_37|>",
|
| 407 |
+
"lstrip": false,
|
| 408 |
+
"normalized": false,
|
| 409 |
+
"rstrip": false,
|
| 410 |
+
"single_word": false,
|
| 411 |
+
"special": true
|
| 412 |
+
},
|
| 413 |
+
"100307": {
|
| 414 |
+
"content": "<|unused_38|>",
|
| 415 |
+
"lstrip": false,
|
| 416 |
+
"normalized": false,
|
| 417 |
+
"rstrip": false,
|
| 418 |
+
"single_word": false,
|
| 419 |
+
"special": true
|
| 420 |
+
},
|
| 421 |
+
"100308": {
|
| 422 |
+
"content": "<|unused_39|>",
|
| 423 |
+
"lstrip": false,
|
| 424 |
+
"normalized": false,
|
| 425 |
+
"rstrip": false,
|
| 426 |
+
"single_word": false,
|
| 427 |
+
"special": true
|
| 428 |
+
},
|
| 429 |
+
"100309": {
|
| 430 |
+
"content": "<|unused_40|>",
|
| 431 |
+
"lstrip": false,
|
| 432 |
+
"normalized": false,
|
| 433 |
+
"rstrip": false,
|
| 434 |
+
"single_word": false,
|
| 435 |
+
"special": true
|
| 436 |
+
},
|
| 437 |
+
"100310": {
|
| 438 |
+
"content": "<|unused_41|>",
|
| 439 |
+
"lstrip": false,
|
| 440 |
+
"normalized": false,
|
| 441 |
+
"rstrip": false,
|
| 442 |
+
"single_word": false,
|
| 443 |
+
"special": true
|
| 444 |
+
},
|
| 445 |
+
"100311": {
|
| 446 |
+
"content": "<|unused_42|>",
|
| 447 |
+
"lstrip": false,
|
| 448 |
+
"normalized": false,
|
| 449 |
+
"rstrip": false,
|
| 450 |
+
"single_word": false,
|
| 451 |
+
"special": true
|
| 452 |
+
},
|
| 453 |
+
"100312": {
|
| 454 |
+
"content": "<|unused_43|>",
|
| 455 |
+
"lstrip": false,
|
| 456 |
+
"normalized": false,
|
| 457 |
+
"rstrip": false,
|
| 458 |
+
"single_word": false,
|
| 459 |
+
"special": true
|
| 460 |
+
},
|
| 461 |
+
"100313": {
|
| 462 |
+
"content": "<|unused_44|>",
|
| 463 |
+
"lstrip": false,
|
| 464 |
+
"normalized": false,
|
| 465 |
+
"rstrip": false,
|
| 466 |
+
"single_word": false,
|
| 467 |
+
"special": true
|
| 468 |
+
},
|
| 469 |
+
"100314": {
|
| 470 |
+
"content": "<|unused_45|>",
|
| 471 |
+
"lstrip": false,
|
| 472 |
+
"normalized": false,
|
| 473 |
+
"rstrip": false,
|
| 474 |
+
"single_word": false,
|
| 475 |
+
"special": true
|
| 476 |
+
},
|
| 477 |
+
"100315": {
|
| 478 |
+
"content": "<|unused_46|>",
|
| 479 |
+
"lstrip": false,
|
| 480 |
+
"normalized": false,
|
| 481 |
+
"rstrip": false,
|
| 482 |
+
"single_word": false,
|
| 483 |
+
"special": true
|
| 484 |
+
},
|
| 485 |
+
"100316": {
|
| 486 |
+
"content": "<|unused_47|>",
|
| 487 |
+
"lstrip": false,
|
| 488 |
+
"normalized": false,
|
| 489 |
+
"rstrip": false,
|
| 490 |
+
"single_word": false,
|
| 491 |
+
"special": true
|
| 492 |
+
},
|
| 493 |
+
"100317": {
|
| 494 |
+
"content": "<|unused_48|>",
|
| 495 |
+
"lstrip": false,
|
| 496 |
+
"normalized": false,
|
| 497 |
+
"rstrip": false,
|
| 498 |
+
"single_word": false,
|
| 499 |
+
"special": true
|
| 500 |
+
},
|
| 501 |
+
"100318": {
|
| 502 |
+
"content": "<|unused_49|>",
|
| 503 |
+
"lstrip": false,
|
| 504 |
+
"normalized": false,
|
| 505 |
+
"rstrip": false,
|
| 506 |
+
"single_word": false,
|
| 507 |
+
"special": true
|
| 508 |
+
},
|
| 509 |
+
"100319": {
|
| 510 |
+
"content": "<|unused_50|>",
|
| 511 |
+
"lstrip": false,
|
| 512 |
+
"normalized": false,
|
| 513 |
+
"rstrip": false,
|
| 514 |
+
"single_word": false,
|
| 515 |
+
"special": true
|
| 516 |
+
},
|
| 517 |
+
"100320": {
|
| 518 |
+
"content": "<|unused_51|>",
|
| 519 |
+
"lstrip": false,
|
| 520 |
+
"normalized": false,
|
| 521 |
+
"rstrip": false,
|
| 522 |
+
"single_word": false,
|
| 523 |
+
"special": true
|
| 524 |
+
},
|
| 525 |
+
"100321": {
|
| 526 |
+
"content": "<|unused_52|>",
|
| 527 |
+
"lstrip": false,
|
| 528 |
+
"normalized": false,
|
| 529 |
+
"rstrip": false,
|
| 530 |
+
"single_word": false,
|
| 531 |
+
"special": true
|
| 532 |
+
},
|
| 533 |
+
"100322": {
|
| 534 |
+
"content": "<|unused_53|>",
|
| 535 |
+
"lstrip": false,
|
| 536 |
+
"normalized": false,
|
| 537 |
+
"rstrip": false,
|
| 538 |
+
"single_word": false,
|
| 539 |
+
"special": true
|
| 540 |
+
},
|
| 541 |
+
"100323": {
|
| 542 |
+
"content": "<|unused_54|>",
|
| 543 |
+
"lstrip": false,
|
| 544 |
+
"normalized": false,
|
| 545 |
+
"rstrip": false,
|
| 546 |
+
"single_word": false,
|
| 547 |
+
"special": true
|
| 548 |
+
},
|
| 549 |
+
"100324": {
|
| 550 |
+
"content": "<|unused_55|>",
|
| 551 |
+
"lstrip": false,
|
| 552 |
+
"normalized": false,
|
| 553 |
+
"rstrip": false,
|
| 554 |
+
"single_word": false,
|
| 555 |
+
"special": true
|
| 556 |
+
},
|
| 557 |
+
"100325": {
|
| 558 |
+
"content": "<|unused_56|>",
|
| 559 |
+
"lstrip": false,
|
| 560 |
+
"normalized": false,
|
| 561 |
+
"rstrip": false,
|
| 562 |
+
"single_word": false,
|
| 563 |
+
"special": true
|
| 564 |
+
},
|
| 565 |
+
"100326": {
|
| 566 |
+
"content": "<|unused_57|>",
|
| 567 |
+
"lstrip": false,
|
| 568 |
+
"normalized": false,
|
| 569 |
+
"rstrip": false,
|
| 570 |
+
"single_word": false,
|
| 571 |
+
"special": true
|
| 572 |
+
},
|
| 573 |
+
"100327": {
|
| 574 |
+
"content": "<|unused_58|>",
|
| 575 |
+
"lstrip": false,
|
| 576 |
+
"normalized": false,
|
| 577 |
+
"rstrip": false,
|
| 578 |
+
"single_word": false,
|
| 579 |
+
"special": true
|
| 580 |
+
},
|
| 581 |
+
"100328": {
|
| 582 |
+
"content": "<|unused_59|>",
|
| 583 |
+
"lstrip": false,
|
| 584 |
+
"normalized": false,
|
| 585 |
+
"rstrip": false,
|
| 586 |
+
"single_word": false,
|
| 587 |
+
"special": true
|
| 588 |
+
},
|
| 589 |
+
"100329": {
|
| 590 |
+
"content": "<|unused_60|>",
|
| 591 |
+
"lstrip": false,
|
| 592 |
+
"normalized": false,
|
| 593 |
+
"rstrip": false,
|
| 594 |
+
"single_word": false,
|
| 595 |
+
"special": true
|
| 596 |
+
},
|
| 597 |
+
"100330": {
|
| 598 |
+
"content": "<|unused_61|>",
|
| 599 |
+
"lstrip": false,
|
| 600 |
+
"normalized": false,
|
| 601 |
+
"rstrip": false,
|
| 602 |
+
"single_word": false,
|
| 603 |
+
"special": true
|
| 604 |
+
},
|
| 605 |
+
"100331": {
|
| 606 |
+
"content": "<|unused_62|>",
|
| 607 |
+
"lstrip": false,
|
| 608 |
+
"normalized": false,
|
| 609 |
+
"rstrip": false,
|
| 610 |
+
"single_word": false,
|
| 611 |
+
"special": true
|
| 612 |
+
},
|
| 613 |
+
"100332": {
|
| 614 |
+
"content": "<|unused_63|>",
|
| 615 |
+
"lstrip": false,
|
| 616 |
+
"normalized": false,
|
| 617 |
+
"rstrip": false,
|
| 618 |
+
"single_word": false,
|
| 619 |
+
"special": true
|
| 620 |
+
},
|
| 621 |
+
"100333": {
|
| 622 |
+
"content": "<|unused_64|>",
|
| 623 |
+
"lstrip": false,
|
| 624 |
+
"normalized": false,
|
| 625 |
+
"rstrip": false,
|
| 626 |
+
"single_word": false,
|
| 627 |
+
"special": true
|
| 628 |
+
},
|
| 629 |
+
"100334": {
|
| 630 |
+
"content": "<|unused_65|>",
|
| 631 |
+
"lstrip": false,
|
| 632 |
+
"normalized": false,
|
| 633 |
+
"rstrip": false,
|
| 634 |
+
"single_word": false,
|
| 635 |
+
"special": true
|
| 636 |
+
},
|
| 637 |
+
"100335": {
|
| 638 |
+
"content": "<|unused_66|>",
|
| 639 |
+
"lstrip": false,
|
| 640 |
+
"normalized": false,
|
| 641 |
+
"rstrip": false,
|
| 642 |
+
"single_word": false,
|
| 643 |
+
"special": true
|
| 644 |
+
},
|
| 645 |
+
"100336": {
|
| 646 |
+
"content": "<|unused_67|>",
|
| 647 |
+
"lstrip": false,
|
| 648 |
+
"normalized": false,
|
| 649 |
+
"rstrip": false,
|
| 650 |
+
"single_word": false,
|
| 651 |
+
"special": true
|
| 652 |
+
},
|
| 653 |
+
"100337": {
|
| 654 |
+
"content": "<|unused_68|>",
|
| 655 |
+
"lstrip": false,
|
| 656 |
+
"normalized": false,
|
| 657 |
+
"rstrip": false,
|
| 658 |
+
"single_word": false,
|
| 659 |
+
"special": true
|
| 660 |
+
},
|
| 661 |
+
"100338": {
|
| 662 |
+
"content": "<|unused_69|>",
|
| 663 |
+
"lstrip": false,
|
| 664 |
+
"normalized": false,
|
| 665 |
+
"rstrip": false,
|
| 666 |
+
"single_word": false,
|
| 667 |
+
"special": true
|
| 668 |
+
},
|
| 669 |
+
"100339": {
|
| 670 |
+
"content": "<|unused_70|>",
|
| 671 |
+
"lstrip": false,
|
| 672 |
+
"normalized": false,
|
| 673 |
+
"rstrip": false,
|
| 674 |
+
"single_word": false,
|
| 675 |
+
"special": true
|
| 676 |
+
},
|
| 677 |
+
"100340": {
|
| 678 |
+
"content": "<|unused_71|>",
|
| 679 |
+
"lstrip": false,
|
| 680 |
+
"normalized": false,
|
| 681 |
+
"rstrip": false,
|
| 682 |
+
"single_word": false,
|
| 683 |
+
"special": true
|
| 684 |
+
},
|
| 685 |
+
"100341": {
|
| 686 |
+
"content": "<|unused_72|>",
|
| 687 |
+
"lstrip": false,
|
| 688 |
+
"normalized": false,
|
| 689 |
+
"rstrip": false,
|
| 690 |
+
"single_word": false,
|
| 691 |
+
"special": true
|
| 692 |
+
},
|
| 693 |
+
"100342": {
|
| 694 |
+
"content": "<|unused_73|>",
|
| 695 |
+
"lstrip": false,
|
| 696 |
+
"normalized": false,
|
| 697 |
+
"rstrip": false,
|
| 698 |
+
"single_word": false,
|
| 699 |
+
"special": true
|
| 700 |
+
},
|
| 701 |
+
"100343": {
|
| 702 |
+
"content": "<|unused_74|>",
|
| 703 |
+
"lstrip": false,
|
| 704 |
+
"normalized": false,
|
| 705 |
+
"rstrip": false,
|
| 706 |
+
"single_word": false,
|
| 707 |
+
"special": true
|
| 708 |
+
},
|
| 709 |
+
"100344": {
|
| 710 |
+
"content": "<|unused_75|>",
|
| 711 |
+
"lstrip": false,
|
| 712 |
+
"normalized": false,
|
| 713 |
+
"rstrip": false,
|
| 714 |
+
"single_word": false,
|
| 715 |
+
"special": true
|
| 716 |
+
},
|
| 717 |
+
"100345": {
|
| 718 |
+
"content": "<|unused_76|>",
|
| 719 |
+
"lstrip": false,
|
| 720 |
+
"normalized": false,
|
| 721 |
+
"rstrip": false,
|
| 722 |
+
"single_word": false,
|
| 723 |
+
"special": true
|
| 724 |
+
},
|
| 725 |
+
"100346": {
|
| 726 |
+
"content": "<|unused_77|>",
|
| 727 |
+
"lstrip": false,
|
| 728 |
+
"normalized": false,
|
| 729 |
+
"rstrip": false,
|
| 730 |
+
"single_word": false,
|
| 731 |
+
"special": true
|
| 732 |
+
},
|
| 733 |
+
"100347": {
|
| 734 |
+
"content": "<|unused_78|>",
|
| 735 |
+
"lstrip": false,
|
| 736 |
+
"normalized": false,
|
| 737 |
+
"rstrip": false,
|
| 738 |
+
"single_word": false,
|
| 739 |
+
"special": true
|
| 740 |
+
},
|
| 741 |
+
"100348": {
|
| 742 |
+
"content": "<|unused_79|>",
|
| 743 |
+
"lstrip": false,
|
| 744 |
+
"normalized": false,
|
| 745 |
+
"rstrip": false,
|
| 746 |
+
"single_word": false,
|
| 747 |
+
"special": true
|
| 748 |
+
},
|
| 749 |
+
"100349": {
|
| 750 |
+
"content": "<|unused_80|>",
|
| 751 |
+
"lstrip": false,
|
| 752 |
+
"normalized": false,
|
| 753 |
+
"rstrip": false,
|
| 754 |
+
"single_word": false,
|
| 755 |
+
"special": true
|
| 756 |
+
},
|
| 757 |
+
"100350": {
|
| 758 |
+
"content": "<|unused_81|>",
|
| 759 |
+
"lstrip": false,
|
| 760 |
+
"normalized": false,
|
| 761 |
+
"rstrip": false,
|
| 762 |
+
"single_word": false,
|
| 763 |
+
"special": true
|
| 764 |
+
},
|
| 765 |
+
"100351": {
|
| 766 |
+
"content": "<|unused_82|>",
|
| 767 |
+
"lstrip": false,
|
| 768 |
+
"normalized": false,
|
| 769 |
+
"rstrip": false,
|
| 770 |
+
"single_word": false,
|
| 771 |
+
"special": true
|
| 772 |
+
}
|
| 773 |
+
},
|
| 774 |
+
"bos_token": "<|end_of_text|>",
|
| 775 |
+
"clean_up_tokenization_spaces": false,
|
| 776 |
+
"eos_token": "<|end_of_text|>",
|
| 777 |
+
"extra_special_tokens": {},
|
| 778 |
+
"model_max_length": 1000000000000000019884624838656,
|
| 779 |
+
"pad_token": "<|pad|>",
|
| 780 |
+
"padding_side": "left",
|
| 781 |
+
"tokenizer_class": "GPT2Tokenizer",
|
| 782 |
+
"unk_token": "<|unk|>"
|
| 783 |
+
}
|