Automatic Speech Recognition
Transformers
Safetensors
PyTorch
arkasr
text-generation
speech
audio
multilingual
hotword
audio8
custom_code
Eval Results
Instructions to use AutoArk-AI/Audio8-ASR-0.1B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use AutoArk-AI/Audio8-ASR-0.1B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("automatic-speech-recognition", model="AutoArk-AI/Audio8-ASR-0.1B", trust_remote_code=True)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("AutoArk-AI/Audio8-ASR-0.1B", trust_remote_code=True, dtype="auto") - Notebooks
- Google Colab
- Kaggle
Commit ·
25ae981
0
Parent(s):
Public release
Browse files- .gitattributes +2 -0
- .gitignore +10 -0
- LICENSE +202 -0
- README.md +183 -0
- added_tokens.json +11 -0
- chat_template.jinja +6 -0
- config.json +286 -0
- configuration_arkasr.py +77 -0
- examples/requirements.txt +6 -0
- examples/transcribe.py +64 -0
- examples/transcribe_hotword.py +122 -0
- generation_config.json +7 -0
- hotword/__init__.py +17 -0
- hotword/hotword_trie.py +165 -0
- merges.txt +0 -0
- model.safetensors +3 -0
- modeling_arkasr.py +332 -0
- preprocessor_config.json +18 -0
- processing_arkasr.py +446 -0
- processor_config.json +9 -0
- qwen3_asr_audio_config.py +425 -0
- qwen3_asr_audio_model.py +229 -0
- special_tokens_map.json +23 -0
- tokenizer.json +3 -0
- tokenizer_config.json +98 -0
- vocab.json +0 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.py[cod]
|
| 3 |
+
.pytest_cache/
|
| 4 |
+
.mypy_cache/
|
| 5 |
+
.ruff_cache/
|
| 6 |
+
.venv/
|
| 7 |
+
.venv-*/
|
| 8 |
+
.env
|
| 9 |
+
*.log
|
| 10 |
+
tmp/
|
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.
|
README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: transformers
|
| 3 |
+
tags:
|
| 4 |
+
- automatic-speech-recognition
|
| 5 |
+
- speech
|
| 6 |
+
- audio
|
| 7 |
+
- multilingual
|
| 8 |
+
- transformers
|
| 9 |
+
- pytorch
|
| 10 |
+
- safetensors
|
| 11 |
+
- hotword
|
| 12 |
+
- audio8
|
| 13 |
+
pipeline_tag: automatic-speech-recognition
|
| 14 |
+
language:
|
| 15 |
+
- en
|
| 16 |
+
- zh
|
| 17 |
+
- fr
|
| 18 |
+
- ja
|
| 19 |
+
- yue
|
| 20 |
+
license: apache-2.0
|
| 21 |
+
repository: https://github.com/AutoArk/open-audio-opd
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
<div align="center">
|
| 25 |
+
|
| 26 |
+
# Audio8-ASR-0.1B
|
| 27 |
+
|
| 28 |
+
[](https://github.com/AutoArk/open-audio-opd)
|
| 29 |
+
[](https://arxiv.org/abs/2605.28139)
|
| 30 |
+
[](https://www.apache.org/licenses/LICENSE-2.0)
|
| 31 |
+
|
| 32 |
+
</div>
|
| 33 |
+
|
| 34 |
+
`Audio8-ASR-0.1B` is a compact autoregressive ASR model whose language-model
|
| 35 |
+
component has only 0.1B parameters. It supports multilingual speech recognition
|
| 36 |
+
for languages including Chinese, English, French, Japanese, and Cantonese. We
|
| 37 |
+
position it as one of the smallest usable performance ASR models in the LLM era.
|
| 38 |
+
|
| 39 |
+
This base repository provides the Hugging Face Transformers checkpoint. We also
|
| 40 |
+
provide deployment-focused releases:
|
| 41 |
+
|
| 42 |
+
- [Audio8-ASR-0.1B-onnx-runtime](https://huggingface.co/AutoArk-AI/Audio8-ASR-0.1B-onnx-runtime)
|
| 43 |
+
- [Audio8-ASR-0.1B-iOS-ANE](https://huggingface.co/AutoArk-AI/Audio8-ASR-0.1B-iOS-ANE)
|
| 44 |
+
|
| 45 |
+
The ONNX Runtime release is designed for edge-device deployment and can run
|
| 46 |
+
with roughly 1.1 GB peak memory footprint, depending on device, runtime
|
| 47 |
+
configuration, and workload.
|
| 48 |
+
|
| 49 |
+
The iOS release is designed for local iPhone transcription with roughly 200 MB
|
| 50 |
+
peak runtime memory footprint, depending on device, iOS version, and workload.
|
| 51 |
+
|
| 52 |
+
## Open ASR Leaderboard Evaluation
|
| 53 |
+
|
| 54 |
+
Coming Soon.
|
| 55 |
+
|
| 56 |
+
## Model Overview
|
| 57 |
+
|
| 58 |
+
- **Task:** automatic speech recognition
|
| 59 |
+
- **Checkpoint format:** `safetensors`
|
| 60 |
+
- **Sampling rate:** 16 kHz
|
| 61 |
+
- **Decoder:** 8-layer Qwen-style causal LM
|
| 62 |
+
- **Audio front end:** Qwen3-ASR audio encoder plus MLP adapter/projector
|
| 63 |
+
- **Language-model parameters:** 103,502,336 (about 0.104B)
|
| 64 |
+
- **End-to-end unique parameters:** 323,990,528 (about 0.324B)
|
| 65 |
+
- **Runtime:** Hugging Face Transformers
|
| 66 |
+
- **Hotwords:** optional decode-time logit boosting, no fine-tuning required
|
| 67 |
+
|
| 68 |
+
The model should be loaded with `trust_remote_code=True`.
|
| 69 |
+
|
| 70 |
+
## Files
|
| 71 |
+
|
| 72 |
+
- `config.json`, tokenizer files, processor files, and `model.safetensors`
|
| 73 |
+
- `configuration_arkasr.py`, `modeling_arkasr.py`, `processing_arkasr.py`
|
| 74 |
+
- `qwen3_asr_audio_config.py`, `qwen3_asr_audio_model.py`
|
| 75 |
+
- `hotword/`: backend-agnostic hotword trie
|
| 76 |
+
- `examples/`: Transformers inference examples
|
| 77 |
+
|
| 78 |
+
The root `config.json` is intentionally kept in this repository so Hugging Face
|
| 79 |
+
can recognize the model package and count downloads through normal model-file
|
| 80 |
+
queries.
|
| 81 |
+
|
| 82 |
+
## Transformers Inference
|
| 83 |
+
|
| 84 |
+
```python
|
| 85 |
+
import torch
|
| 86 |
+
from transformers import AutoModelForCausalLM, AutoProcessor
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
model_path = "AutoArk-AI/Audio8-ASR-0.1B"
|
| 90 |
+
audio_path = "path/to/audio.wav"
|
| 91 |
+
|
| 92 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 93 |
+
torch_dtype = torch.bfloat16 if device == "cuda" else torch.float32
|
| 94 |
+
|
| 95 |
+
processor = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
|
| 96 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 97 |
+
model_path,
|
| 98 |
+
trust_remote_code=True,
|
| 99 |
+
torch_dtype=torch_dtype,
|
| 100 |
+
attn_implementation="eager",
|
| 101 |
+
).to(device)
|
| 102 |
+
model.eval()
|
| 103 |
+
|
| 104 |
+
conversation = [
|
| 105 |
+
{
|
| 106 |
+
"role": "user",
|
| 107 |
+
"content": [
|
| 108 |
+
{"type": "audio", "path": audio_path},
|
| 109 |
+
{"type": "text", "text": "Please transcribe this audio."},
|
| 110 |
+
],
|
| 111 |
+
}
|
| 112 |
+
]
|
| 113 |
+
|
| 114 |
+
batch = processor.apply_chat_template(
|
| 115 |
+
conversation,
|
| 116 |
+
return_tensors="pt",
|
| 117 |
+
sampling_rate=16000,
|
| 118 |
+
audio_padding="longest",
|
| 119 |
+
add_generation_prompt=True,
|
| 120 |
+
audio_max_length=30 * 16000,
|
| 121 |
+
text_kwargs={"padding": "longest", "truncation": True, "max_length": 1000},
|
| 122 |
+
)
|
| 123 |
+
batch = {key: value.to(device) if hasattr(value, "to") else value for key, value in dict(batch).items()}
|
| 124 |
+
|
| 125 |
+
with torch.inference_mode():
|
| 126 |
+
output_ids = model.generate(**batch, max_new_tokens=128, do_sample=False)
|
| 127 |
+
|
| 128 |
+
prompt_len = int(batch["input_ids"].shape[1])
|
| 129 |
+
text = processor.decode(output_ids[0, prompt_len:], skip_special_tokens=True).strip()
|
| 130 |
+
print(text)
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
Equivalent script:
|
| 134 |
+
|
| 135 |
+
```bash
|
| 136 |
+
python examples/transcribe.py path/to/audio.wav --model AutoArk-AI/Audio8-ASR-0.1B
|
| 137 |
+
```
|
| 138 |
+
|
| 139 |
+
For local staging before upload:
|
| 140 |
+
|
| 141 |
+
```bash
|
| 142 |
+
python examples/transcribe.py path/to/audio.wav --model .
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
## Hotword Boosting
|
| 146 |
+
|
| 147 |
+
Hotwords are applied at decode time by nudging logits for tokenizer paths that
|
| 148 |
+
match the requested words. This does not modify model weights and does not
|
| 149 |
+
inject the hotwords into the prompt.
|
| 150 |
+
|
| 151 |
+
```bash
|
| 152 |
+
python examples/transcribe_hotword.py path/to/audio.wav \
|
| 153 |
+
--model AutoArk-AI/Audio8-ASR-0.1B \
|
| 154 |
+
--hotwords "Audio8,AutoArk"
|
| 155 |
+
```
|
| 156 |
+
|
| 157 |
+
Main knobs:
|
| 158 |
+
|
| 159 |
+
- `--hotword_topk`: only boost tokens already inside the current top-k logits.
|
| 160 |
+
- `--hotword_start_boost`: boost for the first token of each hotword.
|
| 161 |
+
- `--hotword_continuation_boost`: boost for continuation tokens after a matched prefix.
|
| 162 |
+
|
| 163 |
+
## Related Releases
|
| 164 |
+
|
| 165 |
+
- [ONNX Runtime package](https://huggingface.co/AutoArk-AI/Audio8-ASR-0.1B-onnx-runtime)
|
| 166 |
+
- [iOS ANE package](https://huggingface.co/AutoArk-AI/Audio8-ASR-0.1B-iOS-ANE)
|
| 167 |
+
|
| 168 |
+
## Limitations
|
| 169 |
+
|
| 170 |
+
- The default examples target short-form ASR and truncate audio at 30 seconds.
|
| 171 |
+
- Hotword boosting can help with near-miss terms but can also over-bias decoding
|
| 172 |
+
when boost values are too high.
|
| 173 |
+
- Some Transformers/tokenizers versions emit a Qwen tokenizer regex warning. The
|
| 174 |
+
staged tokenizer config is kept in the loadable form used by this package; pass
|
| 175 |
+
explicit tokenizer regex flags only after testing your local Transformers version.
|
| 176 |
+
|
| 177 |
+
## Acknowledgements
|
| 178 |
+
|
| 179 |
+
The audio encoder backbone is based on
|
| 180 |
+
[Qwen3-ASR-0.6B](https://huggingface.co/Qwen/Qwen3-ASR-0.6B), with the audio
|
| 181 |
+
adapter and projector trained as part of Audio8-ASR. The language-model backbone
|
| 182 |
+
is based on
|
| 183 |
+
[Ref-Pretrain-Qwen-104M](https://huggingface.co/MiniLLM/Ref-Pretrain-Qwen-104M).
|
added_tokens.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"<|assistant|>": 151650,
|
| 3 |
+
"<|audio|>": 151646,
|
| 4 |
+
"<|begin_of_audio|>": 151648,
|
| 5 |
+
"<|end_of_audio|>": 151649,
|
| 6 |
+
"<|endoftext|>": 151643,
|
| 7 |
+
"<|im_end|>": 151645,
|
| 8 |
+
"<|im_start|>": 151644,
|
| 9 |
+
"<|system|>": 151651,
|
| 10 |
+
"<|user|>": 151647
|
| 11 |
+
}
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<|im_start|>system
|
| 2 |
+
You are a helpful assistant<|im_end|>
|
| 3 |
+
' }}{% endif %}{{'<|im_start|>' + message['role'] + '
|
| 4 |
+
' + message['content'] + '<|im_end|>' + '
|
| 5 |
+
'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant
|
| 6 |
+
' }}{% endif %}
|
config.json
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"adapter_type": "qwen3_asr_mlp_tower",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"ArkasrForConditionalGeneration"
|
| 5 |
+
],
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"audio_token_id": 151646,
|
| 8 |
+
"auto_map": {
|
| 9 |
+
"AutoConfig": "configuration_arkasr.ArkasrConfig",
|
| 10 |
+
"AutoModelForCausalLM": "modeling_arkasr.ArkasrForConditionalGeneration"
|
| 11 |
+
},
|
| 12 |
+
"bos_token_id": 151643,
|
| 13 |
+
"dtype": "bfloat16",
|
| 14 |
+
"eos_token_id": 151645,
|
| 15 |
+
"hidden_act": "silu",
|
| 16 |
+
"hidden_size": 512,
|
| 17 |
+
"initializer_range": 0.02,
|
| 18 |
+
"intermediate_size": 1408,
|
| 19 |
+
"layer_types": [
|
| 20 |
+
"full_attention",
|
| 21 |
+
"full_attention",
|
| 22 |
+
"full_attention",
|
| 23 |
+
"full_attention",
|
| 24 |
+
"full_attention",
|
| 25 |
+
"full_attention",
|
| 26 |
+
"full_attention",
|
| 27 |
+
"full_attention"
|
| 28 |
+
],
|
| 29 |
+
"max_position_embeddings": 32768,
|
| 30 |
+
"max_whisper_length": 1500,
|
| 31 |
+
"max_window_layers": 28,
|
| 32 |
+
"merge_factor": 4,
|
| 33 |
+
"mlp_adapter_act": "gelu",
|
| 34 |
+
"model_type": "arkasr",
|
| 35 |
+
"num_attention_heads": 8,
|
| 36 |
+
"num_hidden_layers": 8,
|
| 37 |
+
"num_key_value_heads": 8,
|
| 38 |
+
"pad_token_id": 151643,
|
| 39 |
+
"rms_norm_eps": 1e-06,
|
| 40 |
+
"rope_scaling": null,
|
| 41 |
+
"rope_theta": 1000000.0,
|
| 42 |
+
"sliding_window": null,
|
| 43 |
+
"spec_aug": false,
|
| 44 |
+
"tie_word_embeddings": true,
|
| 45 |
+
"transformers_version": "4.57.3",
|
| 46 |
+
"use_cache": true,
|
| 47 |
+
"use_rope": false,
|
| 48 |
+
"use_sliding_window": false,
|
| 49 |
+
"vocab_size": 151936,
|
| 50 |
+
"whisper_config": {
|
| 51 |
+
"_name_or_path": "openai/whisper-small",
|
| 52 |
+
"activation_dropout": 0.0,
|
| 53 |
+
"activation_function": "gelu",
|
| 54 |
+
"apply_spec_augment": false,
|
| 55 |
+
"architectures": [
|
| 56 |
+
"WhisperForConditionalGeneration"
|
| 57 |
+
],
|
| 58 |
+
"attention_dropout": 0.0,
|
| 59 |
+
"begin_suppress_tokens": [
|
| 60 |
+
220,
|
| 61 |
+
50257
|
| 62 |
+
],
|
| 63 |
+
"bos_token_id": 50257,
|
| 64 |
+
"classifier_proj_size": 256,
|
| 65 |
+
"d_model": 768,
|
| 66 |
+
"decoder_attention_heads": 12,
|
| 67 |
+
"decoder_ffn_dim": 3072,
|
| 68 |
+
"decoder_layerdrop": 0.0,
|
| 69 |
+
"decoder_layers": 12,
|
| 70 |
+
"decoder_start_token_id": 50258,
|
| 71 |
+
"dropout": 0.0,
|
| 72 |
+
"dtype": "float32",
|
| 73 |
+
"encoder_attention_heads": 12,
|
| 74 |
+
"encoder_ffn_dim": 3072,
|
| 75 |
+
"encoder_layerdrop": 0.0,
|
| 76 |
+
"encoder_layers": 12,
|
| 77 |
+
"eos_token_id": 50257,
|
| 78 |
+
"forced_decoder_ids": [
|
| 79 |
+
[
|
| 80 |
+
1,
|
| 81 |
+
50259
|
| 82 |
+
],
|
| 83 |
+
[
|
| 84 |
+
2,
|
| 85 |
+
50359
|
| 86 |
+
],
|
| 87 |
+
[
|
| 88 |
+
3,
|
| 89 |
+
50363
|
| 90 |
+
]
|
| 91 |
+
],
|
| 92 |
+
"init_std": 0.02,
|
| 93 |
+
"mask_feature_length": 10,
|
| 94 |
+
"mask_feature_min_masks": 0,
|
| 95 |
+
"mask_feature_prob": 0.0,
|
| 96 |
+
"mask_time_length": 10,
|
| 97 |
+
"mask_time_min_masks": 2,
|
| 98 |
+
"mask_time_prob": 0.05,
|
| 99 |
+
"max_length": 448,
|
| 100 |
+
"max_source_positions": 1500,
|
| 101 |
+
"max_target_positions": 448,
|
| 102 |
+
"median_filter_width": 7,
|
| 103 |
+
"model_type": "whisper",
|
| 104 |
+
"num_hidden_layers": 12,
|
| 105 |
+
"num_mel_bins": 80,
|
| 106 |
+
"pad_token_id": 50257,
|
| 107 |
+
"scale_embedding": false,
|
| 108 |
+
"suppress_tokens": [
|
| 109 |
+
1,
|
| 110 |
+
2,
|
| 111 |
+
7,
|
| 112 |
+
8,
|
| 113 |
+
9,
|
| 114 |
+
10,
|
| 115 |
+
14,
|
| 116 |
+
25,
|
| 117 |
+
26,
|
| 118 |
+
27,
|
| 119 |
+
28,
|
| 120 |
+
29,
|
| 121 |
+
31,
|
| 122 |
+
58,
|
| 123 |
+
59,
|
| 124 |
+
60,
|
| 125 |
+
61,
|
| 126 |
+
62,
|
| 127 |
+
63,
|
| 128 |
+
90,
|
| 129 |
+
91,
|
| 130 |
+
92,
|
| 131 |
+
93,
|
| 132 |
+
359,
|
| 133 |
+
503,
|
| 134 |
+
522,
|
| 135 |
+
542,
|
| 136 |
+
873,
|
| 137 |
+
893,
|
| 138 |
+
902,
|
| 139 |
+
918,
|
| 140 |
+
922,
|
| 141 |
+
931,
|
| 142 |
+
1350,
|
| 143 |
+
1853,
|
| 144 |
+
1982,
|
| 145 |
+
2460,
|
| 146 |
+
2627,
|
| 147 |
+
3246,
|
| 148 |
+
3253,
|
| 149 |
+
3268,
|
| 150 |
+
3536,
|
| 151 |
+
3846,
|
| 152 |
+
3961,
|
| 153 |
+
4183,
|
| 154 |
+
4667,
|
| 155 |
+
6585,
|
| 156 |
+
6647,
|
| 157 |
+
7273,
|
| 158 |
+
9061,
|
| 159 |
+
9383,
|
| 160 |
+
10428,
|
| 161 |
+
10929,
|
| 162 |
+
11938,
|
| 163 |
+
12033,
|
| 164 |
+
12331,
|
| 165 |
+
12562,
|
| 166 |
+
13793,
|
| 167 |
+
14157,
|
| 168 |
+
14635,
|
| 169 |
+
15265,
|
| 170 |
+
15618,
|
| 171 |
+
16553,
|
| 172 |
+
16604,
|
| 173 |
+
18362,
|
| 174 |
+
18956,
|
| 175 |
+
20075,
|
| 176 |
+
21675,
|
| 177 |
+
22520,
|
| 178 |
+
26130,
|
| 179 |
+
26161,
|
| 180 |
+
26435,
|
| 181 |
+
28279,
|
| 182 |
+
29464,
|
| 183 |
+
31650,
|
| 184 |
+
32302,
|
| 185 |
+
32470,
|
| 186 |
+
36865,
|
| 187 |
+
42863,
|
| 188 |
+
47425,
|
| 189 |
+
49870,
|
| 190 |
+
50254,
|
| 191 |
+
50258,
|
| 192 |
+
50360,
|
| 193 |
+
50361,
|
| 194 |
+
50362
|
| 195 |
+
],
|
| 196 |
+
"use_cache": true,
|
| 197 |
+
"use_weighted_layer_sum": false,
|
| 198 |
+
"vocab_size": 51865
|
| 199 |
+
},
|
| 200 |
+
"qwen3_asr_audio_config": {
|
| 201 |
+
"_name_or_path": "",
|
| 202 |
+
"activation_dropout": 0,
|
| 203 |
+
"activation_function": "gelu",
|
| 204 |
+
"add_cross_attention": false,
|
| 205 |
+
"architectures": null,
|
| 206 |
+
"attention_dropout": 0,
|
| 207 |
+
"bad_words_ids": null,
|
| 208 |
+
"begin_suppress_tokens": null,
|
| 209 |
+
"bos_token_id": null,
|
| 210 |
+
"chunk_size_feed_forward": 0,
|
| 211 |
+
"conv_chunksize": 500,
|
| 212 |
+
"cross_attention_hidden_size": null,
|
| 213 |
+
"d_model": 896,
|
| 214 |
+
"decoder_start_token_id": null,
|
| 215 |
+
"diversity_penalty": 0.0,
|
| 216 |
+
"do_sample": false,
|
| 217 |
+
"downsample_hidden_size": 480,
|
| 218 |
+
"dropout": 0,
|
| 219 |
+
"dtype": null,
|
| 220 |
+
"early_stopping": false,
|
| 221 |
+
"encoder_attention_heads": 14,
|
| 222 |
+
"encoder_ffn_dim": 3584,
|
| 223 |
+
"encoder_layers": 18,
|
| 224 |
+
"encoder_no_repeat_ngram_size": 0,
|
| 225 |
+
"eos_token_id": null,
|
| 226 |
+
"exponential_decay_length_penalty": null,
|
| 227 |
+
"finetuning_task": null,
|
| 228 |
+
"forced_bos_token_id": null,
|
| 229 |
+
"forced_eos_token_id": null,
|
| 230 |
+
"id2label": {
|
| 231 |
+
"0": "LABEL_0",
|
| 232 |
+
"1": "LABEL_1"
|
| 233 |
+
},
|
| 234 |
+
"initializer_range": 0.02,
|
| 235 |
+
"is_decoder": false,
|
| 236 |
+
"is_encoder_decoder": false,
|
| 237 |
+
"label2id": {
|
| 238 |
+
"LABEL_0": 0,
|
| 239 |
+
"LABEL_1": 1
|
| 240 |
+
},
|
| 241 |
+
"length_penalty": 1.0,
|
| 242 |
+
"max_length": 20,
|
| 243 |
+
"max_source_positions": 1500,
|
| 244 |
+
"min_length": 0,
|
| 245 |
+
"model_type": "qwen3_asr_audio_encoder",
|
| 246 |
+
"n_window": 50,
|
| 247 |
+
"n_window_infer": 800,
|
| 248 |
+
"no_repeat_ngram_size": 0,
|
| 249 |
+
"num_beam_groups": 1,
|
| 250 |
+
"num_beams": 1,
|
| 251 |
+
"num_hidden_layers": 18,
|
| 252 |
+
"num_mel_bins": 128,
|
| 253 |
+
"num_return_sequences": 1,
|
| 254 |
+
"output_attentions": false,
|
| 255 |
+
"output_dim": 1024,
|
| 256 |
+
"output_hidden_states": false,
|
| 257 |
+
"output_scores": false,
|
| 258 |
+
"pad_token_id": null,
|
| 259 |
+
"prefix": null,
|
| 260 |
+
"problem_type": null,
|
| 261 |
+
"pruned_heads": {},
|
| 262 |
+
"remove_invalid_values": false,
|
| 263 |
+
"repetition_penalty": 1.0,
|
| 264 |
+
"return_dict": true,
|
| 265 |
+
"return_dict_in_generate": false,
|
| 266 |
+
"scale_embedding": false,
|
| 267 |
+
"sep_token_id": null,
|
| 268 |
+
"suppress_tokens": null,
|
| 269 |
+
"task_specific_params": null,
|
| 270 |
+
"temperature": 1.0,
|
| 271 |
+
"tf_legacy_loss": false,
|
| 272 |
+
"tie_encoder_decoder": false,
|
| 273 |
+
"tie_word_embeddings": true,
|
| 274 |
+
"tokenizer_class": null,
|
| 275 |
+
"top_k": 50,
|
| 276 |
+
"top_p": 1.0,
|
| 277 |
+
"torchscript": false,
|
| 278 |
+
"typical_p": 1.0,
|
| 279 |
+
"use_bfloat16": false
|
| 280 |
+
},
|
| 281 |
+
"qwen3_asr_mlp_tower_layers": 4,
|
| 282 |
+
"qwen3_asr_mlp_tower_hidden_size": 0,
|
| 283 |
+
"qwen3_asr_mlp_tower_dropout": 0.0,
|
| 284 |
+
"audio_backend": "qwen3_asr_mlp_tower",
|
| 285 |
+
"name_or_path": "audio8-asr-0.1B"
|
| 286 |
+
}
|
configuration_arkasr.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Dict, Optional, Union
|
| 4 |
+
|
| 5 |
+
from transformers import Qwen2Config, WhisperConfig
|
| 6 |
+
|
| 7 |
+
from .qwen3_asr_audio_config import Qwen3ASRAudioEncoderConfig
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class ArkasrConfig(Qwen2Config):
|
| 11 |
+
model_type = "arkasr"
|
| 12 |
+
is_composition = True
|
| 13 |
+
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
whisper_config: Optional[Union[Dict[str, Any], WhisperConfig]] = None,
|
| 17 |
+
qwen3_asr_audio_config: Optional[Union[Dict[str, Any], Qwen3ASRAudioEncoderConfig]] = None,
|
| 18 |
+
adapter_type: str = "qwen3_asr_mlp_tower",
|
| 19 |
+
merge_factor: int = 4,
|
| 20 |
+
spec_aug: bool = False,
|
| 21 |
+
use_rope: bool = False,
|
| 22 |
+
max_whisper_length: int = 1500,
|
| 23 |
+
mlp_adapter_act: str = "gelu",
|
| 24 |
+
qwen3_asr_mlp_tower_layers: int = 4,
|
| 25 |
+
qwen3_asr_mlp_tower_hidden_size: int = 0,
|
| 26 |
+
qwen3_asr_mlp_tower_dropout: float = 0.0,
|
| 27 |
+
**kwargs,
|
| 28 |
+
):
|
| 29 |
+
super().__init__(**kwargs)
|
| 30 |
+
|
| 31 |
+
if isinstance(whisper_config, dict):
|
| 32 |
+
self.whisper_config = WhisperConfig(**whisper_config)
|
| 33 |
+
elif isinstance(whisper_config, WhisperConfig):
|
| 34 |
+
self.whisper_config = whisper_config
|
| 35 |
+
else:
|
| 36 |
+
self.whisper_config = WhisperConfig()
|
| 37 |
+
|
| 38 |
+
if isinstance(qwen3_asr_audio_config, dict):
|
| 39 |
+
self.qwen3_asr_audio_config = Qwen3ASRAudioEncoderConfig(**qwen3_asr_audio_config)
|
| 40 |
+
elif isinstance(qwen3_asr_audio_config, Qwen3ASRAudioEncoderConfig):
|
| 41 |
+
self.qwen3_asr_audio_config = qwen3_asr_audio_config
|
| 42 |
+
else:
|
| 43 |
+
self.qwen3_asr_audio_config = Qwen3ASRAudioEncoderConfig(
|
| 44 |
+
num_mel_bins=128,
|
| 45 |
+
encoder_layers=18,
|
| 46 |
+
encoder_attention_heads=14,
|
| 47 |
+
encoder_ffn_dim=3584,
|
| 48 |
+
d_model=896,
|
| 49 |
+
max_source_positions=1500,
|
| 50 |
+
n_window=50,
|
| 51 |
+
output_dim=1024,
|
| 52 |
+
n_window_infer=800,
|
| 53 |
+
conv_chunksize=500,
|
| 54 |
+
downsample_hidden_size=480,
|
| 55 |
+
)
|
| 56 |
+
|
| 57 |
+
self.adapter_type = adapter_type
|
| 58 |
+
self.merge_factor = int(merge_factor)
|
| 59 |
+
self.spec_aug = bool(spec_aug)
|
| 60 |
+
self.use_rope = bool(use_rope)
|
| 61 |
+
self.max_whisper_length = int(max_whisper_length)
|
| 62 |
+
self.mlp_adapter_act = mlp_adapter_act
|
| 63 |
+
self.qwen3_asr_mlp_tower_layers = int(qwen3_asr_mlp_tower_layers)
|
| 64 |
+
self.qwen3_asr_mlp_tower_hidden_size = int(qwen3_asr_mlp_tower_hidden_size)
|
| 65 |
+
self.qwen3_asr_mlp_tower_dropout = float(qwen3_asr_mlp_tower_dropout)
|
| 66 |
+
|
| 67 |
+
def to_dict(self):
|
| 68 |
+
output = super().to_dict()
|
| 69 |
+
output["whisper_config"] = self.whisper_config.to_dict()
|
| 70 |
+
output["qwen3_asr_audio_config"] = self.qwen3_asr_audio_config.to_dict()
|
| 71 |
+
output["qwen3_asr_mlp_tower_layers"] = self.qwen3_asr_mlp_tower_layers
|
| 72 |
+
output["qwen3_asr_mlp_tower_hidden_size"] = self.qwen3_asr_mlp_tower_hidden_size
|
| 73 |
+
output["qwen3_asr_mlp_tower_dropout"] = self.qwen3_asr_mlp_tower_dropout
|
| 74 |
+
return output
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
__all__ = ["ArkasrConfig"]
|
examples/requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
transformers>=4.57.0
|
| 3 |
+
safetensors
|
| 4 |
+
librosa
|
| 5 |
+
soundfile
|
| 6 |
+
numpy
|
examples/transcribe.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from transformers import AutoModelForCausalLM, AutoProcessor
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
PROMPT = "Please transcribe this audio."
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def build_conversation(audio_path: Path) -> list[dict]:
|
| 15 |
+
return [
|
| 16 |
+
{
|
| 17 |
+
"role": "user",
|
| 18 |
+
"content": [
|
| 19 |
+
{"type": "audio", "path": str(audio_path)},
|
| 20 |
+
{"type": "text", "text": PROMPT},
|
| 21 |
+
],
|
| 22 |
+
}
|
| 23 |
+
]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def main() -> None:
|
| 27 |
+
parser = argparse.ArgumentParser(description="Transcribe one audio file with audio8-asr-0.1B.")
|
| 28 |
+
parser.add_argument("audio", type=Path)
|
| 29 |
+
parser.add_argument("--model", default=".")
|
| 30 |
+
parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
|
| 31 |
+
parser.add_argument("--max_new_tokens", type=int, default=128)
|
| 32 |
+
parser.add_argument("--max_audio_seconds", type=int, default=30)
|
| 33 |
+
args = parser.parse_args()
|
| 34 |
+
|
| 35 |
+
device = torch.device(args.device)
|
| 36 |
+
dtype = torch.bfloat16 if device.type == "cuda" else torch.float32
|
| 37 |
+
processor = AutoProcessor.from_pretrained(args.model, trust_remote_code=True)
|
| 38 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 39 |
+
args.model,
|
| 40 |
+
trust_remote_code=True,
|
| 41 |
+
torch_dtype=dtype,
|
| 42 |
+
attn_implementation="eager",
|
| 43 |
+
).to(device)
|
| 44 |
+
model.eval()
|
| 45 |
+
|
| 46 |
+
batch = processor.apply_chat_template(
|
| 47 |
+
build_conversation(args.audio),
|
| 48 |
+
return_tensors="pt",
|
| 49 |
+
sampling_rate=16000,
|
| 50 |
+
audio_padding="longest",
|
| 51 |
+
add_generation_prompt=True,
|
| 52 |
+
audio_max_length=int(args.max_audio_seconds) * 16000,
|
| 53 |
+
text_kwargs={"padding": "longest", "truncation": True, "max_length": 1000},
|
| 54 |
+
)
|
| 55 |
+
batch = {key: value.to(device) if hasattr(value, "to") else value for key, value in dict(batch).items()}
|
| 56 |
+
with torch.inference_mode():
|
| 57 |
+
output_ids = model.generate(**batch, max_new_tokens=args.max_new_tokens, do_sample=False)
|
| 58 |
+
prompt_len = int(batch["input_ids"].shape[1])
|
| 59 |
+
text = processor.decode(output_ids[0, prompt_len:], skip_special_tokens=True).strip()
|
| 60 |
+
print(text)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
if __name__ == "__main__":
|
| 64 |
+
main()
|
examples/transcribe_hotword.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from transformers import LogitsProcessorList
|
| 9 |
+
from transformers import AutoModelForCausalLM, AutoProcessor
|
| 10 |
+
|
| 11 |
+
from hotword.hotword_trie import build_trie_from_hotwords, parse_hotwords
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
PROMPT = "Please transcribe this audio."
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def build_conversation(audio_path: Path) -> list[dict]:
|
| 18 |
+
return [
|
| 19 |
+
{
|
| 20 |
+
"role": "user",
|
| 21 |
+
"content": [
|
| 22 |
+
{"type": "audio", "path": str(audio_path)},
|
| 23 |
+
{"type": "text", "text": PROMPT},
|
| 24 |
+
],
|
| 25 |
+
}
|
| 26 |
+
]
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def make_hotword_processor(tokenizer, hotwords: str, *, topk: int, start_boost: float, continuation_boost: float):
|
| 30 |
+
words = parse_hotwords(hotwords)
|
| 31 |
+
if not words:
|
| 32 |
+
return None
|
| 33 |
+
special_ids = set(int(value) for value in tokenizer.all_special_ids if value is not None)
|
| 34 |
+
|
| 35 |
+
def encode(text: str) -> list[int]:
|
| 36 |
+
return tokenizer.encode(text, add_special_tokens=False)
|
| 37 |
+
|
| 38 |
+
def id_to_token(token_id: int) -> str:
|
| 39 |
+
return str(tokenizer.convert_ids_to_tokens(int(token_id)))
|
| 40 |
+
|
| 41 |
+
trie, _ = build_trie_from_hotwords(
|
| 42 |
+
words,
|
| 43 |
+
encode=encode,
|
| 44 |
+
id_to_token=id_to_token,
|
| 45 |
+
special_ids=special_ids,
|
| 46 |
+
start_boost=start_boost,
|
| 47 |
+
continuation_boost=continuation_boost,
|
| 48 |
+
)
|
| 49 |
+
if not trie:
|
| 50 |
+
return None
|
| 51 |
+
|
| 52 |
+
def processor(input_ids: torch.LongTensor, scores: torch.FloatTensor) -> torch.FloatTensor:
|
| 53 |
+
vocab_size = int(scores.shape[-1])
|
| 54 |
+
allowed_rows = None
|
| 55 |
+
if int(topk) > 0:
|
| 56 |
+
top_idx = torch.topk(scores.float(), k=min(int(topk), vocab_size), dim=-1).indices
|
| 57 |
+
allowed_rows = [set(int(x) for x in row.tolist()) for row in top_idx]
|
| 58 |
+
for batch_i in range(int(scores.shape[0])):
|
| 59 |
+
boosts = trie.boosts_for_generated(input_ids[batch_i].tolist())
|
| 60 |
+
allowed = allowed_rows[batch_i] if allowed_rows is not None else None
|
| 61 |
+
for token_id, boost in boosts.items():
|
| 62 |
+
if 0 <= int(token_id) < vocab_size and (allowed is None or int(token_id) in allowed):
|
| 63 |
+
scores[batch_i, int(token_id)] += float(boost)
|
| 64 |
+
return scores
|
| 65 |
+
|
| 66 |
+
return processor
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main() -> None:
|
| 70 |
+
parser = argparse.ArgumentParser(description="Transcribe one audio file with optional hotword boosting.")
|
| 71 |
+
parser.add_argument("audio", type=Path)
|
| 72 |
+
parser.add_argument("--model", default=".")
|
| 73 |
+
parser.add_argument("--hotwords", required=True, help="Comma-separated hotwords.")
|
| 74 |
+
parser.add_argument("--hotword_topk", type=int, default=50)
|
| 75 |
+
parser.add_argument("--hotword_start_boost", type=float, default=6.0)
|
| 76 |
+
parser.add_argument("--hotword_continuation_boost", type=float, default=8.0)
|
| 77 |
+
parser.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
|
| 78 |
+
parser.add_argument("--max_new_tokens", type=int, default=128)
|
| 79 |
+
args = parser.parse_args()
|
| 80 |
+
|
| 81 |
+
device = torch.device(args.device)
|
| 82 |
+
dtype = torch.bfloat16 if device.type == "cuda" else torch.float32
|
| 83 |
+
processor = AutoProcessor.from_pretrained(args.model, trust_remote_code=True)
|
| 84 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 85 |
+
args.model,
|
| 86 |
+
trust_remote_code=True,
|
| 87 |
+
torch_dtype=dtype,
|
| 88 |
+
attn_implementation="eager",
|
| 89 |
+
).to(device)
|
| 90 |
+
model.eval()
|
| 91 |
+
|
| 92 |
+
batch = processor.apply_chat_template(
|
| 93 |
+
build_conversation(args.audio),
|
| 94 |
+
return_tensors="pt",
|
| 95 |
+
sampling_rate=16000,
|
| 96 |
+
audio_padding="longest",
|
| 97 |
+
add_generation_prompt=True,
|
| 98 |
+
audio_max_length=30 * 16000,
|
| 99 |
+
text_kwargs={"padding": "longest", "truncation": True, "max_length": 1000},
|
| 100 |
+
)
|
| 101 |
+
batch = {key: value.to(device) if hasattr(value, "to") else value for key, value in dict(batch).items()}
|
| 102 |
+
logits_processor = make_hotword_processor(
|
| 103 |
+
processor.tokenizer,
|
| 104 |
+
args.hotwords,
|
| 105 |
+
topk=args.hotword_topk,
|
| 106 |
+
start_boost=args.hotword_start_boost,
|
| 107 |
+
continuation_boost=args.hotword_continuation_boost,
|
| 108 |
+
)
|
| 109 |
+
with torch.inference_mode():
|
| 110 |
+
output_ids = model.generate(
|
| 111 |
+
**batch,
|
| 112 |
+
max_new_tokens=args.max_new_tokens,
|
| 113 |
+
do_sample=False,
|
| 114 |
+
logits_processor=LogitsProcessorList([logits_processor]) if logits_processor is not None else None,
|
| 115 |
+
)
|
| 116 |
+
prompt_len = int(batch["input_ids"].shape[1])
|
| 117 |
+
text = processor.decode(output_ids[0, prompt_len:], skip_special_tokens=True).strip()
|
| 118 |
+
print(text)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
if __name__ == "__main__":
|
| 122 |
+
main()
|
generation_config.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_from_model_config": true,
|
| 3 |
+
"bos_token_id": 151643,
|
| 4 |
+
"eos_token_id": 151645,
|
| 5 |
+
"pad_token_id": 151643,
|
| 6 |
+
"transformers_version": "4.57.3"
|
| 7 |
+
}
|
hotword/__init__.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .hotword_trie import (
|
| 2 |
+
HotwordTrie,
|
| 3 |
+
build_hotword_sequences,
|
| 4 |
+
build_trie_from_hotwords,
|
| 5 |
+
flatten_sequences,
|
| 6 |
+
parse_hotwords,
|
| 7 |
+
token_is_control_or_special,
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
__all__ = [
|
| 11 |
+
"HotwordTrie",
|
| 12 |
+
"build_hotword_sequences",
|
| 13 |
+
"build_trie_from_hotwords",
|
| 14 |
+
"flatten_sequences",
|
| 15 |
+
"parse_hotwords",
|
| 16 |
+
"token_is_control_or_special",
|
| 17 |
+
]
|
hotword/hotword_trie.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import re
|
| 4 |
+
from typing import Any, Callable, Dict, List, Sequence, Set
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
_CONTROL_TOKEN_RE = re.compile(r"<\|[^>]+?\|>")
|
| 8 |
+
_BARE_TAG_RE = re.compile(r"</?[^>\s]+>")
|
| 9 |
+
_CJK_KANA_HANGUL_RE = re.compile("[\u4e00-\u9fff\u3040-\u30ff\uac00-\ud7af]")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class HotwordTrie:
|
| 13 |
+
"""Prefix trie over hotword token-id sequences."""
|
| 14 |
+
|
| 15 |
+
def __init__(
|
| 16 |
+
self,
|
| 17 |
+
token_sequences: Sequence[Sequence[int]],
|
| 18 |
+
*,
|
| 19 |
+
start_boost: float,
|
| 20 |
+
continuation_boost: float,
|
| 21 |
+
) -> None:
|
| 22 |
+
self.start_boost = float(start_boost)
|
| 23 |
+
self.continuation_boost = float(continuation_boost)
|
| 24 |
+
self.trie: Dict[int, Dict[int, Any]] = {}
|
| 25 |
+
self.max_sequence_len = 0
|
| 26 |
+
for seq in token_sequences:
|
| 27 |
+
ids = [int(token_id) for token_id in seq]
|
| 28 |
+
if not ids:
|
| 29 |
+
continue
|
| 30 |
+
node = self.trie
|
| 31 |
+
for token_id in ids:
|
| 32 |
+
node = node.setdefault(token_id, {})
|
| 33 |
+
self.max_sequence_len = max(self.max_sequence_len, len(ids))
|
| 34 |
+
self.start_token_ids = sorted(self.trie.keys())
|
| 35 |
+
|
| 36 |
+
def __bool__(self) -> bool:
|
| 37 |
+
return bool(self.trie)
|
| 38 |
+
|
| 39 |
+
def boosts_for_generated(self, generated_ids: Sequence[int]) -> Dict[int, float]:
|
| 40 |
+
boosts: Dict[int, float] = {}
|
| 41 |
+
if self.start_boost:
|
| 42 |
+
for token_id in self.start_token_ids:
|
| 43 |
+
boosts[token_id] = max(boosts.get(token_id, 0.0), self.start_boost)
|
| 44 |
+
|
| 45 |
+
if not generated_ids or not self.continuation_boost or self.max_sequence_len <= 1:
|
| 46 |
+
return boosts
|
| 47 |
+
|
| 48 |
+
max_prefix_len = min(len(generated_ids), self.max_sequence_len - 1)
|
| 49 |
+
for prefix_len in range(1, max_prefix_len + 1):
|
| 50 |
+
node: Dict[int, Any] = self.trie
|
| 51 |
+
matched = True
|
| 52 |
+
for token_id in generated_ids[-prefix_len:]:
|
| 53 |
+
next_node = node.get(int(token_id))
|
| 54 |
+
if next_node is None:
|
| 55 |
+
matched = False
|
| 56 |
+
break
|
| 57 |
+
node = next_node
|
| 58 |
+
if not matched:
|
| 59 |
+
continue
|
| 60 |
+
for next_token_id in node.keys():
|
| 61 |
+
boosts[int(next_token_id)] = max(
|
| 62 |
+
boosts.get(int(next_token_id), 0.0),
|
| 63 |
+
self.continuation_boost,
|
| 64 |
+
)
|
| 65 |
+
return boosts
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _has_cjk_or_kana_or_hangul(text: str) -> bool:
|
| 69 |
+
return bool(_CJK_KANA_HANGUL_RE.search(str(text or "")))
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _hotword_text_variants(word: str) -> List[str]:
|
| 73 |
+
word = str(word or "").strip()
|
| 74 |
+
if not word:
|
| 75 |
+
return []
|
| 76 |
+
variants = [word]
|
| 77 |
+
if not _has_cjk_or_kana_or_hangul(word) and re.search(r"[A-Za-z0-9_]", word):
|
| 78 |
+
variants.append(" " + word)
|
| 79 |
+
out: List[str] = []
|
| 80 |
+
seen: Set[str] = set()
|
| 81 |
+
for value in variants:
|
| 82 |
+
if value not in seen:
|
| 83 |
+
seen.add(value)
|
| 84 |
+
out.append(value)
|
| 85 |
+
return out
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def token_is_control_or_special(token: str, token_id: int, special_ids: Set[int]) -> bool:
|
| 89 |
+
if int(token_id) in special_ids:
|
| 90 |
+
return True
|
| 91 |
+
token = str(token)
|
| 92 |
+
return bool(_CONTROL_TOKEN_RE.fullmatch(token) or _BARE_TAG_RE.fullmatch(token))
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def build_hotword_sequences(
|
| 96 |
+
hotwords: Sequence[str],
|
| 97 |
+
*,
|
| 98 |
+
encode: Callable[[str], List[int]],
|
| 99 |
+
id_to_token: Callable[[int], str],
|
| 100 |
+
special_ids: Set[int],
|
| 101 |
+
) -> Dict[str, List[List[int]]]:
|
| 102 |
+
special_ids = set(int(value) for value in special_ids if value is not None)
|
| 103 |
+
sequences: Dict[str, List[List[int]]] = {}
|
| 104 |
+
seen_global: Set[tuple[int, ...]] = set()
|
| 105 |
+
for word in hotwords:
|
| 106 |
+
word = str(word or "").strip()
|
| 107 |
+
if not word:
|
| 108 |
+
continue
|
| 109 |
+
variants: List[List[int]] = []
|
| 110 |
+
for text in _hotword_text_variants(word):
|
| 111 |
+
ids = [
|
| 112 |
+
int(token_id)
|
| 113 |
+
for token_id in encode(text)
|
| 114 |
+
if not token_is_control_or_special(id_to_token(int(token_id)), int(token_id), special_ids)
|
| 115 |
+
]
|
| 116 |
+
key = tuple(ids)
|
| 117 |
+
if not key or key in seen_global:
|
| 118 |
+
continue
|
| 119 |
+
seen_global.add(key)
|
| 120 |
+
variants.append(ids)
|
| 121 |
+
if variants:
|
| 122 |
+
sequences[word] = variants
|
| 123 |
+
return sequences
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def flatten_sequences(sequences_by_word: Dict[str, List[List[int]]]) -> List[List[int]]:
|
| 127 |
+
return [ids for variants in sequences_by_word.values() for ids in variants]
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def parse_hotwords(raw: Any) -> List[str]:
|
| 131 |
+
values: List[str] = []
|
| 132 |
+
if isinstance(raw, (list, tuple)):
|
| 133 |
+
values = [str(value).strip() for value in raw]
|
| 134 |
+
elif raw:
|
| 135 |
+
values = [value.strip() for value in re.split(r"[,,]", str(raw))]
|
| 136 |
+
out: List[str] = []
|
| 137 |
+
seen: Set[str] = set()
|
| 138 |
+
for value in values:
|
| 139 |
+
if value and value not in seen:
|
| 140 |
+
seen.add(value)
|
| 141 |
+
out.append(value)
|
| 142 |
+
return out
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def build_trie_from_hotwords(
|
| 146 |
+
hotwords: Sequence[str],
|
| 147 |
+
*,
|
| 148 |
+
encode: Callable[[str], List[int]],
|
| 149 |
+
id_to_token: Callable[[int], str],
|
| 150 |
+
special_ids: Set[int],
|
| 151 |
+
start_boost: float,
|
| 152 |
+
continuation_boost: float,
|
| 153 |
+
) -> tuple[HotwordTrie, Dict[str, List[List[int]]]]:
|
| 154 |
+
sequences_by_word = build_hotword_sequences(
|
| 155 |
+
hotwords,
|
| 156 |
+
encode=encode,
|
| 157 |
+
id_to_token=id_to_token,
|
| 158 |
+
special_ids=special_ids,
|
| 159 |
+
)
|
| 160 |
+
trie = HotwordTrie(
|
| 161 |
+
flatten_sequences(sequences_by_word),
|
| 162 |
+
start_boost=start_boost,
|
| 163 |
+
continuation_boost=continuation_boost,
|
| 164 |
+
)
|
| 165 |
+
return trie, sequences_by_word
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:971d17f64ef5f193fca567fa3e9dc063c4eede97faabb11c0c6abf0b92b23ca4
|
| 3 |
+
size 648031000
|
modeling_arkasr.py
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from typing import Any, Optional
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
from torch import nn
|
| 8 |
+
from transformers import Qwen2ForCausalLM
|
| 9 |
+
from transformers.generation import GenerationMixin
|
| 10 |
+
from transformers.modeling_outputs import CausalLMOutputWithPast
|
| 11 |
+
from transformers.modeling_utils import PreTrainedModel
|
| 12 |
+
|
| 13 |
+
from .configuration_arkasr import ArkasrConfig
|
| 14 |
+
from .qwen3_asr_audio_config import Qwen3ASRAudioEncoderConfig
|
| 15 |
+
from .qwen3_asr_audio_model import Qwen3ASRAudioEncoder
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Qwen3AsrMlpTowerBlock(nn.Module):
|
| 19 |
+
def __init__(self, hidden_size: int, intermediate_size: Optional[int] = None, dropout: float = 0.0):
|
| 20 |
+
super().__init__()
|
| 21 |
+
hidden_size = int(hidden_size)
|
| 22 |
+
intermediate_size = int(intermediate_size or hidden_size * 4)
|
| 23 |
+
self.norm = nn.LayerNorm(hidden_size)
|
| 24 |
+
self.fc1 = nn.Linear(hidden_size, intermediate_size)
|
| 25 |
+
self.act = nn.GELU()
|
| 26 |
+
self.dropout = nn.Dropout(float(dropout))
|
| 27 |
+
self.fc2 = nn.Linear(intermediate_size, hidden_size)
|
| 28 |
+
|
| 29 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 30 |
+
residual = hidden_states
|
| 31 |
+
hidden_states = self.norm(hidden_states)
|
| 32 |
+
hidden_states = self.fc1(hidden_states)
|
| 33 |
+
hidden_states = self.act(hidden_states)
|
| 34 |
+
hidden_states = self.dropout(hidden_states)
|
| 35 |
+
hidden_states = self.fc2(hidden_states)
|
| 36 |
+
hidden_states = self.dropout(hidden_states)
|
| 37 |
+
return residual + hidden_states
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Qwen3AsrMlpTower(nn.Module):
|
| 41 |
+
def __init__(
|
| 42 |
+
self,
|
| 43 |
+
hidden_size: int,
|
| 44 |
+
num_layers: int = 4,
|
| 45 |
+
intermediate_size: Optional[int] = None,
|
| 46 |
+
dropout: float = 0.0,
|
| 47 |
+
):
|
| 48 |
+
super().__init__()
|
| 49 |
+
hidden_size = int(hidden_size)
|
| 50 |
+
num_layers = int(num_layers)
|
| 51 |
+
intermediate_size = int(intermediate_size or hidden_size * 4)
|
| 52 |
+
self.hidden_size = hidden_size
|
| 53 |
+
self.intermediate_size = intermediate_size
|
| 54 |
+
self.num_layers = num_layers
|
| 55 |
+
self.dropout = float(dropout)
|
| 56 |
+
self.layers = nn.ModuleList(
|
| 57 |
+
[
|
| 58 |
+
Qwen3AsrMlpTowerBlock(
|
| 59 |
+
hidden_size=hidden_size,
|
| 60 |
+
intermediate_size=intermediate_size,
|
| 61 |
+
dropout=dropout,
|
| 62 |
+
)
|
| 63 |
+
for _ in range(num_layers)
|
| 64 |
+
]
|
| 65 |
+
)
|
| 66 |
+
self.final_norm = nn.LayerNorm(hidden_size)
|
| 67 |
+
|
| 68 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 69 |
+
for layer in self.layers:
|
| 70 |
+
hidden_states = layer(hidden_states)
|
| 71 |
+
return self.final_norm(hidden_states)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class ArkasrForConditionalGeneration(PreTrainedModel, GenerationMixin):
|
| 75 |
+
config_class = ArkasrConfig
|
| 76 |
+
base_model_prefix = "language_model"
|
| 77 |
+
_no_split_modules = ["Qwen3ASRAudioEncoder"]
|
| 78 |
+
_tied_weights_keys = ["language_model.lm_head.weight", "language_model.model.embed_tokens.weight"]
|
| 79 |
+
|
| 80 |
+
def __init__(self, config: ArkasrConfig):
|
| 81 |
+
super().__init__(config)
|
| 82 |
+
self.audio_token_id = getattr(config, "audio_token_id", None)
|
| 83 |
+
if self.audio_token_id is None:
|
| 84 |
+
raise ValueError("`audio_token_id` must be defined in config.")
|
| 85 |
+
|
| 86 |
+
audio_config = getattr(config, "qwen3_asr_audio_config", None)
|
| 87 |
+
if isinstance(audio_config, dict):
|
| 88 |
+
audio_config = Qwen3ASRAudioEncoderConfig(**audio_config)
|
| 89 |
+
if audio_config is None:
|
| 90 |
+
raise ValueError("`qwen3_asr_audio_config` must be defined in config.")
|
| 91 |
+
|
| 92 |
+
self.language_model = Qwen2ForCausalLM(config)
|
| 93 |
+
self.audio_encoder = Qwen3ASRAudioEncoder(audio_config)
|
| 94 |
+
audio_dim = int(getattr(audio_config, "output_dim", 0) or 0)
|
| 95 |
+
if audio_dim <= 0:
|
| 96 |
+
raise ValueError("qwen3_asr_audio_config.output_dim must be positive.")
|
| 97 |
+
|
| 98 |
+
layers = int(getattr(config, "qwen3_asr_mlp_tower_layers", 4) or 4)
|
| 99 |
+
intermediate_size = int(getattr(config, "qwen3_asr_mlp_tower_hidden_size", 0) or audio_dim * 4)
|
| 100 |
+
dropout = float(getattr(config, "qwen3_asr_mlp_tower_dropout", 0.0) or 0.0)
|
| 101 |
+
self.audio_mlp_tower = Qwen3AsrMlpTower(
|
| 102 |
+
hidden_size=audio_dim,
|
| 103 |
+
num_layers=layers,
|
| 104 |
+
intermediate_size=intermediate_size,
|
| 105 |
+
dropout=dropout,
|
| 106 |
+
)
|
| 107 |
+
self.audio_projector = nn.Sequential(
|
| 108 |
+
nn.LayerNorm(audio_dim),
|
| 109 |
+
nn.Linear(audio_dim, int(config.hidden_size)),
|
| 110 |
+
)
|
| 111 |
+
self.all_tied_weights_keys = {}
|
| 112 |
+
|
| 113 |
+
def get_input_embeddings(self):
|
| 114 |
+
return self.language_model.get_input_embeddings()
|
| 115 |
+
|
| 116 |
+
def set_input_embeddings(self, value):
|
| 117 |
+
return self.language_model.set_input_embeddings(value)
|
| 118 |
+
|
| 119 |
+
def get_output_embeddings(self):
|
| 120 |
+
return self.language_model.get_output_embeddings()
|
| 121 |
+
|
| 122 |
+
def set_output_embeddings(self, new_embeddings):
|
| 123 |
+
return self.language_model.set_output_embeddings(new_embeddings)
|
| 124 |
+
|
| 125 |
+
def resize_token_embeddings(self, *args, **kwargs):
|
| 126 |
+
return self.language_model.resize_token_embeddings(*args, **kwargs)
|
| 127 |
+
|
| 128 |
+
@staticmethod
|
| 129 |
+
def _cache_seq_len(past_key_values) -> int:
|
| 130 |
+
if past_key_values is None:
|
| 131 |
+
return 0
|
| 132 |
+
if hasattr(past_key_values, "get_seq_length"):
|
| 133 |
+
try:
|
| 134 |
+
return int(past_key_values.get_seq_length())
|
| 135 |
+
except Exception:
|
| 136 |
+
return 0
|
| 137 |
+
try:
|
| 138 |
+
return int(past_key_values[0][0].shape[-2])
|
| 139 |
+
except Exception:
|
| 140 |
+
return 0
|
| 141 |
+
|
| 142 |
+
def _project_audio_row(
|
| 143 |
+
self,
|
| 144 |
+
input_features: torch.Tensor,
|
| 145 |
+
token_count: int,
|
| 146 |
+
dtype: torch.dtype,
|
| 147 |
+
feature_length: Optional[int] = None,
|
| 148 |
+
) -> torch.Tensor:
|
| 149 |
+
token_count = int(token_count)
|
| 150 |
+
if token_count <= 0:
|
| 151 |
+
return input_features.new_zeros((0, self.get_input_embeddings().embedding_dim), dtype=dtype)
|
| 152 |
+
if input_features.dim() == 3 and input_features.size(0) == 1:
|
| 153 |
+
input_features = input_features.squeeze(0)
|
| 154 |
+
if input_features.dim() != 2:
|
| 155 |
+
raise ValueError(f"Expected audio features with shape [mel, frames], got {tuple(input_features.shape)}")
|
| 156 |
+
|
| 157 |
+
expected_mels = int(getattr(self.audio_encoder.config, "num_mel_bins", input_features.size(0)))
|
| 158 |
+
if input_features.size(0) != expected_mels and input_features.size(1) == expected_mels:
|
| 159 |
+
input_features = input_features.transpose(0, 1)
|
| 160 |
+
if input_features.size(0) != expected_mels:
|
| 161 |
+
raise ValueError(f"Audio feature bins mismatch: expected {expected_mels}, got {input_features.size(0)}")
|
| 162 |
+
|
| 163 |
+
feature_length = int(feature_length) if feature_length is not None else int(input_features.size(1))
|
| 164 |
+
feature_length = max(1, min(feature_length, int(input_features.size(1))))
|
| 165 |
+
input_features = input_features[:, :feature_length]
|
| 166 |
+
|
| 167 |
+
encoder_param = next(self.audio_encoder.parameters())
|
| 168 |
+
encoded = self.audio_encoder(
|
| 169 |
+
input_features.to(device=encoder_param.device, dtype=encoder_param.dtype),
|
| 170 |
+
feature_lens=torch.tensor([feature_length], dtype=torch.long, device=encoder_param.device),
|
| 171 |
+
)
|
| 172 |
+
hidden = getattr(encoded, "last_hidden_state", encoded)
|
| 173 |
+
if isinstance(hidden, (tuple, list)):
|
| 174 |
+
hidden = hidden[0]
|
| 175 |
+
if hidden.dim() == 3 and hidden.size(0) == 1:
|
| 176 |
+
hidden = hidden.squeeze(0)
|
| 177 |
+
if hidden.dim() != 2:
|
| 178 |
+
raise ValueError(f"Expected audio encoder output [time, dim], got {tuple(hidden.shape)}")
|
| 179 |
+
|
| 180 |
+
tower_param = next(self.audio_mlp_tower.parameters())
|
| 181 |
+
hidden = self.audio_mlp_tower(hidden.to(device=tower_param.device, dtype=tower_param.dtype))
|
| 182 |
+
projector_param = next(self.audio_projector.parameters())
|
| 183 |
+
hidden = hidden.to(device=projector_param.device, dtype=projector_param.dtype)
|
| 184 |
+
if int(hidden.size(0)) != token_count:
|
| 185 |
+
hidden = F.adaptive_avg_pool1d(
|
| 186 |
+
hidden.transpose(0, 1).float().unsqueeze(0),
|
| 187 |
+
output_size=token_count,
|
| 188 |
+
).squeeze(0).transpose(0, 1).to(dtype=projector_param.dtype)
|
| 189 |
+
return self.audio_projector(hidden).to(dtype=dtype)
|
| 190 |
+
|
| 191 |
+
def _inject_audio_embeddings(
|
| 192 |
+
self,
|
| 193 |
+
input_ids: torch.Tensor,
|
| 194 |
+
input_features: Optional[torch.Tensor] = None,
|
| 195 |
+
feature_lens: Optional[torch.Tensor] = None,
|
| 196 |
+
audios: Optional[torch.Tensor] = None,
|
| 197 |
+
audio_feature_lengths: Optional[torch.Tensor] = None,
|
| 198 |
+
) -> torch.Tensor:
|
| 199 |
+
input_embeddings = self.get_input_embeddings()(input_ids).clone()
|
| 200 |
+
if input_features is None:
|
| 201 |
+
input_features = audios
|
| 202 |
+
if feature_lens is None:
|
| 203 |
+
feature_lens = audio_feature_lengths
|
| 204 |
+
if input_features is None:
|
| 205 |
+
return input_embeddings
|
| 206 |
+
|
| 207 |
+
input_features = input_features.to(device=input_embeddings.device)
|
| 208 |
+
if input_features.dim() == 4 and input_features.size(1) == 1:
|
| 209 |
+
input_features = input_features.squeeze(1)
|
| 210 |
+
if feature_lens is not None and not torch.is_tensor(feature_lens):
|
| 211 |
+
feature_lens = torch.tensor(feature_lens, dtype=torch.long, device=input_embeddings.device)
|
| 212 |
+
if torch.is_tensor(feature_lens):
|
| 213 |
+
feature_lens = feature_lens.to(device=input_embeddings.device)
|
| 214 |
+
|
| 215 |
+
audio_mask = input_ids.eq(self.audio_token_id)
|
| 216 |
+
for batch_i in range(int(input_ids.size(0))):
|
| 217 |
+
positions = torch.nonzero(audio_mask[batch_i], as_tuple=False).flatten()
|
| 218 |
+
if positions.numel() == 0:
|
| 219 |
+
continue
|
| 220 |
+
feature_length = None
|
| 221 |
+
if torch.is_tensor(feature_lens) and batch_i < int(feature_lens.numel()):
|
| 222 |
+
feature_length = int(feature_lens[batch_i].item())
|
| 223 |
+
projected = self._project_audio_row(
|
| 224 |
+
input_features[batch_i],
|
| 225 |
+
int(positions.numel()),
|
| 226 |
+
input_embeddings.dtype,
|
| 227 |
+
feature_length=feature_length,
|
| 228 |
+
)
|
| 229 |
+
input_embeddings[batch_i, positions, :] = projected.to(device=input_embeddings.device)
|
| 230 |
+
return input_embeddings
|
| 231 |
+
|
| 232 |
+
def forward(
|
| 233 |
+
self,
|
| 234 |
+
input_ids: Optional[torch.LongTensor] = None,
|
| 235 |
+
input_features: Optional[torch.Tensor] = None,
|
| 236 |
+
feature_lens: Optional[torch.Tensor] = None,
|
| 237 |
+
audios: Optional[torch.Tensor] = None,
|
| 238 |
+
audio_feature_lengths: Optional[torch.Tensor] = None,
|
| 239 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 240 |
+
position_ids: Optional[torch.Tensor] = None,
|
| 241 |
+
past_key_values: Optional[Any] = None,
|
| 242 |
+
inputs_embeds: Optional[torch.Tensor] = None,
|
| 243 |
+
use_cache: Optional[bool] = None,
|
| 244 |
+
labels: Optional[torch.LongTensor] = None,
|
| 245 |
+
output_attentions: Optional[bool] = None,
|
| 246 |
+
output_hidden_states: Optional[bool] = None,
|
| 247 |
+
logits_to_keep: int | torch.Tensor = 0,
|
| 248 |
+
**kwargs,
|
| 249 |
+
) -> CausalLMOutputWithPast:
|
| 250 |
+
if inputs_embeds is None:
|
| 251 |
+
if input_ids is None:
|
| 252 |
+
raise ValueError("Either `input_ids` or `inputs_embeds` must be provided.")
|
| 253 |
+
inputs_embeds = self.language_model.model.embed_tokens(input_ids)
|
| 254 |
+
|
| 255 |
+
past_len = self._cache_seq_len(past_key_values)
|
| 256 |
+
audio_inputs = input_features if input_features is not None else audios
|
| 257 |
+
audio_lengths = feature_lens if feature_lens is not None else audio_feature_lengths
|
| 258 |
+
if audio_inputs is not None and input_ids is not None and past_len == 0:
|
| 259 |
+
inputs_embeds = self._inject_audio_embeddings(
|
| 260 |
+
input_ids=input_ids,
|
| 261 |
+
input_features=audio_inputs,
|
| 262 |
+
feature_lens=audio_lengths,
|
| 263 |
+
)
|
| 264 |
+
|
| 265 |
+
outputs = self.language_model.model(
|
| 266 |
+
input_ids=None,
|
| 267 |
+
attention_mask=attention_mask,
|
| 268 |
+
position_ids=position_ids,
|
| 269 |
+
past_key_values=past_key_values,
|
| 270 |
+
inputs_embeds=inputs_embeds,
|
| 271 |
+
use_cache=use_cache,
|
| 272 |
+
output_attentions=output_attentions,
|
| 273 |
+
output_hidden_states=output_hidden_states,
|
| 274 |
+
)
|
| 275 |
+
hidden_states = outputs[0]
|
| 276 |
+
|
| 277 |
+
if isinstance(logits_to_keep, int) and logits_to_keep > 0:
|
| 278 |
+
hidden_for_logits = hidden_states[:, -logits_to_keep:, :]
|
| 279 |
+
elif isinstance(logits_to_keep, torch.Tensor):
|
| 280 |
+
hidden_for_logits = hidden_states[:, logits_to_keep, :]
|
| 281 |
+
else:
|
| 282 |
+
hidden_for_logits = hidden_states
|
| 283 |
+
logits = self.language_model.lm_head(hidden_for_logits)
|
| 284 |
+
|
| 285 |
+
loss = None
|
| 286 |
+
if labels is not None:
|
| 287 |
+
loss = self.language_model.loss_function(
|
| 288 |
+
logits=logits,
|
| 289 |
+
labels=labels,
|
| 290 |
+
vocab_size=self.config.vocab_size,
|
| 291 |
+
**kwargs,
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
return CausalLMOutputWithPast(
|
| 295 |
+
loss=loss,
|
| 296 |
+
logits=logits,
|
| 297 |
+
past_key_values=outputs.past_key_values,
|
| 298 |
+
hidden_states=outputs.hidden_states,
|
| 299 |
+
attentions=outputs.attentions,
|
| 300 |
+
)
|
| 301 |
+
|
| 302 |
+
def prepare_inputs_for_generation(
|
| 303 |
+
self,
|
| 304 |
+
input_ids,
|
| 305 |
+
past_key_values=None,
|
| 306 |
+
attention_mask=None,
|
| 307 |
+
inputs_embeds=None,
|
| 308 |
+
**kwargs,
|
| 309 |
+
):
|
| 310 |
+
past_len = self._cache_seq_len(past_key_values)
|
| 311 |
+
if past_len > 0:
|
| 312 |
+
input_ids = input_ids[:, -1:]
|
| 313 |
+
|
| 314 |
+
model_inputs = {
|
| 315 |
+
"input_ids": input_ids,
|
| 316 |
+
"past_key_values": past_key_values,
|
| 317 |
+
"use_cache": kwargs.get("use_cache"),
|
| 318 |
+
"attention_mask": attention_mask,
|
| 319 |
+
"input_features": kwargs.get("input_features", kwargs.get("audios", None)),
|
| 320 |
+
"feature_lens": kwargs.get("feature_lens", kwargs.get("audio_feature_lengths", None)),
|
| 321 |
+
}
|
| 322 |
+
if inputs_embeds is not None and past_key_values is None:
|
| 323 |
+
model_inputs["inputs_embeds"] = inputs_embeds
|
| 324 |
+
del model_inputs["input_ids"]
|
| 325 |
+
return model_inputs
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
__all__ = [
|
| 329 |
+
"ArkasrForConditionalGeneration",
|
| 330 |
+
"Qwen3AsrMlpTower",
|
| 331 |
+
"Qwen3AsrMlpTowerBlock",
|
| 332 |
+
]
|
preprocessor_config.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoProcessor": "processing_arkasr.ArkasrProcessor"
|
| 4 |
+
},
|
| 5 |
+
"chunk_length": 30,
|
| 6 |
+
"dither": 0.0,
|
| 7 |
+
"feature_extractor_type": "WhisperFeatureExtractor",
|
| 8 |
+
"feature_size": 128,
|
| 9 |
+
"hop_length": 160,
|
| 10 |
+
"n_fft": 400,
|
| 11 |
+
"n_samples": 480000,
|
| 12 |
+
"nb_max_frames": 3000,
|
| 13 |
+
"padding_side": "right",
|
| 14 |
+
"padding_value": 0.0,
|
| 15 |
+
"processor_class": "ArkasrProcessor",
|
| 16 |
+
"return_attention_mask": false,
|
| 17 |
+
"sampling_rate": 16000
|
| 18 |
+
}
|
processing_arkasr.py
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import base64
|
| 5 |
+
import io
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
from typing import Any, Dict, List, Optional, Union
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
import librosa
|
| 13 |
+
import soundfile as sf # Used for BytesIO fallback decoding.
|
| 14 |
+
|
| 15 |
+
from transformers import AutoTokenizer, WhisperFeatureExtractor
|
| 16 |
+
from transformers.feature_extraction_utils import BatchFeature
|
| 17 |
+
from transformers.processing_utils import ProcessorMixin
|
| 18 |
+
from transformers.utils import logging
|
| 19 |
+
|
| 20 |
+
logger = logging.get_logger(__name__)
|
| 21 |
+
|
| 22 |
+
_AUDIO_MARKER = "<<AUDIO_TOKENS>>"
|
| 23 |
+
|
| 24 |
+
def _normalize_dtype_name(name: str) -> str:
|
| 25 |
+
name = name.strip().lower()
|
| 26 |
+
alias = {
|
| 27 |
+
"fp16": "float16",
|
| 28 |
+
"float16": "float16",
|
| 29 |
+
"half": "float16",
|
| 30 |
+
"bf16": "bfloat16",
|
| 31 |
+
"bfloat16": "bfloat16",
|
| 32 |
+
"fp32": "float32",
|
| 33 |
+
"float32": "float32",
|
| 34 |
+
"float": "float32",
|
| 35 |
+
}
|
| 36 |
+
return alias.get(name, name)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _resolve_torch_dtype(x: Any, default: str = "float32") -> torch.dtype:
|
| 40 |
+
if isinstance(x, torch.dtype):
|
| 41 |
+
return x
|
| 42 |
+
if x is None:
|
| 43 |
+
x = default
|
| 44 |
+
if isinstance(x, str):
|
| 45 |
+
name = _normalize_dtype_name(x)
|
| 46 |
+
if not hasattr(torch, name):
|
| 47 |
+
raise ValueError(f"Unknown torch dtype string: {x} (normalized: {name})")
|
| 48 |
+
return getattr(torch, name)
|
| 49 |
+
raise TypeError(f"audio_dtype/audio_torch_dtype must be str or torch.dtype or None, got {type(x)}")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class ArkasrProcessor(ProcessorMixin):
|
| 53 |
+
attributes = ["feature_extractor", "tokenizer"]
|
| 54 |
+
valid_kwargs = ["merge_factor", "audio_token", "audio_dtype"]
|
| 55 |
+
feature_extractor_class = ("WhisperFeatureExtractor", "SequenceFeatureExtractor")
|
| 56 |
+
tokenizer_class = ("PreTrainedTokenizerFast", "PreTrainedTokenizer")
|
| 57 |
+
|
| 58 |
+
def __init__(
|
| 59 |
+
self,
|
| 60 |
+
feature_extractor,
|
| 61 |
+
tokenizer,
|
| 62 |
+
merge_factor: int = 4,
|
| 63 |
+
audio_token: str = "<|audio|>",
|
| 64 |
+
audio_dtype: str = "float32",
|
| 65 |
+
**kwargs,
|
| 66 |
+
):
|
| 67 |
+
super().__init__(feature_extractor, tokenizer)
|
| 68 |
+
self.merge_factor = int(merge_factor)
|
| 69 |
+
self.audio_token = str(audio_token)
|
| 70 |
+
self.audio_dtype = str(audio_dtype)
|
| 71 |
+
|
| 72 |
+
self.bos_audio_token = "<|begin_of_audio|>"
|
| 73 |
+
self.eos_audio_token = "<|end_of_audio|>"
|
| 74 |
+
self.user_token = "<|user|>"
|
| 75 |
+
self.assistant_token = "<|assistant|>"
|
| 76 |
+
self.assistant_end_token = "<|im_end|>"
|
| 77 |
+
|
| 78 |
+
@classmethod
|
| 79 |
+
def from_pretrained(cls, pretrained_model_name_or_path: str, **kwargs) -> "ArkasrProcessor":
|
| 80 |
+
trust_remote_code = bool(kwargs.pop("trust_remote_code", False))
|
| 81 |
+
passthrough_keys = {"cache_dir", "force_download", "local_files_only", "token", "revision", "subfolder"}
|
| 82 |
+
shared_kwargs = {k: kwargs[k] for k in list(kwargs.keys()) if k in passthrough_keys}
|
| 83 |
+
|
| 84 |
+
merge_factor = 4
|
| 85 |
+
audio_token = "<|audio|>"
|
| 86 |
+
audio_dtype = "float32"
|
| 87 |
+
tokenizer_cfg: Dict[str, Any] = {}
|
| 88 |
+
feat_cfg: Dict[str, Any] = {}
|
| 89 |
+
|
| 90 |
+
proc_cfg_path = os.path.join(pretrained_model_name_or_path, "processor_config.json")
|
| 91 |
+
if os.path.isfile(proc_cfg_path):
|
| 92 |
+
with open(proc_cfg_path, "r", encoding="utf-8") as f:
|
| 93 |
+
proc_cfg = json.load(f)
|
| 94 |
+
merge_factor = int(proc_cfg.get("merge_factor", merge_factor))
|
| 95 |
+
audio_token = str(proc_cfg.get("audio_token", audio_token))
|
| 96 |
+
audio_dtype = str(proc_cfg.get("audio_dtype", audio_dtype))
|
| 97 |
+
tokenizer_cfg = proc_cfg.get("tokenizer_config", {}) or {}
|
| 98 |
+
feat_cfg = proc_cfg.get("feature_extractor_config", {}) or {}
|
| 99 |
+
|
| 100 |
+
feature_extractor = WhisperFeatureExtractor.from_pretrained(pretrained_model_name_or_path, **shared_kwargs)
|
| 101 |
+
for k, v in feat_cfg.items():
|
| 102 |
+
if hasattr(feature_extractor, k):
|
| 103 |
+
try: setattr(feature_extractor, k, v)
|
| 104 |
+
except Exception: pass
|
| 105 |
+
|
| 106 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 107 |
+
pretrained_model_name_or_path, use_fast=True, trust_remote_code=trust_remote_code, **shared_kwargs
|
| 108 |
+
)
|
| 109 |
+
for k, v in tokenizer_cfg.items():
|
| 110 |
+
if hasattr(tokenizer, k):
|
| 111 |
+
try: setattr(tokenizer, k, v)
|
| 112 |
+
except Exception: pass
|
| 113 |
+
|
| 114 |
+
return cls(
|
| 115 |
+
feature_extractor=feature_extractor,
|
| 116 |
+
tokenizer=tokenizer,
|
| 117 |
+
merge_factor=merge_factor,
|
| 118 |
+
audio_token=audio_token,
|
| 119 |
+
audio_dtype=audio_dtype,
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
# =========================
|
| 123 |
+
# Audio helpers.
|
| 124 |
+
# =========================
|
| 125 |
+
def _load_audio_file(self, path: str, sampling_rate: int = 16000, offset: float = 0.0, duration: Optional[float] = None) -> np.ndarray:
|
| 126 |
+
# librosa supports offset and duration for file inputs.
|
| 127 |
+
audio_array, _ = librosa.load(path, sr=int(sampling_rate), mono=True, offset=offset, duration=duration)
|
| 128 |
+
return np.asarray(audio_array, dtype=np.float32)
|
| 129 |
+
|
| 130 |
+
def _strip_data_url_prefix(self, b64: str) -> str:
|
| 131 |
+
if "," in b64 and b64[:30].lower().startswith("data:"):
|
| 132 |
+
return b64.split(",", 1)[1]
|
| 133 |
+
return b64
|
| 134 |
+
|
| 135 |
+
def _load_audio_base64(self, b64: str, sampling_rate: int = 16000, offset: float = 0.0, duration: Optional[float] = None) -> np.ndarray:
|
| 136 |
+
b64 = self._strip_data_url_prefix(b64)
|
| 137 |
+
raw = base64.b64decode(b64)
|
| 138 |
+
bio = io.BytesIO(raw)
|
| 139 |
+
|
| 140 |
+
# librosa also supports offset and duration for BytesIO inputs.
|
| 141 |
+
try:
|
| 142 |
+
wav, _sr = librosa.load(bio, sr=int(sampling_rate), mono=True, offset=offset, duration=duration)
|
| 143 |
+
return np.asarray(wav, dtype=np.float32)
|
| 144 |
+
except Exception as e:
|
| 145 |
+
# Fallback path with manual slicing.
|
| 146 |
+
try:
|
| 147 |
+
bio.seek(0)
|
| 148 |
+
data, sr = sf.read(bio, dtype="float32", always_2d=True)
|
| 149 |
+
wav = data.mean(axis=1)
|
| 150 |
+
if int(sr) != int(sampling_rate):
|
| 151 |
+
wav = librosa.resample(wav, orig_sr=int(sr), target_sr=int(sampling_rate))
|
| 152 |
+
|
| 153 |
+
start_sample = int(offset * sampling_rate)
|
| 154 |
+
end_sample = None
|
| 155 |
+
if duration is not None:
|
| 156 |
+
end_sample = start_sample + int(duration * sampling_rate)
|
| 157 |
+
|
| 158 |
+
return np.asarray(wav[start_sample:end_sample], dtype=np.float32)
|
| 159 |
+
except Exception as e2:
|
| 160 |
+
raise ValueError("Failed to decode base64 audio.") from e2
|
| 161 |
+
|
| 162 |
+
def calculate_audio_token_count(self, mel_frames: int) -> int:
|
| 163 |
+
downsampled = (int(mel_frames) + 1) // 2
|
| 164 |
+
merged = downsampled // max(self.merge_factor, 1)
|
| 165 |
+
return max(int(merged), 1)
|
| 166 |
+
|
| 167 |
+
def _build_templates_and_audios(
|
| 168 |
+
self,
|
| 169 |
+
conversations: List[List[dict]],
|
| 170 |
+
sampling_rate: int,
|
| 171 |
+
add_generation_prompt: bool,
|
| 172 |
+
) -> tuple[List[str], List[np.ndarray], List[int]]:
|
| 173 |
+
prompts_template: List[str] = []
|
| 174 |
+
audios_raw: List[np.ndarray] = []
|
| 175 |
+
prompt_audio_counts: List[int] = []
|
| 176 |
+
|
| 177 |
+
for conv in conversations:
|
| 178 |
+
conv_str = ""
|
| 179 |
+
last_role = None
|
| 180 |
+
audio_count_this_conv = 0
|
| 181 |
+
|
| 182 |
+
for msg in conv:
|
| 183 |
+
role = msg["role"]
|
| 184 |
+
last_role = role
|
| 185 |
+
content = msg["content"]
|
| 186 |
+
|
| 187 |
+
if role == "user": conv_str += f"{self.user_token}"
|
| 188 |
+
elif role == "assistant": conv_str += f"{self.assistant_token}"
|
| 189 |
+
else: conv_str += f"<|{role}|>"
|
| 190 |
+
|
| 191 |
+
if isinstance(content, str):
|
| 192 |
+
conv_str += f"{content}"
|
| 193 |
+
elif isinstance(content, list):
|
| 194 |
+
for part in content:
|
| 195 |
+
ptype = part.get("type")
|
| 196 |
+
if ptype == "audio":
|
| 197 |
+
# Optional segment selection from begin_time/end_time.
|
| 198 |
+
begin_time = part.get("begin_time", -1)
|
| 199 |
+
end_time = part.get("end_time", -1)
|
| 200 |
+
|
| 201 |
+
offset = 0.0
|
| 202 |
+
duration = None
|
| 203 |
+
|
| 204 |
+
# Apply slicing only when begin_time is valid.
|
| 205 |
+
if begin_time is not None and begin_time >= 0:
|
| 206 |
+
offset = float(begin_time)
|
| 207 |
+
if end_time is not None and end_time > begin_time:
|
| 208 |
+
duration = float(end_time) - float(begin_time)
|
| 209 |
+
|
| 210 |
+
audio_raw_this = None
|
| 211 |
+
if "array" in part:
|
| 212 |
+
arr = part["array"]
|
| 213 |
+
if isinstance(arr, torch.Tensor):
|
| 214 |
+
arr = arr.detach().cpu().numpy()
|
| 215 |
+
full_arr = np.asarray(arr, dtype=np.float32).reshape(-1)
|
| 216 |
+
|
| 217 |
+
# Slice in-memory audio arrays as needed.
|
| 218 |
+
start_idx = int(offset * sampling_rate)
|
| 219 |
+
end_idx = None
|
| 220 |
+
if duration is not None:
|
| 221 |
+
end_idx = start_idx + int(duration * sampling_rate)
|
| 222 |
+
audio_raw_this = full_arr[start_idx:end_idx]
|
| 223 |
+
|
| 224 |
+
elif "path" in part:
|
| 225 |
+
audio_raw_this = self._load_audio_file(
|
| 226 |
+
part["path"],
|
| 227 |
+
sampling_rate=sampling_rate,
|
| 228 |
+
offset=offset,
|
| 229 |
+
duration=duration
|
| 230 |
+
)
|
| 231 |
+
elif "base64" in part:
|
| 232 |
+
audio_raw_this = self._load_audio_base64(
|
| 233 |
+
part["base64"],
|
| 234 |
+
sampling_rate=sampling_rate,
|
| 235 |
+
offset=offset,
|
| 236 |
+
duration=duration
|
| 237 |
+
)
|
| 238 |
+
else:
|
| 239 |
+
raise ValueError("Audio part must contain 'path' or 'array' or 'base64'.")
|
| 240 |
+
|
| 241 |
+
audios_raw.append(audio_raw_this)
|
| 242 |
+
audio_count_this_conv += 1
|
| 243 |
+
conv_str += f"{self.bos_audio_token}{_AUDIO_MARKER}{self.eos_audio_token}"
|
| 244 |
+
|
| 245 |
+
elif ptype == "text":
|
| 246 |
+
conv_str += f"{part.get('text', '')}"
|
| 247 |
+
else:
|
| 248 |
+
raise ValueError(f"Unknown content part type: {ptype}")
|
| 249 |
+
else:
|
| 250 |
+
raise ValueError(f"Unsupported message content type: {type(content)}")
|
| 251 |
+
|
| 252 |
+
if add_generation_prompt:
|
| 253 |
+
if last_role == "user": conv_str += f"{self.assistant_token}"
|
| 254 |
+
elif last_role == "assistant": conv_str += f"{self.assistant_end_token}"
|
| 255 |
+
else: conv_str += f"{self.assistant_token}"
|
| 256 |
+
|
| 257 |
+
prompts_template.append(conv_str)
|
| 258 |
+
prompt_audio_counts.append(audio_count_this_conv)
|
| 259 |
+
|
| 260 |
+
return prompts_template, audios_raw, prompt_audio_counts
|
| 261 |
+
|
| 262 |
+
def _calculate_audio_token_counts_per_sample(
|
| 263 |
+
self,
|
| 264 |
+
audios_raw: List[np.ndarray],
|
| 265 |
+
sampling_rate: int,
|
| 266 |
+
audio_max_length: Optional[int],
|
| 267 |
+
audio_pad_to_multiple_of: Optional[int],
|
| 268 |
+
) -> List[int]:
|
| 269 |
+
del sampling_rate, audio_pad_to_multiple_of
|
| 270 |
+
|
| 271 |
+
hop_length = int(getattr(self.feature_extractor, "hop_length", 160))
|
| 272 |
+
max_audio_samples = int(audio_max_length) if audio_max_length is not None else None
|
| 273 |
+
token_counts: List[int] = []
|
| 274 |
+
|
| 275 |
+
for audio_raw in audios_raw:
|
| 276 |
+
audio_np = np.asarray(audio_raw, dtype=np.float32).reshape(-1)
|
| 277 |
+
effective_len = int(audio_np.shape[0])
|
| 278 |
+
if max_audio_samples is not None:
|
| 279 |
+
effective_len = min(effective_len, max_audio_samples)
|
| 280 |
+
|
| 281 |
+
mel_frames = effective_len // max(hop_length, 1)
|
| 282 |
+
token_counts.append(self.calculate_audio_token_count(int(mel_frames)))
|
| 283 |
+
|
| 284 |
+
return token_counts
|
| 285 |
+
|
| 286 |
+
# =========================
|
| 287 |
+
# apply_chat_template
|
| 288 |
+
# =========================
|
| 289 |
+
def apply_chat_template(
|
| 290 |
+
self,
|
| 291 |
+
conversation: Union[List[dict], List[List[dict]]],
|
| 292 |
+
chat_template: Optional[str] = None,
|
| 293 |
+
add_generation_prompt: bool = True,
|
| 294 |
+
**kwargs,
|
| 295 |
+
) -> Union[BatchFeature, str, List[str]]:
|
| 296 |
+
if chat_template is not None:
|
| 297 |
+
logger.warning("chat_template argument is ignored.")
|
| 298 |
+
|
| 299 |
+
tokenize = kwargs.pop("tokenize", True)
|
| 300 |
+
return_tensors = kwargs.pop("return_tensors", "pt")
|
| 301 |
+
kwargs.pop("return_dict", None)
|
| 302 |
+
|
| 303 |
+
audio_torch_dtype = kwargs.pop("audio_torch_dtype", None)
|
| 304 |
+
audio_dtype_override = kwargs.pop("audio_dtype", None)
|
| 305 |
+
dtype_source = audio_torch_dtype if audio_torch_dtype is not None else audio_dtype_override
|
| 306 |
+
target_dtype = _resolve_torch_dtype(dtype_source, default=getattr(self, "audio_dtype", "float32"))
|
| 307 |
+
|
| 308 |
+
text_kwargs = dict(kwargs.pop("text_kwargs", {}) or {})
|
| 309 |
+
for k in ("padding", "truncation", "max_length", "add_special_tokens"):
|
| 310 |
+
if k in kwargs and k not in text_kwargs:
|
| 311 |
+
text_kwargs[k] = kwargs.pop(k)
|
| 312 |
+
|
| 313 |
+
sampling_rate = int(kwargs.pop("sampling_rate", 16000))
|
| 314 |
+
audio_padding = kwargs.pop("audio_padding", "longest")
|
| 315 |
+
audio_max_length = kwargs.pop("audio_max_length", None)
|
| 316 |
+
audio_pad_to_multiple_of = kwargs.pop("audio_pad_to_multiple_of", None)
|
| 317 |
+
|
| 318 |
+
if kwargs:
|
| 319 |
+
logger.warning(f"Ignored unused kwargs: {list(kwargs.keys())}")
|
| 320 |
+
|
| 321 |
+
if isinstance(conversation, list) and conversation and isinstance(conversation[0], dict):
|
| 322 |
+
conversations = [conversation]
|
| 323 |
+
is_single = True
|
| 324 |
+
else:
|
| 325 |
+
conversations = conversation
|
| 326 |
+
is_single = False
|
| 327 |
+
|
| 328 |
+
prompt_templates, audios_raw, prompt_audio_counts = self._build_templates_and_audios(
|
| 329 |
+
conversations=conversations,
|
| 330 |
+
sampling_rate=sampling_rate,
|
| 331 |
+
add_generation_prompt=add_generation_prompt,
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
input_features = None
|
| 335 |
+
audio_token_counts: List[int] = []
|
| 336 |
+
|
| 337 |
+
if len(audios_raw) > 0:
|
| 338 |
+
feat = self.feature_extractor(
|
| 339 |
+
audios_raw,
|
| 340 |
+
sampling_rate=sampling_rate,
|
| 341 |
+
return_tensors="np",
|
| 342 |
+
return_attention_mask=False,
|
| 343 |
+
padding=audio_padding,
|
| 344 |
+
max_length=audio_max_length,
|
| 345 |
+
pad_to_multiple_of=audio_pad_to_multiple_of,
|
| 346 |
+
)
|
| 347 |
+
input_features = feat["input_features"]
|
| 348 |
+
if not isinstance(input_features, np.ndarray):
|
| 349 |
+
input_features = np.asarray(input_features)
|
| 350 |
+
|
| 351 |
+
audio_token_counts = self._calculate_audio_token_counts_per_sample(
|
| 352 |
+
audios_raw=audios_raw,
|
| 353 |
+
sampling_rate=sampling_rate,
|
| 354 |
+
audio_max_length=audio_max_length,
|
| 355 |
+
audio_pad_to_multiple_of=audio_pad_to_multiple_of,
|
| 356 |
+
)
|
| 357 |
+
|
| 358 |
+
prompts: List[str] = []
|
| 359 |
+
audio_idx = 0
|
| 360 |
+
for prompt_template, audio_count in zip(prompt_templates, prompt_audio_counts):
|
| 361 |
+
prompt = prompt_template
|
| 362 |
+
for _ in range(audio_count):
|
| 363 |
+
if audio_idx >= len(audio_token_counts):
|
| 364 |
+
raise ValueError("Audio token count mismatch while building prompts.")
|
| 365 |
+
audio_tokens_str = "".join([self.audio_token] * audio_token_counts[audio_idx])
|
| 366 |
+
prompt = prompt.replace(_AUDIO_MARKER, audio_tokens_str, 1)
|
| 367 |
+
audio_idx += 1
|
| 368 |
+
if _AUDIO_MARKER in prompt:
|
| 369 |
+
raise ValueError("Unresolved audio marker remained in prompt.")
|
| 370 |
+
prompts.append(prompt)
|
| 371 |
+
|
| 372 |
+
if audio_idx != len(audio_token_counts):
|
| 373 |
+
raise ValueError("Unused audio token counts remained after prompt construction.")
|
| 374 |
+
|
| 375 |
+
if not tokenize:
|
| 376 |
+
return prompts[0] if is_single else prompts
|
| 377 |
+
|
| 378 |
+
text_kwargs.setdefault("padding", "longest")
|
| 379 |
+
text_kwargs.setdefault("add_special_tokens", False)
|
| 380 |
+
text_kwargs["return_tensors"] = return_tensors
|
| 381 |
+
|
| 382 |
+
enc = self.tokenizer(prompts, **text_kwargs)
|
| 383 |
+
data: Dict[str, Any] = dict(enc)
|
| 384 |
+
|
| 385 |
+
if input_features is not None:
|
| 386 |
+
audio_tensor = torch.tensor(input_features, dtype=target_dtype)
|
| 387 |
+
data["input_features"] = audio_tensor
|
| 388 |
+
data["audios"] = audio_tensor
|
| 389 |
+
|
| 390 |
+
return BatchFeature(data=data, tensor_type=return_tensors)
|
| 391 |
+
|
| 392 |
+
# Tokenizer proxy methods and direct audio-array path.
|
| 393 |
+
def batch_decode(self, *args, **kwargs):
|
| 394 |
+
return self.tokenizer.batch_decode(*args, **kwargs)
|
| 395 |
+
|
| 396 |
+
def decode(self, *args, **kwargs):
|
| 397 |
+
return self.tokenizer.decode(*args, **kwargs)
|
| 398 |
+
|
| 399 |
+
def __call__(
|
| 400 |
+
self,
|
| 401 |
+
text: Union[str, List[str]],
|
| 402 |
+
audios: Union[np.ndarray, torch.Tensor, List[Union[np.ndarray, torch.Tensor]]],
|
| 403 |
+
sampling_rate: int = 16000,
|
| 404 |
+
return_tensors: str = "pt",
|
| 405 |
+
**tokenizer_kwargs,
|
| 406 |
+
) -> BatchFeature:
|
| 407 |
+
# Direct audio-array path; segment slicing is handled by apply_chat_template.
|
| 408 |
+
audios_list = []
|
| 409 |
+
def flatten_audios(obj):
|
| 410 |
+
if isinstance(obj, (list, tuple)):
|
| 411 |
+
if len(obj) > 0 and isinstance(obj[0], (float, int)):
|
| 412 |
+
audios_list.append(obj)
|
| 413 |
+
else:
|
| 414 |
+
for item in obj: flatten_audios(item)
|
| 415 |
+
elif isinstance(obj, (np.ndarray, torch.Tensor)):
|
| 416 |
+
audios_list.append(obj)
|
| 417 |
+
flatten_audios(audios)
|
| 418 |
+
|
| 419 |
+
audios_np: List[np.ndarray] = []
|
| 420 |
+
for a in audios_list:
|
| 421 |
+
if isinstance(a, torch.Tensor): a = a.detach().cpu().numpy()
|
| 422 |
+
a = np.asarray(a, dtype=np.float32).reshape(-1)
|
| 423 |
+
audios_np.append(a)
|
| 424 |
+
|
| 425 |
+
input_features = None
|
| 426 |
+
if audios_np:
|
| 427 |
+
feat = self.feature_extractor(audios_np, sampling_rate=int(sampling_rate), return_tensors="np", return_attention_mask=False, padding="longest")
|
| 428 |
+
input_features = feat["input_features"]
|
| 429 |
+
if not isinstance(input_features, np.ndarray): input_features = np.asarray(input_features)
|
| 430 |
+
|
| 431 |
+
tokenizer_kwargs = dict(tokenizer_kwargs or {})
|
| 432 |
+
tokenizer_kwargs.setdefault("padding", "longest")
|
| 433 |
+
tokenizer_kwargs.setdefault("add_special_tokens", False)
|
| 434 |
+
tokenizer_kwargs["return_tensors"] = return_tensors
|
| 435 |
+
|
| 436 |
+
enc = self.tokenizer(text, **tokenizer_kwargs)
|
| 437 |
+
data: Dict[str, Any] = dict(enc)
|
| 438 |
+
if input_features is not None:
|
| 439 |
+
audio_tensor = torch.tensor(input_features, dtype=_resolve_torch_dtype(getattr(self, "audio_dtype", "float32")))
|
| 440 |
+
data["input_features"] = audio_tensor
|
| 441 |
+
data["audios"] = audio_tensor
|
| 442 |
+
return BatchFeature(data=data, tensor_type=return_tensors)
|
| 443 |
+
|
| 444 |
+
@property
|
| 445 |
+
def model_input_names(self):
|
| 446 |
+
return ["input_ids", "attention_mask", "input_features", "audios"]
|
processor_config.json
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_dtype": "bfloat16",
|
| 3 |
+
"audio_token": "<|audio|>",
|
| 4 |
+
"auto_map": {
|
| 5 |
+
"AutoProcessor": "processing_arkasr.ArkasrProcessor"
|
| 6 |
+
},
|
| 7 |
+
"merge_factor": 4,
|
| 8 |
+
"processor_class": "ArkasrProcessor"
|
| 9 |
+
}
|
qwen3_asr_audio_config.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2026 The Qwen team, Alibaba Group and the HuggingFace Inc. team. All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 5 |
+
# you may not use this file except in compliance with the License.
|
| 6 |
+
# You may obtain a copy of the License at
|
| 7 |
+
#
|
| 8 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 9 |
+
#
|
| 10 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 11 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 12 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 13 |
+
# See the License for the specific language governing permissions and
|
| 14 |
+
# limitations under the License.
|
| 15 |
+
from transformers.configuration_utils import PretrainedConfig
|
| 16 |
+
from transformers.utils import logging
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
logger = logging.get_logger(__name__)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class Qwen3ASRAudioEncoderConfig(PretrainedConfig):
|
| 23 |
+
r"""
|
| 24 |
+
This is the configuration class to store the configuration of a [`Qwen3ASRAudioEncoder`]. It is used to instantiate a
|
| 25 |
+
Qwen3-ASR audio encoder according to the specified arguments, defining the model architecture. Instantiating a
|
| 26 |
+
configuration with the defaults will yield a similar configuration to that of the audio encoder of the Qwen2-Audio
|
| 27 |
+
architecture.
|
| 28 |
+
|
| 29 |
+
e.g. [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
|
| 30 |
+
|
| 31 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 32 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 33 |
+
|
| 34 |
+
Args:
|
| 35 |
+
num_mel_bins (`int`, *optional*, defaults to 128):
|
| 36 |
+
Number of mel features used per input features. Should correspond to the value used in the
|
| 37 |
+
`Qwen3ASRProcessor` class.
|
| 38 |
+
encoder_layers (`int`, *optional*, defaults to 32):
|
| 39 |
+
Number of encoder layers.
|
| 40 |
+
encoder_attention_heads (`int`, *optional*, defaults to 20):
|
| 41 |
+
Number of attention heads for each attention layer in the Transformer encoder.
|
| 42 |
+
encoder_ffn_dim (`int`, *optional*, defaults to 5120):
|
| 43 |
+
Dimensionality of the "intermediate" (often named feed-forward) layer in encoder.
|
| 44 |
+
d_model (`int`, *optional*, defaults to 1280):
|
| 45 |
+
Dimensionality of the layers.
|
| 46 |
+
dropout (`float`, *optional*, defaults to 0.0):
|
| 47 |
+
The dropout probability for all fully connected layers in the embeddings, encoder, and pooler.
|
| 48 |
+
attention_dropout (`float`, *optional*, defaults to 0.0):
|
| 49 |
+
The dropout ratio for the attention probabilities.
|
| 50 |
+
activation_function (`str`, *optional*, defaults to `"gelu"`):
|
| 51 |
+
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
| 52 |
+
`"relu"`, `"silu"` and `"gelu_new"` are supported.
|
| 53 |
+
activation_dropout (`float`, *optional*, defaults to 0.0):
|
| 54 |
+
The dropout ratio for activations inside the fully connected layer.
|
| 55 |
+
scale_embedding (`bool`, *optional*, defaults to `False`):
|
| 56 |
+
Scale embeddings by diving by sqrt(d_model).
|
| 57 |
+
initializer_range (`float`, *optional*, defaults to 0.02):
|
| 58 |
+
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
| 59 |
+
max_source_positions (`int`, *optional*, defaults to 1500):
|
| 60 |
+
The maximum sequence length of log-mel filter-bank features that this model might ever be used with.
|
| 61 |
+
n_window (`int`, *optional*, defaults to 100):
|
| 62 |
+
The chunk for conv and flash attn in AudioEncoder.
|
| 63 |
+
output_dim (`int`, *optional*, defaults to 3584):
|
| 64 |
+
The output dimension of AudioEncoder.
|
| 65 |
+
|
| 66 |
+
Example:
|
| 67 |
+
|
| 68 |
+
```python
|
| 69 |
+
>>> from transformers import Qwen3ASRAudioEncoderConfig, Qwen3ASRAudioEncoder
|
| 70 |
+
|
| 71 |
+
>>> # Initializing a Qwen3ASRAudioEncoderConfig
|
| 72 |
+
>>> configuration = Qwen3ASRAudioEncoderConfig()
|
| 73 |
+
|
| 74 |
+
>>> # Initializing a Qwen3ASRAudioEncoder (with random weights)
|
| 75 |
+
>>> model = Qwen3ASRAudioEncoder(configuration)
|
| 76 |
+
|
| 77 |
+
>>> # Accessing the model configuration
|
| 78 |
+
>>> configuration = model.config
|
| 79 |
+
```"""
|
| 80 |
+
|
| 81 |
+
model_type = "qwen3_asr_audio_encoder"
|
| 82 |
+
|
| 83 |
+
def __init__(
|
| 84 |
+
self,
|
| 85 |
+
num_mel_bins=128,
|
| 86 |
+
encoder_layers=32,
|
| 87 |
+
encoder_attention_heads=20,
|
| 88 |
+
encoder_ffn_dim=5120,
|
| 89 |
+
d_model=1280,
|
| 90 |
+
dropout=0,
|
| 91 |
+
attention_dropout=0,
|
| 92 |
+
activation_function="gelu",
|
| 93 |
+
activation_dropout=0,
|
| 94 |
+
scale_embedding=False,
|
| 95 |
+
initializer_range=0.02,
|
| 96 |
+
max_source_positions=1500,
|
| 97 |
+
n_window=100,
|
| 98 |
+
output_dim=3584,
|
| 99 |
+
n_window_infer=400,
|
| 100 |
+
conv_chunksize=500,
|
| 101 |
+
downsample_hidden_size=480,
|
| 102 |
+
**kwargs,
|
| 103 |
+
):
|
| 104 |
+
super().__init__(**kwargs)
|
| 105 |
+
|
| 106 |
+
self.num_mel_bins = num_mel_bins
|
| 107 |
+
self.d_model = d_model
|
| 108 |
+
self.encoder_layers = encoder_layers
|
| 109 |
+
self.encoder_attention_heads = encoder_attention_heads
|
| 110 |
+
self.encoder_ffn_dim = encoder_ffn_dim
|
| 111 |
+
self.dropout = dropout
|
| 112 |
+
self.attention_dropout = attention_dropout
|
| 113 |
+
self.activation_function = activation_function
|
| 114 |
+
self.activation_dropout = activation_dropout
|
| 115 |
+
self.num_hidden_layers = encoder_layers
|
| 116 |
+
self.initializer_range = initializer_range
|
| 117 |
+
self.scale_embedding = scale_embedding # scale factor will be sqrt(d_model) if True
|
| 118 |
+
self.max_source_positions = max_source_positions
|
| 119 |
+
self.n_window = n_window
|
| 120 |
+
self.output_dim = output_dim
|
| 121 |
+
self.n_window_infer = n_window_infer
|
| 122 |
+
self.conv_chunksize = conv_chunksize
|
| 123 |
+
self.downsample_hidden_size = downsample_hidden_size
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class Qwen3ASRTextConfig(PretrainedConfig):
|
| 127 |
+
r"""
|
| 128 |
+
This is the configuration class to store the configuration of a [`Qwen3ASRTextModel`]. It is used to instantiate a
|
| 129 |
+
Qwen3-ASR model according to the specified arguments, defining the model architecture. Instantiating a configuration
|
| 130 |
+
with the defaults will yield a similar configuration to that of
|
| 131 |
+
Qwen3-ASR-1.7B [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
|
| 132 |
+
|
| 133 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 134 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 135 |
+
|
| 136 |
+
Args:
|
| 137 |
+
vocab_size (`int`, *optional*, defaults to 151936):
|
| 138 |
+
Vocabulary size of the Qwen3ASR model. Defines the number of different tokens that can be represented by the
|
| 139 |
+
`inputs_ids` passed when calling [`Qwen3ASRModel`]
|
| 140 |
+
hidden_size (`int`, *optional*, defaults to 4096):
|
| 141 |
+
Dimension of the hidden representations.
|
| 142 |
+
intermediate_size (`int`, *optional*, defaults to 22016):
|
| 143 |
+
Dimension of the MLP representations.
|
| 144 |
+
num_hidden_layers (`int`, *optional*, defaults to 32):
|
| 145 |
+
Number of hidden layers in the Transformer encoder.
|
| 146 |
+
num_attention_heads (`int`, *optional*, defaults to 32):
|
| 147 |
+
Number of attention heads for each attention layer in the Transformer encoder.
|
| 148 |
+
num_key_value_heads (`int`, *optional*, defaults to 32):
|
| 149 |
+
This is the number of key_value heads that should be used to implement Grouped Query Attention. If
|
| 150 |
+
`num_key_value_heads=num_attention_heads`, the model will use Multi Head Attention (MHA), if
|
| 151 |
+
`num_key_value_heads=1` the model will use Multi Query Attention (MQA) otherwise GQA is used. When
|
| 152 |
+
converting a multi-head checkpoint to a GQA checkpoint, each group key and value head should be constructed
|
| 153 |
+
by meanpooling all the original heads within that group. For more details, check out [this
|
| 154 |
+
paper](https://huggingface.co/papers/2305.13245). If it is not specified, will default to `32`.
|
| 155 |
+
head_dim (`int`, *optional*, defaults to 128):
|
| 156 |
+
The dimension of the head. If not specified, will default to `hidden_size // num_attention_heads`.
|
| 157 |
+
hidden_act (`str` or `function`, *optional*, defaults to `"silu"`):
|
| 158 |
+
The non-linear activation function (function or string) in the decoder.
|
| 159 |
+
max_position_embeddings (`int`, *optional*, defaults to 128000):
|
| 160 |
+
The maximum sequence length that this model might ever be used with.
|
| 161 |
+
initializer_range (`float`, *optional*, defaults to 0.02):
|
| 162 |
+
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
| 163 |
+
rms_norm_eps (`float`, *optional*, defaults to 1e-06):
|
| 164 |
+
The epsilon used by the rms normalization layers.
|
| 165 |
+
use_cache (`bool`, *optional*, defaults to `True`):
|
| 166 |
+
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
| 167 |
+
relevant if `config.is_decoder=True`.
|
| 168 |
+
tie_word_embeddings (`bool`, *optional*, defaults to `False`):
|
| 169 |
+
Whether the model's input and output word embeddings should be tied.
|
| 170 |
+
rope_theta (`float`, *optional*, defaults to 5000000.0):
|
| 171 |
+
The base period of the RoPE embeddings.
|
| 172 |
+
rope_scaling (`Dict`, *optional*):
|
| 173 |
+
Dictionary containing the scaling configuration for the RoPE embeddings. NOTE: if you apply new rope type
|
| 174 |
+
and you expect the model to work on longer `max_position_embeddings`, we recommend you to update this value
|
| 175 |
+
accordingly.
|
| 176 |
+
Expected contents:
|
| 177 |
+
`rope_type` (`str`):
|
| 178 |
+
The sub-variant of RoPE to use. Can be one of ['default', 'linear', 'dynamic', 'yarn', 'longrope',
|
| 179 |
+
'llama3'], with 'default' being the original RoPE implementation.
|
| 180 |
+
`factor` (`float`, *optional*):
|
| 181 |
+
Used with all rope types except 'default'. The scaling factor to apply to the RoPE embeddings. In
|
| 182 |
+
most scaling types, a `factor` of x will enable the model to handle sequences of length x *
|
| 183 |
+
original maximum pre-trained length.
|
| 184 |
+
`original_max_position_embeddings` (`int`, *optional*):
|
| 185 |
+
Used with 'dynamic', 'longrope' and 'llama3'. The original max position embeddings used during
|
| 186 |
+
pretraining.
|
| 187 |
+
`attention_factor` (`float`, *optional*):
|
| 188 |
+
Used with 'yarn' and 'longrope'. The scaling factor to be applied on the attention
|
| 189 |
+
computation. If unspecified, it defaults to value recommended by the implementation, using the
|
| 190 |
+
`factor` field to infer the suggested value.
|
| 191 |
+
`beta_fast` (`float`, *optional*):
|
| 192 |
+
Only used with 'yarn'. Parameter to set the boundary for extrapolation (only) in the linear
|
| 193 |
+
ramp function. If unspecified, it defaults to 32.
|
| 194 |
+
`beta_slow` (`float`, *optional*):
|
| 195 |
+
Only used with 'yarn'. Parameter to set the boundary for interpolation (only) in the linear
|
| 196 |
+
ramp function. If unspecified, it defaults to 1.
|
| 197 |
+
`short_factor` (`list[float]`, *optional*):
|
| 198 |
+
Only used with 'longrope'. The scaling factor to be applied to short contexts (<
|
| 199 |
+
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
| 200 |
+
size divided by the number of attention heads divided by 2
|
| 201 |
+
`long_factor` (`list[float]`, *optional*):
|
| 202 |
+
Only used with 'longrope'. The scaling factor to be applied to long contexts (<
|
| 203 |
+
`original_max_position_embeddings`). Must be a list of numbers with the same length as the hidden
|
| 204 |
+
size divided by the number of attention heads divided by 2
|
| 205 |
+
`low_freq_factor` (`float`, *optional*):
|
| 206 |
+
Only used with 'llama3'. Scaling factor applied to low frequency components of the RoPE
|
| 207 |
+
`high_freq_factor` (`float`, *optional*):
|
| 208 |
+
Only used with 'llama3'. Scaling factor applied to high frequency components of the RoPE
|
| 209 |
+
attention_bias (`bool`, defaults to `False`, *optional*, defaults to `False`):
|
| 210 |
+
Whether to use a bias in the query, key, value and output projection layers during self-attention.
|
| 211 |
+
attention_dropout (`float`, *optional*, defaults to 0.0):
|
| 212 |
+
The dropout ratio for the attention probabilities.
|
| 213 |
+
|
| 214 |
+
```python
|
| 215 |
+
>>> from transformers import Qwen3ASRTextModel, Qwen3ASRTextConfig
|
| 216 |
+
|
| 217 |
+
>>> # Initializing a Qwen3ASR style configuration
|
| 218 |
+
>>> configuration = Qwen3ASRTextConfig()
|
| 219 |
+
|
| 220 |
+
>>> # Initializing a model from the Qwen3-VL-7B style configuration
|
| 221 |
+
>>> model = Qwen3ASRTextModel(configuration)
|
| 222 |
+
|
| 223 |
+
>>> # Accessing the model configuration
|
| 224 |
+
>>> configuration = model.config
|
| 225 |
+
```"""
|
| 226 |
+
|
| 227 |
+
model_type = "qwen3_asr_text"
|
| 228 |
+
base_config_key = "text_config"
|
| 229 |
+
|
| 230 |
+
def __init__(
|
| 231 |
+
self,
|
| 232 |
+
vocab_size=151936,
|
| 233 |
+
hidden_size=4096,
|
| 234 |
+
intermediate_size=22016,
|
| 235 |
+
num_hidden_layers=32,
|
| 236 |
+
num_attention_heads=32,
|
| 237 |
+
num_key_value_heads=32,
|
| 238 |
+
head_dim=128,
|
| 239 |
+
hidden_act="silu",
|
| 240 |
+
max_position_embeddings=128000,
|
| 241 |
+
initializer_range=0.02,
|
| 242 |
+
rms_norm_eps=1e-6,
|
| 243 |
+
use_cache=True,
|
| 244 |
+
tie_word_embeddings=False,
|
| 245 |
+
rope_theta=5000000.0,
|
| 246 |
+
rope_scaling=None,
|
| 247 |
+
attention_bias=False,
|
| 248 |
+
attention_dropout=0.0,
|
| 249 |
+
**kwargs,
|
| 250 |
+
):
|
| 251 |
+
self.vocab_size = vocab_size
|
| 252 |
+
self.max_position_embeddings = max_position_embeddings
|
| 253 |
+
self.hidden_size = hidden_size
|
| 254 |
+
self.intermediate_size = intermediate_size
|
| 255 |
+
self.num_hidden_layers = num_hidden_layers
|
| 256 |
+
self.num_attention_heads = num_attention_heads
|
| 257 |
+
|
| 258 |
+
# for backward compatibility
|
| 259 |
+
if num_key_value_heads is None:
|
| 260 |
+
num_key_value_heads = num_attention_heads
|
| 261 |
+
|
| 262 |
+
self.num_key_value_heads = num_key_value_heads
|
| 263 |
+
self.head_dim = head_dim
|
| 264 |
+
self.hidden_act = hidden_act
|
| 265 |
+
self.initializer_range = initializer_range
|
| 266 |
+
self.rms_norm_eps = rms_norm_eps
|
| 267 |
+
self.use_cache = use_cache
|
| 268 |
+
self.rope_theta = rope_theta
|
| 269 |
+
self.rope_scaling = rope_scaling
|
| 270 |
+
self.attention_bias = attention_bias
|
| 271 |
+
self.attention_dropout = attention_dropout
|
| 272 |
+
# Validate the correctness of rotary position embeddings parameters
|
| 273 |
+
# BC: if there is a 'type' field, move it to 'rope_type'.
|
| 274 |
+
if self.rope_scaling is not None and "type" in self.rope_scaling:
|
| 275 |
+
self.rope_scaling["rope_type"] = self.rope_scaling["type"]
|
| 276 |
+
|
| 277 |
+
super().__init__(tie_word_embeddings=tie_word_embeddings, **kwargs)
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
class Qwen3ASRThinkerConfig(PretrainedConfig):
|
| 281 |
+
r"""
|
| 282 |
+
This is the configuration class to store the configuration of a [`Qwen3ASRThinker`]. It is used to instantiate a
|
| 283 |
+
Qwen3-ASR-Thinker model according to the specified arguments, defining the model architecture. Instantiating a
|
| 284 |
+
configuration with the defaults will yield a similar configuration to that of the thinker component of the Qwen3-Omni
|
| 285 |
+
architecture.
|
| 286 |
+
|
| 287 |
+
e.g. [Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B)
|
| 288 |
+
|
| 289 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 290 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
audio_config (`dict`, *optional*):
|
| 294 |
+
The config dictionary of the audio backbone.
|
| 295 |
+
text_config (`dict`, *optional*):
|
| 296 |
+
The config dictionary of the text backbone.
|
| 297 |
+
audio_token_id (`int`, *optional*, defaults to 151646):
|
| 298 |
+
The audio token id to encode the audio prompt.
|
| 299 |
+
audio_start_token_id (`int`, *optional*, defaults to 151647):
|
| 300 |
+
The audio start token id to encode the audio prompt.
|
| 301 |
+
user_token_id (`int`, *optional*, defaults to 872):
|
| 302 |
+
The user token id to encode the user token.
|
| 303 |
+
initializer_range (`float`, *optional*, defaults to 0.02):
|
| 304 |
+
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
| 305 |
+
|
| 306 |
+
Example:
|
| 307 |
+
|
| 308 |
+
```python
|
| 309 |
+
>>> from transformers import Qwen3ASRThinkerModel, Qwen3ASRThinkerConfig
|
| 310 |
+
|
| 311 |
+
>>> # Initializing a default Qwen3ASRThinkerConfig
|
| 312 |
+
>>> configuration = Qwen3ASRThinkerConfig()
|
| 313 |
+
|
| 314 |
+
>>> # Initializing a model (with random weights) from the default configuration
|
| 315 |
+
>>> model = Qwen3ASRThinkerModel(configuration)
|
| 316 |
+
|
| 317 |
+
>>> # Accessing the model configuration
|
| 318 |
+
>>> configuration = model.config
|
| 319 |
+
```"""
|
| 320 |
+
|
| 321 |
+
model_type = "qwen3_asr_thinker"
|
| 322 |
+
|
| 323 |
+
attribute_map = {}
|
| 324 |
+
sub_configs = {
|
| 325 |
+
"audio_config": Qwen3ASRAudioEncoderConfig,
|
| 326 |
+
"text_config": Qwen3ASRTextConfig,
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
def __init__(
|
| 330 |
+
self,
|
| 331 |
+
audio_config=None,
|
| 332 |
+
text_config=None,
|
| 333 |
+
audio_token_id=151646,
|
| 334 |
+
audio_start_token_id=151647,
|
| 335 |
+
user_token_id=872,
|
| 336 |
+
initializer_range=0.02,
|
| 337 |
+
**kwargs,
|
| 338 |
+
):
|
| 339 |
+
super().__init__(**kwargs)
|
| 340 |
+
self.user_token_id = user_token_id
|
| 341 |
+
self.audio_start_token_id = audio_start_token_id
|
| 342 |
+
self.initializer_range = initializer_range
|
| 343 |
+
|
| 344 |
+
if isinstance(audio_config, dict):
|
| 345 |
+
audio_config = Qwen3ASRAudioEncoderConfig(**audio_config)
|
| 346 |
+
elif audio_config is None:
|
| 347 |
+
audio_config = Qwen3ASRAudioEncoderConfig()
|
| 348 |
+
self.audio_config = audio_config
|
| 349 |
+
|
| 350 |
+
if isinstance(text_config, dict):
|
| 351 |
+
text_config = Qwen3ASRTextConfig(**text_config)
|
| 352 |
+
elif text_config is None:
|
| 353 |
+
text_config = Qwen3ASRTextConfig()
|
| 354 |
+
self.text_config = text_config
|
| 355 |
+
self.audio_token_id = audio_token_id
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
class Qwen3ASRConfig(PretrainedConfig):
|
| 359 |
+
"""
|
| 360 |
+
This is the configuration class to store the configuration of a [`Qwen3ASRForConditionalGeneration`]. It is used to instantiate a Qwen3ASR
|
| 361 |
+
model according to the specified sub-models configurations, defining the model architecture.
|
| 362 |
+
|
| 363 |
+
Instantiating a configuration with the defaults will yield a similar configuration to that of the
|
| 364 |
+
[Qwen/Qwen3-ASR-1.7B](https://huggingface.co/Qwen/Qwen3-ASR-1.7B) architecture.
|
| 365 |
+
|
| 366 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 367 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 368 |
+
|
| 369 |
+
Args:
|
| 370 |
+
thinker_config (`dict`, *optional*): Configuration of the underlying thinker sub-model.
|
| 371 |
+
support_languages (`List[str]`, *optional*): The languages supported by the model.
|
| 372 |
+
|
| 373 |
+
Example:
|
| 374 |
+
|
| 375 |
+
```python
|
| 376 |
+
>>> from transformers import (
|
| 377 |
+
... Qwen3ASRThinkerConfig,
|
| 378 |
+
... Qwen3ASRForConditionalGeneration,
|
| 379 |
+
... Qwen3ASRConfig,
|
| 380 |
+
... )
|
| 381 |
+
|
| 382 |
+
>>> # Initializing a Qwen3ASR style configuration
|
| 383 |
+
>>> configuration = Qwen3ASRConfig()
|
| 384 |
+
|
| 385 |
+
>>> # Initializing a model from the configuration
|
| 386 |
+
>>> model = Qwen3ASRForConditionalGeneration(configuration)
|
| 387 |
+
|
| 388 |
+
>>> # Accessing the model configuration
|
| 389 |
+
>>> configuration = model.config
|
| 390 |
+
```"""
|
| 391 |
+
|
| 392 |
+
model_type = "qwen3_asr"
|
| 393 |
+
sub_configs = {
|
| 394 |
+
"thinker_config": Qwen3ASRThinkerConfig,
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
def __init__(
|
| 398 |
+
self,
|
| 399 |
+
thinker_config=None,
|
| 400 |
+
support_languages=None,
|
| 401 |
+
**kwargs,
|
| 402 |
+
):
|
| 403 |
+
super().__init__(**kwargs)
|
| 404 |
+
if thinker_config is None:
|
| 405 |
+
thinker_config = {}
|
| 406 |
+
|
| 407 |
+
self.thinker_config = Qwen3ASRThinkerConfig(**thinker_config)
|
| 408 |
+
self.support_languages = support_languages
|
| 409 |
+
|
| 410 |
+
def get_text_config(self, decoder=False) -> "PretrainedConfig":
|
| 411 |
+
"""
|
| 412 |
+
Returns the config that is meant to be used with text IO. On most models, it is the original config instance
|
| 413 |
+
itself. On specific composite models, it is under a set of valid names.
|
| 414 |
+
|
| 415 |
+
Args:
|
| 416 |
+
decoder (`Optional[bool]`, *optional*, defaults to `False`):
|
| 417 |
+
If set to `True`, then only search for decoder config names.
|
| 418 |
+
"""
|
| 419 |
+
# Overridden for deeply nested config like Qwen2.5-Omni. We don't have any omni model
|
| 420 |
+
# except for Qwen yet. This has to be generalized if more deeply nested configs are
|
| 421 |
+
# added. NOTE: currently method used only by vLLM
|
| 422 |
+
return self.thinker_config.get_text_config()
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
__all__ = ["Qwen3ASRConfig", "Qwen3ASRThinkerConfig", "Qwen3ASRAudioEncoderConfig"]
|
qwen3_asr_audio_model.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import math
|
| 4 |
+
from typing import Optional
|
| 5 |
+
|
| 6 |
+
import numpy as np
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
from torch import nn
|
| 10 |
+
from transformers.activations import ACT2FN
|
| 11 |
+
from transformers.modeling_outputs import BaseModelOutput
|
| 12 |
+
from transformers.modeling_utils import PreTrainedModel
|
| 13 |
+
|
| 14 |
+
from .qwen3_asr_audio_config import Qwen3ASRAudioEncoderConfig
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def _get_feat_extract_output_lengths(input_lengths: torch.Tensor) -> torch.Tensor:
|
| 18 |
+
input_lengths = torch.clamp(input_lengths.long(), min=1)
|
| 19 |
+
input_lengths_leave = input_lengths % 100
|
| 20 |
+
feat_lengths = (input_lengths_leave - 1) // 2 + 1
|
| 21 |
+
return ((feat_lengths - 1) // 2 + 1 - 1) // 2 + 1 + (input_lengths // 100) * 13
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _block_diagonal_attention_mask(hidden_states: torch.Tensor, cu_seqlens: torch.Tensor) -> torch.Tensor:
|
| 25 |
+
seq_length = int(hidden_states.shape[0])
|
| 26 |
+
mask = torch.full(
|
| 27 |
+
(1, 1, seq_length, seq_length),
|
| 28 |
+
torch.finfo(hidden_states.dtype).min,
|
| 29 |
+
dtype=hidden_states.dtype,
|
| 30 |
+
device=hidden_states.device,
|
| 31 |
+
)
|
| 32 |
+
for i in range(1, int(cu_seqlens.numel())):
|
| 33 |
+
start = int(cu_seqlens[i - 1].item())
|
| 34 |
+
end = int(cu_seqlens[i].item())
|
| 35 |
+
mask[..., start:end, start:end] = 0
|
| 36 |
+
return mask
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class Qwen3ASRAudioAttention(nn.Module):
|
| 40 |
+
def __init__(self, config: Qwen3ASRAudioEncoderConfig):
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.embed_dim = int(config.d_model)
|
| 43 |
+
self.num_heads = int(config.encoder_attention_heads)
|
| 44 |
+
self.head_dim = self.embed_dim // self.num_heads
|
| 45 |
+
if self.head_dim * self.num_heads != self.embed_dim:
|
| 46 |
+
raise ValueError("d_model must be divisible by encoder_attention_heads")
|
| 47 |
+
self.scaling = self.head_dim**-0.5
|
| 48 |
+
self.attention_dropout = float(config.attention_dropout)
|
| 49 |
+
self.k_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
|
| 50 |
+
self.v_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
|
| 51 |
+
self.q_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
|
| 52 |
+
self.out_proj = nn.Linear(self.embed_dim, self.embed_dim, bias=True)
|
| 53 |
+
|
| 54 |
+
def forward(
|
| 55 |
+
self,
|
| 56 |
+
hidden_states: torch.Tensor,
|
| 57 |
+
cu_seqlens: torch.Tensor,
|
| 58 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 59 |
+
) -> torch.Tensor:
|
| 60 |
+
del attention_mask
|
| 61 |
+
seq_length = int(hidden_states.size(0))
|
| 62 |
+
query = self.q_proj(hidden_states).reshape(seq_length, self.num_heads, self.head_dim).transpose(0, 1)
|
| 63 |
+
key = self.k_proj(hidden_states).reshape(seq_length, self.num_heads, self.head_dim).transpose(0, 1)
|
| 64 |
+
value = self.v_proj(hidden_states).reshape(seq_length, self.num_heads, self.head_dim).transpose(0, 1)
|
| 65 |
+
query = query.unsqueeze(0)
|
| 66 |
+
key = key.unsqueeze(0)
|
| 67 |
+
value = value.unsqueeze(0)
|
| 68 |
+
mask = _block_diagonal_attention_mask(hidden_states, cu_seqlens)
|
| 69 |
+
attn_weights = torch.matmul(query, key.transpose(2, 3)) * self.scaling
|
| 70 |
+
attn_weights = attn_weights + mask
|
| 71 |
+
attn_weights = F.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
|
| 72 |
+
attn_weights = F.dropout(attn_weights, p=self.attention_dropout, training=self.training)
|
| 73 |
+
attn_output = torch.matmul(attn_weights, value)
|
| 74 |
+
attn_output = attn_output.reshape(seq_length, self.embed_dim).contiguous()
|
| 75 |
+
return self.out_proj(attn_output)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
class Qwen3ASRAudioEncoderLayer(nn.Module):
|
| 79 |
+
def __init__(self, config: Qwen3ASRAudioEncoderConfig):
|
| 80 |
+
super().__init__()
|
| 81 |
+
self.embed_dim = int(config.d_model)
|
| 82 |
+
self.self_attn = Qwen3ASRAudioAttention(config)
|
| 83 |
+
self.self_attn_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 84 |
+
self.activation_fn = ACT2FN[config.activation_function]
|
| 85 |
+
self.fc1 = nn.Linear(self.embed_dim, int(config.encoder_ffn_dim))
|
| 86 |
+
self.fc2 = nn.Linear(int(config.encoder_ffn_dim), self.embed_dim)
|
| 87 |
+
self.final_layer_norm = nn.LayerNorm(self.embed_dim)
|
| 88 |
+
|
| 89 |
+
def forward(self, hidden_states: torch.Tensor, cu_seqlens: torch.Tensor) -> tuple[torch.Tensor]:
|
| 90 |
+
residual = hidden_states
|
| 91 |
+
hidden_states = self.self_attn_layer_norm(hidden_states)
|
| 92 |
+
hidden_states = self.self_attn(hidden_states=hidden_states, cu_seqlens=cu_seqlens)
|
| 93 |
+
hidden_states = residual + hidden_states
|
| 94 |
+
residual = hidden_states
|
| 95 |
+
hidden_states = self.final_layer_norm(hidden_states)
|
| 96 |
+
hidden_states = self.fc1(hidden_states)
|
| 97 |
+
hidden_states = self.activation_fn(hidden_states)
|
| 98 |
+
hidden_states = self.fc2(hidden_states)
|
| 99 |
+
hidden_states = residual + hidden_states
|
| 100 |
+
if hidden_states.dtype == torch.float16:
|
| 101 |
+
clamp_value = torch.finfo(hidden_states.dtype).max - 1000
|
| 102 |
+
hidden_states = torch.clamp(hidden_states, min=-clamp_value, max=clamp_value)
|
| 103 |
+
return (hidden_states,)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
class SinusoidsPositionEmbedding(nn.Module):
|
| 107 |
+
def __init__(self, length: int, channels: int, max_timescale: int = 10000):
|
| 108 |
+
super().__init__()
|
| 109 |
+
if channels % 2 != 0:
|
| 110 |
+
raise ValueError("SinusoidsPositionEmbedding requires an even channel count")
|
| 111 |
+
log_timescale_increment = np.log(max_timescale) / (channels // 2 - 1)
|
| 112 |
+
inv_timescales = torch.exp(-log_timescale_increment * torch.arange(channels // 2).float())
|
| 113 |
+
scaled_time = torch.arange(length)[:, np.newaxis] * inv_timescales[np.newaxis, :]
|
| 114 |
+
self.register_buffer(
|
| 115 |
+
"positional_embedding",
|
| 116 |
+
torch.cat([torch.sin(scaled_time), torch.cos(scaled_time)], dim=1),
|
| 117 |
+
persistent=False,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
def forward(self, seqlen: int):
|
| 121 |
+
return self.positional_embedding[:seqlen, :]
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class Qwen3ASRAudioEncoder(PreTrainedModel):
|
| 125 |
+
config_class = Qwen3ASRAudioEncoderConfig
|
| 126 |
+
main_input_name = "input_features"
|
| 127 |
+
_no_split_modules = ["Qwen3ASRAudioEncoderLayer"]
|
| 128 |
+
|
| 129 |
+
def __init__(self, config: Qwen3ASRAudioEncoderConfig):
|
| 130 |
+
super().__init__(config)
|
| 131 |
+
embed_dim = int(config.d_model)
|
| 132 |
+
self.dropout = float(config.dropout)
|
| 133 |
+
self.num_mel_bins = int(config.num_mel_bins)
|
| 134 |
+
self.max_source_positions = int(config.max_source_positions)
|
| 135 |
+
self.embed_scale = math.sqrt(embed_dim) if bool(config.scale_embedding) else 1.0
|
| 136 |
+
self.n_window = int(config.n_window)
|
| 137 |
+
self.positional_embedding = SinusoidsPositionEmbedding(self.max_source_positions, embed_dim)
|
| 138 |
+
self.layers = nn.ModuleList([Qwen3ASRAudioEncoderLayer(config) for _ in range(int(config.encoder_layers))])
|
| 139 |
+
self.ln_post = nn.LayerNorm(embed_dim)
|
| 140 |
+
self.gradient_checkpointing = False
|
| 141 |
+
self.conv2d1 = nn.Conv2d(1, int(config.downsample_hidden_size), 3, 2, padding=1)
|
| 142 |
+
self.conv2d2 = nn.Conv2d(int(config.downsample_hidden_size), int(config.downsample_hidden_size), 3, 2, padding=1)
|
| 143 |
+
self.conv2d3 = nn.Conv2d(int(config.downsample_hidden_size), int(config.downsample_hidden_size), 3, 2, padding=1)
|
| 144 |
+
conv_freq = ((((int(config.num_mel_bins) + 1) // 2 + 1) // 2 + 1) // 2)
|
| 145 |
+
self.conv_out = nn.Linear(int(config.downsample_hidden_size) * conv_freq, embed_dim, bias=False)
|
| 146 |
+
self.proj1 = nn.Linear(embed_dim, embed_dim)
|
| 147 |
+
self.act = ACT2FN[config.activation_function]
|
| 148 |
+
self.proj2 = nn.Linear(embed_dim, int(config.output_dim))
|
| 149 |
+
self.n_window_infer = int(config.n_window_infer)
|
| 150 |
+
self.conv_chunksize = int(config.conv_chunksize)
|
| 151 |
+
self.post_init()
|
| 152 |
+
|
| 153 |
+
def _freeze_parameters(self):
|
| 154 |
+
for param in self.parameters():
|
| 155 |
+
param.requires_grad = False
|
| 156 |
+
self._requires_grad = False
|
| 157 |
+
|
| 158 |
+
def _prepare_attention_mask(self, inputs_tensor: torch.Tensor, cu_seqlens: torch.Tensor) -> torch.Tensor:
|
| 159 |
+
return _block_diagonal_attention_mask(inputs_tensor, cu_seqlens)
|
| 160 |
+
|
| 161 |
+
def forward(
|
| 162 |
+
self,
|
| 163 |
+
input_features: torch.Tensor,
|
| 164 |
+
feature_lens: Optional[torch.Tensor] = None,
|
| 165 |
+
aftercnn_lens: Optional[torch.Tensor] = None,
|
| 166 |
+
):
|
| 167 |
+
if feature_lens is None:
|
| 168 |
+
feature_lens = torch.tensor([input_features.shape[-1]], dtype=torch.long, device=input_features.device)
|
| 169 |
+
feature_lens = feature_lens.to(device=input_features.device, dtype=torch.long)
|
| 170 |
+
if aftercnn_lens is None:
|
| 171 |
+
aftercnn_lens = _get_feat_extract_output_lengths(feature_lens)
|
| 172 |
+
aftercnn_lens = aftercnn_lens.to(device=input_features.device, dtype=torch.long)
|
| 173 |
+
|
| 174 |
+
chunk_num = torch.ceil(feature_lens / (self.n_window * 2)).long()
|
| 175 |
+
chunk_lengths = torch.tensor(
|
| 176 |
+
[self.n_window * 2] * int(chunk_num.sum().item()),
|
| 177 |
+
dtype=torch.long,
|
| 178 |
+
device=feature_lens.device,
|
| 179 |
+
)
|
| 180 |
+
tail_chunk_index = F.pad(chunk_num, (1, 0), value=-1).cumsum(0)[1:]
|
| 181 |
+
chunk_lengths[tail_chunk_index] = feature_lens % (self.n_window * 2)
|
| 182 |
+
chunk_lengths[chunk_lengths == 0] = self.n_window * 2
|
| 183 |
+
|
| 184 |
+
chunk_list = input_features.T.split(chunk_lengths.tolist(), dim=0)
|
| 185 |
+
padded_feature = nn.utils.rnn.pad_sequence(chunk_list, batch_first=True).transpose(1, 2)
|
| 186 |
+
feature_lens_after_cnn = _get_feat_extract_output_lengths(chunk_lengths)
|
| 187 |
+
padded_mask_after_cnn = nn.utils.rnn.pad_sequence(
|
| 188 |
+
[torch.ones(int(length.item()), dtype=torch.bool, device=padded_feature.device) for length in feature_lens_after_cnn],
|
| 189 |
+
batch_first=True,
|
| 190 |
+
)
|
| 191 |
+
padded_feature = padded_feature.unsqueeze(1)
|
| 192 |
+
padded_embeds = []
|
| 193 |
+
for chunk in padded_feature.split(self.conv_chunksize, dim=0):
|
| 194 |
+
padded_embed = F.gelu(self.conv2d1(chunk))
|
| 195 |
+
padded_embed = F.gelu(self.conv2d2(padded_embed))
|
| 196 |
+
padded_embed = F.gelu(self.conv2d3(padded_embed))
|
| 197 |
+
padded_embeds.append(padded_embed)
|
| 198 |
+
padded_embed = torch.cat(padded_embeds, dim=0)
|
| 199 |
+
bsz, channels, freq, time = padded_embed.size()
|
| 200 |
+
padded_embed = self.conv_out(padded_embed.permute(0, 3, 1, 2).contiguous().view(bsz, time, channels * freq))
|
| 201 |
+
|
| 202 |
+
positional_embedding = (
|
| 203 |
+
self.positional_embedding.positional_embedding[: padded_embed.shape[1], :]
|
| 204 |
+
.unsqueeze(0)
|
| 205 |
+
.to(padded_embed.dtype)
|
| 206 |
+
)
|
| 207 |
+
padded_embed = padded_embed + positional_embedding
|
| 208 |
+
hidden_states = padded_embed[padded_mask_after_cnn]
|
| 209 |
+
cu_chunk_lens = [0]
|
| 210 |
+
window_aftercnn = padded_mask_after_cnn.shape[-1] * (self.n_window_infer // (self.n_window * 2))
|
| 211 |
+
for cnn_len in aftercnn_lens:
|
| 212 |
+
cnn_len_int = int(cnn_len.item())
|
| 213 |
+
cu_chunk_lens += [window_aftercnn] * (cnn_len_int // window_aftercnn)
|
| 214 |
+
remainder = cnn_len_int % window_aftercnn
|
| 215 |
+
if remainder != 0:
|
| 216 |
+
cu_chunk_lens += [remainder]
|
| 217 |
+
cu_seqlens = torch.tensor(cu_chunk_lens, device=aftercnn_lens.device).cumsum(-1, dtype=torch.int32)
|
| 218 |
+
|
| 219 |
+
for encoder_layer in self.layers:
|
| 220 |
+
hidden_states = encoder_layer(hidden_states, cu_seqlens)[0]
|
| 221 |
+
|
| 222 |
+
hidden_states = self.ln_post(hidden_states)
|
| 223 |
+
hidden_states = self.proj1(hidden_states)
|
| 224 |
+
hidden_states = self.act(hidden_states)
|
| 225 |
+
hidden_states = self.proj2(hidden_states)
|
| 226 |
+
return BaseModelOutput(last_hidden_state=hidden_states)
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
__all__ = ["Qwen3ASRAudioEncoder", "Qwen3ASRAudioEncoderLayer", "Qwen3ASRAudioAttention"]
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<|user|>",
|
| 4 |
+
"<|begin_of_audio|>",
|
| 5 |
+
"<|end_of_audio|>",
|
| 6 |
+
"<|assistant|>",
|
| 7 |
+
"<|system|>"
|
| 8 |
+
],
|
| 9 |
+
"eos_token": {
|
| 10 |
+
"content": "<|im_end|>",
|
| 11 |
+
"lstrip": false,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "<|endoftext|>",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
}
|
| 23 |
+
}
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:64ecaabc85a9272a8099f8aa7c097ce3a4c65a79bafbfd1450add736938ae45b
|
| 3 |
+
size 11419524
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"added_tokens_decoder": {
|
| 4 |
+
"151643": {
|
| 5 |
+
"content": "<|endoftext|>",
|
| 6 |
+
"lstrip": false,
|
| 7 |
+
"normalized": false,
|
| 8 |
+
"rstrip": false,
|
| 9 |
+
"single_word": false,
|
| 10 |
+
"special": true
|
| 11 |
+
},
|
| 12 |
+
"151644": {
|
| 13 |
+
"content": "<|im_start|>",
|
| 14 |
+
"lstrip": false,
|
| 15 |
+
"normalized": false,
|
| 16 |
+
"rstrip": false,
|
| 17 |
+
"single_word": false,
|
| 18 |
+
"special": true
|
| 19 |
+
},
|
| 20 |
+
"151645": {
|
| 21 |
+
"content": "<|im_end|>",
|
| 22 |
+
"lstrip": false,
|
| 23 |
+
"normalized": false,
|
| 24 |
+
"rstrip": false,
|
| 25 |
+
"single_word": false,
|
| 26 |
+
"special": true
|
| 27 |
+
},
|
| 28 |
+
"151646": {
|
| 29 |
+
"content": "<|audio|>",
|
| 30 |
+
"lstrip": false,
|
| 31 |
+
"normalized": true,
|
| 32 |
+
"rstrip": false,
|
| 33 |
+
"single_word": false,
|
| 34 |
+
"special": false
|
| 35 |
+
},
|
| 36 |
+
"151647": {
|
| 37 |
+
"content": "<|user|>",
|
| 38 |
+
"lstrip": false,
|
| 39 |
+
"normalized": false,
|
| 40 |
+
"rstrip": false,
|
| 41 |
+
"single_word": false,
|
| 42 |
+
"special": true
|
| 43 |
+
},
|
| 44 |
+
"151648": {
|
| 45 |
+
"content": "<|begin_of_audio|>",
|
| 46 |
+
"lstrip": false,
|
| 47 |
+
"normalized": false,
|
| 48 |
+
"rstrip": false,
|
| 49 |
+
"single_word": false,
|
| 50 |
+
"special": true
|
| 51 |
+
},
|
| 52 |
+
"151649": {
|
| 53 |
+
"content": "<|end_of_audio|>",
|
| 54 |
+
"lstrip": false,
|
| 55 |
+
"normalized": false,
|
| 56 |
+
"rstrip": false,
|
| 57 |
+
"single_word": false,
|
| 58 |
+
"special": true
|
| 59 |
+
},
|
| 60 |
+
"151650": {
|
| 61 |
+
"content": "<|assistant|>",
|
| 62 |
+
"lstrip": false,
|
| 63 |
+
"normalized": false,
|
| 64 |
+
"rstrip": false,
|
| 65 |
+
"single_word": false,
|
| 66 |
+
"special": true
|
| 67 |
+
},
|
| 68 |
+
"151651": {
|
| 69 |
+
"content": "<|system|>",
|
| 70 |
+
"lstrip": false,
|
| 71 |
+
"normalized": false,
|
| 72 |
+
"rstrip": false,
|
| 73 |
+
"single_word": false,
|
| 74 |
+
"special": true
|
| 75 |
+
}
|
| 76 |
+
},
|
| 77 |
+
"additional_special_tokens": [
|
| 78 |
+
"<|user|>",
|
| 79 |
+
"<|begin_of_audio|>",
|
| 80 |
+
"<|end_of_audio|>",
|
| 81 |
+
"<|assistant|>",
|
| 82 |
+
"<|system|>"
|
| 83 |
+
],
|
| 84 |
+
"auto_map": {
|
| 85 |
+
"AutoProcessor": "processing_arkasr.ArkasrProcessor"
|
| 86 |
+
},
|
| 87 |
+
"bos_token": null,
|
| 88 |
+
"clean_up_tokenization_spaces": false,
|
| 89 |
+
"eos_token": "<|im_end|>",
|
| 90 |
+
"errors": "replace",
|
| 91 |
+
"extra_special_tokens": {},
|
| 92 |
+
"model_max_length": 32768,
|
| 93 |
+
"pad_token": "<|endoftext|>",
|
| 94 |
+
"processor_class": "ArkasrProcessor",
|
| 95 |
+
"split_special_tokens": false,
|
| 96 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 97 |
+
"unk_token": null
|
| 98 |
+
}
|
vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|