Any-to-Any
Transformers
ONNX
Safetensors
English
Chinese
multimodal
audio
video
speech
streaming
full-duplex
long-video
custom-code
Instructions to use inclusionAI/Realtime-Venus with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use inclusionAI/Realtime-Venus with Transformers:
# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("inclusionAI/Realtime-Venus", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Initial release
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +8 -0
- LEGAL.md +7 -0
- LICENSE +201 -0
- README.md +650 -0
- README_zh.md +621 -0
- Realtime-Venus-Audio/NOTICE +9 -0
- Realtime-Venus-Audio/added_tokens.json +111 -0
- Realtime-Venus-Audio/assets/HT_ref_audio.wav +3 -0
- Realtime-Venus-Audio/assets/case_duplex.wav +3 -0
- Realtime-Venus-Audio/assets/case_offline.wav +3 -0
- Realtime-Venus-Audio/assets/token2wav/campplus.onnx +3 -0
- Realtime-Venus-Audio/assets/token2wav/flow.pt +3 -0
- Realtime-Venus-Audio/assets/token2wav/flow.yaml +34 -0
- Realtime-Venus-Audio/assets/token2wav/hift.pt +3 -0
- Realtime-Venus-Audio/assets/token2wav/speech_tokenizer_v2_25hz.onnx +3 -0
- Realtime-Venus-Audio/chat_template.jinja +88 -0
- Realtime-Venus-Audio/config.json +323 -0
- Realtime-Venus-Audio/configuration_minicpmo.py +261 -0
- Realtime-Venus-Audio/generation_config.json +13 -0
- Realtime-Venus-Audio/merges.txt +0 -0
- Realtime-Venus-Audio/model-00001-of-00004.safetensors +3 -0
- Realtime-Venus-Audio/model-00002-of-00004.safetensors +3 -0
- Realtime-Venus-Audio/model-00003-of-00004.safetensors +3 -0
- Realtime-Venus-Audio/model-00004-of-00004.safetensors +3 -0
- Realtime-Venus-Audio/model.safetensors.index.json +0 -0
- Realtime-Venus-Audio/modeling_minicpmo.py +0 -0
- Realtime-Venus-Audio/modeling_navit_siglip.py +981 -0
- Realtime-Venus-Audio/preprocessor_config.json +49 -0
- Realtime-Venus-Audio/processing_minicpmo.py +1666 -0
- Realtime-Venus-Audio/processor_config.json +6 -0
- Realtime-Venus-Audio/special_tokens_map.json +121 -0
- Realtime-Venus-Audio/tokenization_minicpmo_fast.py +121 -0
- Realtime-Venus-Audio/tokenizer.json +3 -0
- Realtime-Venus-Audio/tokenizer_config.json +990 -0
- Realtime-Venus-Audio/utils.py +2418 -0
- Realtime-Venus-Audio/vocab.json +0 -0
- Realtime-Venus-Omni/LICENSE +201 -0
- Realtime-Venus-Omni/NOTICE +10 -0
- Realtime-Venus-Omni/added_tokens.json +111 -0
- Realtime-Venus-Omni/assets/HT_ref_audio.wav +3 -0
- Realtime-Venus-Omni/assets/sample_1_real.mp4 +3 -0
- Realtime-Venus-Omni/assets/speech_in.mp4 +3 -0
- Realtime-Venus-Omni/assets/token2wav/campplus.onnx +3 -0
- Realtime-Venus-Omni/assets/token2wav/flow.pt +3 -0
- Realtime-Venus-Omni/assets/token2wav/flow.yaml +34 -0
- Realtime-Venus-Omni/assets/token2wav/hift.pt +3 -0
- Realtime-Venus-Omni/assets/token2wav/speech_tokenizer_v2_25hz.onnx +3 -0
- Realtime-Venus-Omni/config.json +285 -0
- Realtime-Venus-Omni/configuration_realtime_venus_omni.py +261 -0
- Realtime-Venus-Omni/generation_config.json +12 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,11 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
Realtime-Venus-Audio/assets/HT_ref_audio.wav filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
Realtime-Venus-Audio/assets/case_duplex.wav filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
Realtime-Venus-Audio/assets/case_offline.wav filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
Realtime-Venus-Audio/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
Realtime-Venus-Omni/assets/HT_ref_audio.wav filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
Realtime-Venus-Omni/assets/sample_1_real.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
Realtime-Venus-Omni/assets/speech_in.mp4 filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
assets/venus-logo-white.gif filter=lfs diff=lfs merge=lfs -text
|
LEGAL.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Legal Disclaimer
|
| 2 |
+
|
| 3 |
+
Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail.
|
| 4 |
+
|
| 5 |
+
法律免责声明
|
| 6 |
+
|
| 7 |
+
关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright OpenBMB
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
README.md
ADDED
|
@@ -0,0 +1,650 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- en
|
| 5 |
+
- zh
|
| 6 |
+
library_name: transformers
|
| 7 |
+
pipeline_tag: any-to-any
|
| 8 |
+
tags:
|
| 9 |
+
- multimodal
|
| 10 |
+
- audio
|
| 11 |
+
- video
|
| 12 |
+
- speech
|
| 13 |
+
- streaming
|
| 14 |
+
- full-duplex
|
| 15 |
+
- long-video
|
| 16 |
+
- custom-code
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
<div align="center">
|
| 20 |
+
<img src="./assets/venus-logo-white.gif" alt="Realtime-Venus logo" width="180">
|
| 21 |
+
<br>
|
| 22 |
+
|
| 23 |
+
# Realtime-Venus
|
| 24 |
+
|
| 25 |
+
**A full-duplex interaction system with asynchronous delegation**
|
| 26 |
+
|
| 27 |
+
**English** | [简体中文](./README_zh.md)
|
| 28 |
+
|
| 29 |
+
<br>
|
| 30 |
+
|
| 31 |
+
<a href="https://realtime-venus.github.io/"><img src="https://img.shields.io/badge/Project_Page-4c9aff.svg?logo=googlechrome&logoColor=white" alt="Project Page"></a>
|
| 32 |
+
<a href="https://huggingface.co/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/Hugging_Face-Realtime--Venus-FFD21E.svg?logo=huggingface&logoColor=000" alt="Realtime-Venus on Hugging Face"></a>
|
| 33 |
+
<a href="https://www.modelscope.cn/models/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/ModelScope-Realtime--Venus-624AFF.svg?logo=modelscope&logoColor=white" alt="Realtime-Venus on ModelScope"></a>
|
| 34 |
+
<a href="https://arxiv.org/abs/2609.13814"><img src="https://img.shields.io/badge/arXiv-2609.13814-b31b1b.svg?logo=arxiv&logoColor=white" alt="arXiv"></a>
|
| 35 |
+
<a href="https://github.com/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/GitHub-Realtime--Venus-181717.svg?logo=github&logoColor=white" alt="GitHub"></a>
|
| 36 |
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-0b7285.svg?logo=apache&logoColor=white" alt="Apache License 2.0"></a>
|
| 37 |
+
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<p align="center">
|
| 41 |
+
<a href="https://arxiv.org/abs/2609.13814">
|
| 42 |
+
<img src="https://arxiv.org/html/2609.13814v1/case.png" alt="Example proactive, delegated, and full-duplex interactions with Realtime-Venus" width="100%">
|
| 43 |
+
</a>
|
| 44 |
+
</p>
|
| 45 |
+
<p align="center"><em>Realtime-Venus supports proactive audio-visual interaction, asynchronous delegation, and interruption-aware full-duplex dialogue.</em></p>
|
| 46 |
+
|
| 47 |
+
## 1. 🧭 Overview
|
| 48 |
+
|
| 49 |
+
This repository hosts two checkpoints of the
|
| 50 |
+
[Realtime-Venus](https://realtime-venus.github.io/) system:
|
| 51 |
+
|
| 52 |
+
- **Realtime-Venus-Omni** (`Realtime-Venus-Omni/`): the 9B audio-visual
|
| 53 |
+
interaction model. It continuously watches and listens, decides whether and
|
| 54 |
+
when to respond, and generates text and speech on a shared causal timeline.
|
| 55 |
+
Adapted from MiniCPM-o 4.5, it supports proactive interaction, semantic
|
| 56 |
+
interruption handling, and training-free long-video memory.
|
| 57 |
+
- **Realtime-Venus-Audio** (`Realtime-Venus-Audio/`): the audio-focused
|
| 58 |
+
checkpoint on the same streaming backbone, for audio understanding and
|
| 59 |
+
audio-driven conversation with text or speech output.
|
| 60 |
+
|
| 61 |
+
Both directories contain model weights and custom Hugging Face Transformers
|
| 62 |
+
code. The asynchronous Realtime-Venus-Harness and its external tool
|
| 63 |
+
integrations live in the
|
| 64 |
+
[GitHub repository](https://github.com/inclusionAI/Realtime-Venus).
|
| 65 |
+
|
| 66 |
+
## 2. ✨ Highlights
|
| 67 |
+
|
| 68 |
+
- **Native full-duplex conversation:** keeps perceiving while speaking and
|
| 69 |
+
distinguishes backchannels, interruptions, corrections, and redirections.
|
| 70 |
+
- **Omni-Proactive interaction:** continuously processes temporally aligned
|
| 71 |
+
video and audio, and initiates a response when an event warrants it — without
|
| 72 |
+
waiting for a user prompt.
|
| 73 |
+
- **Delegation:** emits in-stream `<delegate>` requests on the shared causal
|
| 74 |
+
timeline and consumes asynchronous backend results the same way, so external
|
| 75 |
+
tasks never block the ongoing conversation. (Executing requests requires the
|
| 76 |
+
Realtime-Venus-Harness runtime, available in the
|
| 77 |
+
[GitHub repository](https://github.com/inclusionAI/Realtime-Venus).)
|
| 78 |
+
- **Training-free long-video Memory:** archives visually informative moments,
|
| 79 |
+
retrieves query-relevant and non-redundant evidence, and reassembles the
|
| 80 |
+
corresponding audio-visual context — no additional training required.
|
| 81 |
+
- **Text and speech output:** generates response text together with native
|
| 82 |
+
speech through the bundled Token2wav resources and a reference voice.
|
| 83 |
+
|
| 84 |
+
## 3. 📋 Model Details
|
| 85 |
+
|
| 86 |
+
| Item | Realtime-Venus-Omni | Realtime-Venus-Audio |
|
| 87 |
+
| --- | --- | --- |
|
| 88 |
+
| Parameters | 9B | 9B |
|
| 89 |
+
| Base architecture | MiniCPM-o 4.5 / Omni-Flow | MiniCPM-o 4.5 / Omni-Flow |
|
| 90 |
+
| Visual encoder | SigLIP2 | not used at inference |
|
| 91 |
+
| Audio encoder | Whisper-Medium | Whisper-Medium |
|
| 92 |
+
| Language backbone | Qwen3-8B | Qwen3-8B |
|
| 93 |
+
| Speech generation | Discrete S3 speech tokens with a streaming flow-matching decoder | same decoder, enabled in full-duplex mode |
|
| 94 |
+
| Inputs | Video/images, audio, and text | Audio and text |
|
| 95 |
+
| Outputs | Text and optional speech waveform | Text and speech waveform |
|
| 96 |
+
| Context length | 40,960 tokens | 40,960 tokens |
|
| 97 |
+
| Weight dtype | BF16 | BF16 |
|
| 98 |
+
|
| 99 |
+
## 4. 📊 Evaluation
|
| 100 |
+
|
| 101 |
+
All values are reported in the
|
| 102 |
+
[Realtime-Venus technical report](https://arxiv.org/abs/2609.13814).
|
| 103 |
+
|
| 104 |
+
<p align="center"><img src="assets/paper-understanding.svg" width="100%" alt="Radar charts comparing video understanding for Omni and audio understanding for Audio" /><br /><sub>Figure 1. Video and audio understanding results from the <a href="https://arxiv.org/html/2609.13814v1#S0.F1">paper</a>.</sub></p>
|
| 105 |
+
|
| 106 |
+
<p align="center"><img src="assets/paper-duplex.svg" width="100%" alt="Full-duplex benchmark comparisons for interruption handling and continuation under different types of overlapping speech" /><br /><sub>Figure 2. Full-duplex interaction results from the <a href="https://arxiv.org/html/2609.13814v1#S0.F2">paper</a>.</sub></p>
|
| 107 |
+
|
| 108 |
+
## 5. 🗂️ Repository Layout
|
| 109 |
+
|
| 110 |
+
```text
|
| 111 |
+
.
|
| 112 |
+
├── Realtime-Venus-Omni/ # Audio-visual full-duplex checkpoint
|
| 113 |
+
│ ├── model-*.safetensors # Sharded model weights
|
| 114 |
+
│ ├── config.json, *.py # Model config and custom Transformers code
|
| 115 |
+
│ ├── realtime_venus_omni_memory.py # Public Memory entry point
|
| 116 |
+
│ ├── memory_adapter/ # Chat and Duplex Memory runtime
|
| 117 |
+
│ ├── assets/ # Reference voice, Token2wav, demo videos
|
| 118 |
+
│ └── requirements.txt
|
| 119 |
+
├── Realtime-Venus-Audio/ # Audio-focused checkpoint
|
| 120 |
+
│ ├── model-*.safetensors # Sharded model weights
|
| 121 |
+
│ ├── config.json, *.py # Model config and custom Transformers code
|
| 122 |
+
│ └── assets/ # Reference voice, Token2wav, demo audio
|
| 123 |
+
├── assets/ # Brand resources (logo)
|
| 124 |
+
├── README.md
|
| 125 |
+
├── README_zh.md
|
| 126 |
+
└── LICENSE
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
The examples below write generated media to `output/`. Use a new filename or a
|
| 130 |
+
new output directory when repeating an experiment.
|
| 131 |
+
|
| 132 |
+
## 6. 🛠️ Installation
|
| 133 |
+
|
| 134 |
+
Requires Python 3.10, CUDA, and FFmpeg. Download the repository (the two
|
| 135 |
+
checkpoints live in its sub-directories) and install the Python dependencies:
|
| 136 |
+
|
| 137 |
+
```bash
|
| 138 |
+
huggingface-cli download inclusionAI/Realtime-Venus --local-dir .
|
| 139 |
+
# or: modelscope download --model inclusionAI/Realtime-Venus --local_dir .
|
| 140 |
+
|
| 141 |
+
python -m pip install -r Realtime-Venus-Omni/requirements.txt
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
All example paths below are relative to this repository's root directory.
|
| 145 |
+
`from_pretrained` does not address sub-directories of a Hub repo, so the
|
| 146 |
+
examples point at the local `Realtime-Venus-Omni/` and `Realtime-Venus-Audio/`
|
| 147 |
+
paths after the download.
|
| 148 |
+
|
| 149 |
+
## 7. 🎙️ Realtime-Venus-Omni Usages
|
| 150 |
+
|
| 151 |
+
Runnable standalone versions of these examples live in the
|
| 152 |
+
[Omni cookbook](https://github.com/inclusionAI/Realtime-Venus/tree/main/frontend/Realtime-Venus-Omni)
|
| 153 |
+
on GitHub.
|
| 154 |
+
|
| 155 |
+
### 7.1 🧱 Model Initialization
|
| 156 |
+
|
| 157 |
+
The examples below share the following model initialization; run each
|
| 158 |
+
example in a fresh Python process.
|
| 159 |
+
Chat and Duplex automatically load the default reference voice.
|
| 160 |
+
|
| 161 |
+
<details>
|
| 162 |
+
<summary>Click to show Omni model loading code.</summary>
|
| 163 |
+
|
| 164 |
+
```python
|
| 165 |
+
from pathlib import Path
|
| 166 |
+
|
| 167 |
+
import torch
|
| 168 |
+
from transformers import AutoModel, set_seed
|
| 169 |
+
|
| 170 |
+
Path("output").mkdir(exist_ok=True)
|
| 171 |
+
set_seed(42)
|
| 172 |
+
print("Loading model ...")
|
| 173 |
+
model = AutoModel.from_pretrained(
|
| 174 |
+
"./Realtime-Venus-Omni", # or an absolute path to the sub-directory
|
| 175 |
+
trust_remote_code=True,
|
| 176 |
+
local_files_only=True,
|
| 177 |
+
attn_implementation="sdpa",
|
| 178 |
+
torch_dtype=torch.bfloat16,
|
| 179 |
+
)
|
| 180 |
+
model.eval().cuda()
|
| 181 |
+
print("Model loaded.")
|
| 182 |
+
```
|
| 183 |
+
|
| 184 |
+
</details>
|
| 185 |
+
|
| 186 |
+
### 7.2 🔊 Duplex Omni Mode
|
| 187 |
+
|
| 188 |
+
`model = model.as_duplex()` switches the model to full-duplex streaming:
|
| 189 |
+
`prepare()` initializes the session, then each second of input is handled by
|
| 190 |
+
one `streaming_prefill()` + `streaming_generate()` pair, and `as_simplex()`
|
| 191 |
+
switches back to offline mode. Set `MAX_NUM_FRAMES` before importing
|
| 192 |
+
`minicpmo.utils`, otherwise videos longer than 64 seconds are truncated to the
|
| 193 |
+
default frame cap.
|
| 194 |
+
|
| 195 |
+
Subtitle font note: Duplex examples burn the response text into the output
|
| 196 |
+
video through FFmpeg/libass, which resolves fonts via fontconfig. Rendering
|
| 197 |
+
non-Latin responses (e.g. Chinese) requires a CJK-capable font on the system,
|
| 198 |
+
otherwise those glyphs show up as empty boxes. On any Linux distribution,
|
| 199 |
+
install one without root and refresh the font cache:
|
| 200 |
+
|
| 201 |
+
```bash
|
| 202 |
+
mkdir -p ~/.local/share/fonts
|
| 203 |
+
curl --fail --location --retry 3 \
|
| 204 |
+
--output ~/.local/share/fonts/NotoSansCJKsc-Regular.otf \
|
| 205 |
+
https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf
|
| 206 |
+
fc-cache -f
|
| 207 |
+
```
|
| 208 |
+
|
| 209 |
+
Package-manager equivalents: `apt install -y fonts-noto-cjk` (Debian/Ubuntu) or
|
| 210 |
+
`yum install -y cjkuni-ukai-fonts cjkuni-uming-fonts` (RHEL/Alibaba Cloud Linux).
|
| 211 |
+
No code changes are needed.
|
| 212 |
+
|
| 213 |
+
#### 7.2.1 Duplex Chat
|
| 214 |
+
|
| 215 |
+
Stream the demo video second by second and inject text questions at the seconds
|
| 216 |
+
given by `question_times` (paired with `questions`). The model listens
|
| 217 |
+
continuously and speaks when it answers.
|
| 218 |
+
|
| 219 |
+
<details>
|
| 220 |
+
<summary>Click to show the Duplex Chat code.</summary>
|
| 221 |
+
|
| 222 |
+
```python
|
| 223 |
+
import os
|
| 224 |
+
|
| 225 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 226 |
+
|
| 227 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 228 |
+
|
| 229 |
+
model = model.as_duplex() # switch to full-duplex streaming
|
| 230 |
+
model.prepare()
|
| 231 |
+
|
| 232 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 233 |
+
# each question is injected at the corresponding second
|
| 234 |
+
question_times = [60, 128]
|
| 235 |
+
questions = [
|
| 236 |
+
"What do you see in the video so far?",
|
| 237 |
+
"What is the color of the cooler labeled PRIME near the team bench?",
|
| 238 |
+
]
|
| 239 |
+
question_plan = dict(zip(question_times, questions))
|
| 240 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 241 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 242 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 243 |
+
)
|
| 244 |
+
print(f"Streaming {len(audios)} seconds; questions are injected at {question_times}.")
|
| 245 |
+
results, output_audio = [], []
|
| 246 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 247 |
+
model.streaming_prefill(
|
| 248 |
+
audio_waveform=audio,
|
| 249 |
+
frame_list=[frame] if frame is not None else None,
|
| 250 |
+
text_list=[question_plan[second]] if second in question_plan else None,
|
| 251 |
+
)
|
| 252 |
+
result = model.streaming_generate()
|
| 253 |
+
print(
|
| 254 |
+
f"[{second}/{len(audios)}]",
|
| 255 |
+
"listen..." if result["is_listen"] else f"speak> {result['text']}",
|
| 256 |
+
flush=True,
|
| 257 |
+
)
|
| 258 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 259 |
+
if result["audio_waveform"] is not None:
|
| 260 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 261 |
+
|
| 262 |
+
model = model.as_simplex()
|
| 263 |
+
print("Muxing the spoken responses into the output video ...")
|
| 264 |
+
generate_duplex_video(
|
| 265 |
+
video_path=video_path,
|
| 266 |
+
output_video_path="output/duplex_chat.mp4",
|
| 267 |
+
results_log=results,
|
| 268 |
+
timed_output_audio=output_audio,
|
| 269 |
+
)
|
| 270 |
+
```
|
| 271 |
+
|
| 272 |
+
</details>
|
| 273 |
+
|
| 274 |
+
#### 7.2.2 Speech-In Duplex Chat
|
| 275 |
+
|
| 276 |
+
Same as above, except the question is spoken and already mixed into the video's
|
| 277 |
+
audio track (at ~3 s, asking for an alert when the water boils), so no text is
|
| 278 |
+
injected — the model must hear it.
|
| 279 |
+
|
| 280 |
+
<details>
|
| 281 |
+
<summary>Click to show the Speech-In Duplex Chat code.</summary>
|
| 282 |
+
|
| 283 |
+
```python
|
| 284 |
+
import os
|
| 285 |
+
|
| 286 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 287 |
+
|
| 288 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 289 |
+
|
| 290 |
+
model = model.as_duplex() # switch to full-duplex streaming
|
| 291 |
+
model.prepare()
|
| 292 |
+
|
| 293 |
+
video_path = "Realtime-Venus-Omni/assets/speech_in.mp4"
|
| 294 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 295 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 296 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 297 |
+
)
|
| 298 |
+
print(f"Streaming {len(audios)} seconds; the spoken question is already in the audio track.")
|
| 299 |
+
results, output_audio = [], []
|
| 300 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 301 |
+
model.streaming_prefill(
|
| 302 |
+
audio_waveform=audio,
|
| 303 |
+
frame_list=[frame] if frame is not None else None,
|
| 304 |
+
)
|
| 305 |
+
result = model.streaming_generate()
|
| 306 |
+
print(
|
| 307 |
+
f"[{second}/{len(audios)}]",
|
| 308 |
+
"listen..." if result["is_listen"] else result["text"],
|
| 309 |
+
flush=True,
|
| 310 |
+
)
|
| 311 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 312 |
+
if result["audio_waveform"] is not None:
|
| 313 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 314 |
+
|
| 315 |
+
model = model.as_simplex()
|
| 316 |
+
print("Muxing the spoken responses into the output video ...")
|
| 317 |
+
generate_duplex_video(
|
| 318 |
+
video_path=video_path,
|
| 319 |
+
output_video_path="output/duplex_speech_in_chat.mp4",
|
| 320 |
+
results_log=results,
|
| 321 |
+
timed_output_audio=output_audio,
|
| 322 |
+
)
|
| 323 |
+
```
|
| 324 |
+
|
| 325 |
+
</details>
|
| 326 |
+
|
| 327 |
+
#### 7.2.3 Memory Duplex Chat
|
| 328 |
+
|
| 329 |
+
`model.use_memory(memory_minutes=40)` enables the long-video Memory before
|
| 330 |
+
entering duplex mode.
|
| 331 |
+
|
| 332 |
+
<details>
|
| 333 |
+
<summary>Click to show the Memory Duplex Chat code.</summary>
|
| 334 |
+
|
| 335 |
+
```python
|
| 336 |
+
import os
|
| 337 |
+
|
| 338 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 339 |
+
|
| 340 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 341 |
+
|
| 342 |
+
model.use_memory(memory_minutes=40) # enable long-video memory
|
| 343 |
+
model = model.as_duplex() # switch to full-duplex streaming
|
| 344 |
+
model.prepare()
|
| 345 |
+
|
| 346 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 347 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 348 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 349 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 350 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 351 |
+
)
|
| 352 |
+
print(f"Streaming {len(audios)} seconds; the text question is injected at second 128.")
|
| 353 |
+
results, output_audio = [], []
|
| 354 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 355 |
+
model.streaming_prefill(
|
| 356 |
+
audio_waveform=audio,
|
| 357 |
+
frame_list=[frame] if frame is not None else None,
|
| 358 |
+
text_list=[question] if second == 128 else None,
|
| 359 |
+
)
|
| 360 |
+
result = model.streaming_generate()
|
| 361 |
+
print(
|
| 362 |
+
f"[{second}/{len(audios)}]",
|
| 363 |
+
"listen..." if result["is_listen"] else f"speak> {result['text']}",
|
| 364 |
+
flush=True,
|
| 365 |
+
)
|
| 366 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 367 |
+
if result["audio_waveform"] is not None:
|
| 368 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 369 |
+
|
| 370 |
+
model = model.as_simplex()
|
| 371 |
+
print("Muxing the spoken responses into the output video ...")
|
| 372 |
+
generate_duplex_video(
|
| 373 |
+
video_path=video_path,
|
| 374 |
+
output_video_path="output/duplex_memory_chat.mp4",
|
| 375 |
+
results_log=results,
|
| 376 |
+
timed_output_audio=output_audio,
|
| 377 |
+
)
|
| 378 |
+
```
|
| 379 |
+
|
| 380 |
+
</details>
|
| 381 |
+
|
| 382 |
+
### 7.3 💬 Half-Duplex Omni Mode
|
| 383 |
+
|
| 384 |
+
`model.chat(...)` answers one turn at a time over the whole video.
|
| 385 |
+
`model.init_tts()` enables speech output.
|
| 386 |
+
|
| 387 |
+
#### 7.3.1 Offline Chat
|
| 388 |
+
|
| 389 |
+
Sampled frames, per-second audio, and the question go into a single `chat()`
|
| 390 |
+
call. The 128-frame cap (`MAX_NUM_FRAMES`) limits the visual load, while
|
| 391 |
+
`max_inp_length=32768` sets the input-token budget. Full audio is still
|
| 392 |
+
retained, so very long videos can exceed that budget even with frame sampling.
|
| 393 |
+
|
| 394 |
+
<details>
|
| 395 |
+
<summary>Click to show the Offline Chat code.</summary>
|
| 396 |
+
|
| 397 |
+
```python
|
| 398 |
+
import os
|
| 399 |
+
|
| 400 |
+
os.environ.setdefault("MAX_NUM_FRAMES", "128")
|
| 401 |
+
|
| 402 |
+
from minicpmo.utils import get_video_frame_audio_segments
|
| 403 |
+
|
| 404 |
+
model.init_tts() # enable speech output
|
| 405 |
+
|
| 406 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 407 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 408 |
+
print(f"Extracting audio and frames from {video_path} ...")
|
| 409 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 410 |
+
video_path, stack_frames=1
|
| 411 |
+
)
|
| 412 |
+
content = []
|
| 413 |
+
for frame, audio in zip(frames, audios):
|
| 414 |
+
if frame is not None:
|
| 415 |
+
content.append(frame)
|
| 416 |
+
content.append(audio)
|
| 417 |
+
content.append(question)
|
| 418 |
+
|
| 419 |
+
print("Running chat inference ...")
|
| 420 |
+
response = model.chat(
|
| 421 |
+
msgs=[{"role": "user", "content": content}],
|
| 422 |
+
max_new_tokens=4096,
|
| 423 |
+
max_inp_length=32768,
|
| 424 |
+
do_sample=True,
|
| 425 |
+
temperature=0.7,
|
| 426 |
+
use_image_id=False,
|
| 427 |
+
max_slice_nums=1,
|
| 428 |
+
use_tts_template=True,
|
| 429 |
+
enable_thinking=False,
|
| 430 |
+
omni_mode=True,
|
| 431 |
+
generate_audio=True,
|
| 432 |
+
output_audio_path="output/offline_chat.wav",
|
| 433 |
+
)
|
| 434 |
+
print(response)
|
| 435 |
+
```
|
| 436 |
+
|
| 437 |
+
</details>
|
| 438 |
+
|
| 439 |
+
#### 7.3.2 Memory Offline Chat
|
| 440 |
+
|
| 441 |
+
`model.use_memory()` enables Memory before the chat call; retrieval selects up
|
| 442 |
+
to 96 historical frames plus 4 recent frames, each with ±1 s of audio.
|
| 443 |
+
|
| 444 |
+
<details>
|
| 445 |
+
<summary>Click to show the Memory Offline Chat code.</summary>
|
| 446 |
+
|
| 447 |
+
```python
|
| 448 |
+
import os
|
| 449 |
+
|
| 450 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 451 |
+
|
| 452 |
+
from minicpmo.utils import get_video_frame_audio_segments
|
| 453 |
+
|
| 454 |
+
model.use_memory() # enable long-video memory
|
| 455 |
+
model.init_tts() # enable speech output
|
| 456 |
+
|
| 457 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 458 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 459 |
+
print(f"Extracting audio and frames from {video_path} ...")
|
| 460 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 461 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 462 |
+
)
|
| 463 |
+
content = []
|
| 464 |
+
for frame, audio in zip(frames, audios):
|
| 465 |
+
if frame is not None:
|
| 466 |
+
content.append(frame)
|
| 467 |
+
content.append(audio)
|
| 468 |
+
content.append(question)
|
| 469 |
+
|
| 470 |
+
print("Running chat inference ...")
|
| 471 |
+
response = model.chat(
|
| 472 |
+
msgs=[{"role": "user", "content": content}],
|
| 473 |
+
max_new_tokens=4096,
|
| 474 |
+
max_inp_length=32768,
|
| 475 |
+
do_sample=True,
|
| 476 |
+
temperature=0.7,
|
| 477 |
+
use_image_id=False,
|
| 478 |
+
max_slice_nums=1,
|
| 479 |
+
use_tts_template=True,
|
| 480 |
+
enable_thinking=False,
|
| 481 |
+
omni_mode=True,
|
| 482 |
+
generate_audio=True,
|
| 483 |
+
output_audio_path="output/offline_memory_chat.wav",
|
| 484 |
+
)
|
| 485 |
+
print(response)
|
| 486 |
+
```
|
| 487 |
+
|
| 488 |
+
</details>
|
| 489 |
+
|
| 490 |
+
## 8. 🎧 Realtime-Venus-Audio Usages
|
| 491 |
+
|
| 492 |
+
Runnable standalone versions of these examples live in the
|
| 493 |
+
[Audio cookbook](https://github.com/inclusionAI/Realtime-Venus/tree/main/frontend/Realtime-Venus-Audio)
|
| 494 |
+
on GitHub.
|
| 495 |
+
|
| 496 |
+
The Audio checkpoint runs audio-only inference in two ways: turn-based
|
| 497 |
+
`model.chat` (text response) and the full-duplex streaming API (spoken
|
| 498 |
+
response). Inputs are decoded as 16 kHz mono audio from any audio or video
|
| 499 |
+
file.
|
| 500 |
+
|
| 501 |
+
### 8.1 🧱 Model Initialization
|
| 502 |
+
|
| 503 |
+
Speech output is enabled with `init_tts=True` so the same `model` serves both
|
| 504 |
+
examples; use `init_tts=False` for text-only chat to load faster.
|
| 505 |
+
|
| 506 |
+
<details>
|
| 507 |
+
<summary>Click to show Audio model loading code.</summary>
|
| 508 |
+
|
| 509 |
+
```python
|
| 510 |
+
from pathlib import Path
|
| 511 |
+
|
| 512 |
+
import torch
|
| 513 |
+
from transformers import AutoModel, AutoTokenizer, set_seed
|
| 514 |
+
|
| 515 |
+
Path("output").mkdir(exist_ok=True)
|
| 516 |
+
set_seed(42)
|
| 517 |
+
print("Loading model ...")
|
| 518 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 519 |
+
"./Realtime-Venus-Audio", trust_remote_code=True, local_files_only=True,
|
| 520 |
+
fix_mistral_regex=True,
|
| 521 |
+
)
|
| 522 |
+
model = AutoModel.from_pretrained(
|
| 523 |
+
"./Realtime-Venus-Audio",
|
| 524 |
+
trust_remote_code=True,
|
| 525 |
+
local_files_only=True,
|
| 526 |
+
attn_implementation="sdpa",
|
| 527 |
+
torch_dtype=torch.bfloat16,
|
| 528 |
+
init_vision=False, # audio-only usage
|
| 529 |
+
init_audio=True,
|
| 530 |
+
init_tts=True, # speech output; set False for text-only chat
|
| 531 |
+
).eval().cuda()
|
| 532 |
+
print("Model loaded.")
|
| 533 |
+
```
|
| 534 |
+
|
| 535 |
+
</details>
|
| 536 |
+
|
| 537 |
+
### 8.2 💭 Offline Chat
|
| 538 |
+
|
| 539 |
+
One deterministic turn over the full audio input: the audio (plus an optional
|
| 540 |
+
text instruction) goes into a single `model.chat()` call.
|
| 541 |
+
|
| 542 |
+
<details>
|
| 543 |
+
<summary>Click to show the Offline Chat code.</summary>
|
| 544 |
+
|
| 545 |
+
```python
|
| 546 |
+
import librosa
|
| 547 |
+
|
| 548 |
+
print("Loading audio ...")
|
| 549 |
+
audio, _ = librosa.load(
|
| 550 |
+
"Realtime-Venus-Audio/assets/case_offline.wav", sr=16000, mono=True
|
| 551 |
+
)
|
| 552 |
+
msgs = [
|
| 553 |
+
{"role": "system", "content": "You are a helpful assistant."},
|
| 554 |
+
{"role": "user", "content": [audio, "What is the speaker asking about?"]},
|
| 555 |
+
]
|
| 556 |
+
|
| 557 |
+
print("Running chat inference ...")
|
| 558 |
+
answer = model.chat(
|
| 559 |
+
msgs=msgs,
|
| 560 |
+
tokenizer=tokenizer,
|
| 561 |
+
do_sample=False,
|
| 562 |
+
max_new_tokens=2048,
|
| 563 |
+
enable_thinking=False,
|
| 564 |
+
use_tts_template=True,
|
| 565 |
+
generate_audio=False,
|
| 566 |
+
)
|
| 567 |
+
print(answer)
|
| 568 |
+
```
|
| 569 |
+
|
| 570 |
+
</details>
|
| 571 |
+
|
| 572 |
+
### 8.3 🎙️ Duplex Chat
|
| 573 |
+
|
| 574 |
+
`model.as_duplex(generate_audio=True)` switches to full-duplex streaming:
|
| 575 |
+
audio is fed second by second, the model listens continuously and speaks when
|
| 576 |
+
it answers. The example appends 10 s of trailing silence so the model can
|
| 577 |
+
finish its response after the input ends, and writes the generated speech to
|
| 578 |
+
`output/audio_full_duplex.wav`.
|
| 579 |
+
|
| 580 |
+
<details>
|
| 581 |
+
<summary>Click to show the Duplex Chat code.</summary>
|
| 582 |
+
|
| 583 |
+
```python
|
| 584 |
+
import librosa
|
| 585 |
+
import numpy as np
|
| 586 |
+
import soundfile as sf
|
| 587 |
+
|
| 588 |
+
duplex = model.as_duplex(generate_audio=True) # full-duplex with speech output
|
| 589 |
+
duplex.prepare(prompt_wav_path="Realtime-Venus-Audio/assets/HT_ref_audio.wav")
|
| 590 |
+
|
| 591 |
+
audio, _ = librosa.load(
|
| 592 |
+
"Realtime-Venus-Audio/assets/case_duplex.wav", sr=16000, mono=True
|
| 593 |
+
)
|
| 594 |
+
audio = np.concatenate([audio, np.zeros(10 * 16000, dtype=np.float32)])
|
| 595 |
+
|
| 596 |
+
chunk_samples = int(duplex.CHUNK_MS * duplex.SAMPLE_RATE / 1000)
|
| 597 |
+
total_chunks = max(1, (len(audio) + chunk_samples - 1) // chunk_samples)
|
| 598 |
+
timed_audio = []
|
| 599 |
+
for chunk_index in range(total_chunks):
|
| 600 |
+
chunk = audio[chunk_index * chunk_samples:(chunk_index + 1) * chunk_samples]
|
| 601 |
+
if len(chunk) < chunk_samples:
|
| 602 |
+
chunk = np.pad(chunk, (0, chunk_samples - len(chunk)))
|
| 603 |
+
duplex.streaming_prefill(audio_waveform=chunk)
|
| 604 |
+
result = duplex.streaming_generate(
|
| 605 |
+
max_new_speak_tokens_per_chunk=20,
|
| 606 |
+
decode_mode="sampling",
|
| 607 |
+
temperature=0.7,
|
| 608 |
+
top_k=20,
|
| 609 |
+
top_p=0.8,
|
| 610 |
+
listen_prob_scale=1.0,
|
| 611 |
+
)
|
| 612 |
+
state = "listen" if result["is_listen"] else f"speak> {result['text']}"
|
| 613 |
+
print(f"[{chunk_index + 1}/{total_chunks}] {state}", flush=True)
|
| 614 |
+
if result["audio_waveform"] is not None and not result["is_listen"]:
|
| 615 |
+
timed_audio.append((chunk_index, result["audio_waveform"]))
|
| 616 |
+
|
| 617 |
+
# stitch the generated speech on its original timeline (24 kHz)
|
| 618 |
+
sample_rate = 24000
|
| 619 |
+
total_samples = max(
|
| 620 |
+
t * sample_rate + len(np.asarray(w, dtype=np.float32).squeeze())
|
| 621 |
+
for t, w in timed_audio
|
| 622 |
+
)
|
| 623 |
+
output = np.zeros(total_samples, dtype=np.float32)
|
| 624 |
+
for t, waveform in timed_audio:
|
| 625 |
+
w = np.asarray(waveform, dtype=np.float32).squeeze()
|
| 626 |
+
output[t * sample_rate: t * sample_rate + len(w)] += w
|
| 627 |
+
sf.write("output/audio_full_duplex.wav", np.clip(output, -1.0, 1.0), sample_rate)
|
| 628 |
+
print("Saved generated speech to output/audio_full_duplex.wav")
|
| 629 |
+
```
|
| 630 |
+
|
| 631 |
+
</details>
|
| 632 |
+
|
| 633 |
+
## 9. 📝 Citation
|
| 634 |
+
|
| 635 |
+
If you find Realtime-Venus useful, please cite the technical report:
|
| 636 |
+
|
| 637 |
+
```bibtex
|
| 638 |
+
@article{zhao2026realtime,
|
| 639 |
+
title={{Realtime-Venus}: A full-duplex interaction system with asynchronous delegation},
|
| 640 |
+
author={{Venus Team,Ant Group;Tsinghua University}},
|
| 641 |
+
journal={arXiv preprint arXiv:2609.13814},
|
| 642 |
+
year={2026}
|
| 643 |
+
}
|
| 644 |
+
```
|
| 645 |
+
|
| 646 |
+
## 10. 📄 License
|
| 647 |
+
|
| 648 |
+
This repository includes an [Apache License 2.0](./LICENSE). Please also review
|
| 649 |
+
the licenses and acceptable-use terms of the upstream model, third-party
|
| 650 |
+
libraries, and any data used with this checkpoint.
|
README_zh.md
ADDED
|
@@ -0,0 +1,621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
language:
|
| 4 |
+
- zh
|
| 5 |
+
- en
|
| 6 |
+
library_name: transformers
|
| 7 |
+
pipeline_tag: any-to-any
|
| 8 |
+
tags:
|
| 9 |
+
- multimodal
|
| 10 |
+
- audio
|
| 11 |
+
- video
|
| 12 |
+
- speech
|
| 13 |
+
- streaming
|
| 14 |
+
- full-duplex
|
| 15 |
+
- long-video
|
| 16 |
+
- custom-code
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
<div align="center">
|
| 20 |
+
<img src="./assets/venus-logo-white.gif" alt="Realtime-Venus logo" width="180">
|
| 21 |
+
<br>
|
| 22 |
+
|
| 23 |
+
# Realtime-Venus
|
| 24 |
+
|
| 25 |
+
**支持异步委派的全双工交互系统**
|
| 26 |
+
|
| 27 |
+
[English](./README.md) | **简体中文**
|
| 28 |
+
|
| 29 |
+
<br>
|
| 30 |
+
|
| 31 |
+
<a href="https://realtime-venus.github.io/"><img src="https://img.shields.io/badge/Project_Page-4c9aff.svg?logo=googlechrome&logoColor=white" alt="项目主页"></a>
|
| 32 |
+
<a href="https://huggingface.co/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/Hugging_Face-Realtime--Venus-FFD21E.svg?logo=huggingface&logoColor=000" alt="Hugging Face 上的 Realtime-Venus"></a>
|
| 33 |
+
<a href="https://www.modelscope.cn/models/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/ModelScope-Realtime--Venus-624AFF.svg?logo=modelscope&logoColor=white" alt="ModelScope 上的 Realtime-Venus"></a>
|
| 34 |
+
<a href="https://arxiv.org/abs/2609.13814"><img src="https://img.shields.io/badge/arXiv-2609.13814-b31b1b.svg?logo=arxiv&logoColor=white" alt="arXiv"></a>
|
| 35 |
+
<a href="https://github.com/inclusionAI/Realtime-Venus"><img src="https://img.shields.io/badge/GitHub-Realtime--Venus-181717.svg?logo=github&logoColor=white" alt="GitHub"></a>
|
| 36 |
+
<a href="./LICENSE"><img src="https://img.shields.io/badge/License-Apache_2.0-0b7285.svg?logo=apache&logoColor=white" alt="Apache License 2.0"></a>
|
| 37 |
+
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<p align="center">
|
| 41 |
+
<a href="https://arxiv.org/abs/2609.13814">
|
| 42 |
+
<img src="https://arxiv.org/html/2609.13814v1/case.png" alt="Realtime-Venus 的主动交互、异步委派与全双工交互示例" width="100%">
|
| 43 |
+
</a>
|
| 44 |
+
</p>
|
| 45 |
+
<p align="center"><em>Realtime-Venus 支持主动式音视频交互、异步任务委派和可感知用户打断的全双工对话。</em></p>
|
| 46 |
+
|
| 47 |
+
## 1. 🧭 概览
|
| 48 |
+
|
| 49 |
+
本仓库包含 [Realtime-Venus](https://realtime-venus.github.io/) 系统的两个模型:
|
| 50 |
+
|
| 51 |
+
- **Realtime-Venus-Omni**(`Realtime-Venus-Omni/`):9B 音视频交互模型。模型能够持续
|
| 52 |
+
观看和聆听,在共享的因果时间线上决定是否、何时响应,并生成文本与语音。模型基于
|
| 53 |
+
MiniCPM-o 4.5 改造,支持主动式交互、语义级打断处理,以及免训练的长视频 Memory。
|
| 54 |
+
- **Realtime-Venus-Audio**(`Realtime-Venus-Audio/`):基于同一流式骨干的音频模型,
|
| 55 |
+
用于不包含视觉输入的音频理解与音频对话,输出文本或语音。
|
| 56 |
+
|
| 57 |
+
两个目录都包含模型权重与自定义 Hugging Face Transformers 代码。异步
|
| 58 |
+
Realtime-Venus-Harness 及其外部工具集成见
|
| 59 |
+
[GitHub 仓库](https://github.com/inclusionAI/Realtime-Venus)。
|
| 60 |
+
|
| 61 |
+
## 2. ✨ 核心亮点
|
| 62 |
+
|
| 63 |
+
- **原生全双工对话:** 在说话的同时继续感知,并区分附和、打断、纠正和话题重定向。
|
| 64 |
+
- **Omni-Proactive 主动交互:** 持续处理时间对齐的视频和音频,在新事件值得响应时
|
| 65 |
+
主动发言,无需等待用户提问。
|
| 66 |
+
- **Delegate 委派能力:** 在共享因果时间线上发出流内 `<delegate>` 请求,并以同样方式
|
| 67 |
+
接收异步返回的后端结果,外部任务不会阻塞当前对话。(执行请求需要
|
| 68 |
+
Realtime-Venus-Harness 运行时,见
|
| 69 |
+
[GitHub 仓库](https://github.com/inclusionAI/Realtime-Venus)。)
|
| 70 |
+
- **免训练长视频 Memory:** 归档视觉信息丰富的时刻,检索与问题相关且不冗余的证据,
|
| 71 |
+
并重新组装对应的音视频上下文,无需额外训练模型。
|
| 72 |
+
- **文本与语音输出:** 通过随仓库提供的 Token2wav 资源和参考音色,同时生成回复文本
|
| 73 |
+
与原生语音。
|
| 74 |
+
|
| 75 |
+
## 3. 📋 模型信息
|
| 76 |
+
|
| 77 |
+
| 项目 | Realtime-Venus-Omni | Realtime-Venus-Audio |
|
| 78 |
+
| --- | --- | --- |
|
| 79 |
+
| 参数量 | 9B | 9B |
|
| 80 |
+
| 基础架构 | MiniCPM-o 4.5 / Omni-Flow | MiniCPM-o 4.5 / Omni-Flow |
|
| 81 |
+
| 视觉编码器 | SigLIP2 | 推理时不使用 |
|
| 82 |
+
| 音频编码器 | Whisper-Medium | Whisper-Medium |
|
| 83 |
+
| 语言模型骨干 | Qwen3-8B | Qwen3-8B |
|
| 84 |
+
| 语音生成 | 离散 S3 语音 token 与流式 flow-matching 解码器 | 同一解码器,在全双工模式下启用 |
|
| 85 |
+
| 输入 | 视频/图像、音频和文本 | 音频和文本 |
|
| 86 |
+
| 输出 | 文本及可选语音波形 | 文本和语音波形 |
|
| 87 |
+
| 上下文长度 | 40,960 tokens | 40,960 tokens |
|
| 88 |
+
| 权重精度 | BF16 | BF16 |
|
| 89 |
+
|
| 90 |
+
## 4. 📊 评测
|
| 91 |
+
|
| 92 |
+
下列结果均来自 [Realtime-Venus 技术报告](https://arxiv.org/abs/2609.13814)。
|
| 93 |
+
|
| 94 |
+
<p align="center"><img src="assets/paper-understanding.svg" width="100%" alt="Omni 视频理解与 Audio 音频理解的雷达图对比" /><br /><sub>图 1. 论文中的视频与音频理解结果(<a href="https://arxiv.org/html/2609.13814v1#S0.F1">链接</a>)。</sub></p>
|
| 95 |
+
|
| 96 |
+
<p align="center"><img src="assets/paper-duplex.svg" width="100%" alt="不同重叠语音场景下打断处理与续说的全双工基准对比" /><br /><sub>图 2. 论文中的全双工交互结果(<a href="https://arxiv.org/html/2609.13814v1#S0.F2">链接</a>)。</sub></p>
|
| 97 |
+
|
| 98 |
+
## 5. 🗂️ 仓库结构
|
| 99 |
+
|
| 100 |
+
```text
|
| 101 |
+
.
|
| 102 |
+
├── Realtime-Venus-Omni/ # 音视频全双工模型
|
| 103 |
+
│ ├── model-*.safetensors # 分片模型权重
|
| 104 |
+
│ ├── config.json, *.py # 模型配置与自定义 Transformers 代码
|
| 105 |
+
│ ├── realtime_venus_omni_memory.py # Memory 公开入口
|
| 106 |
+
│ ├── memory_adapter/ # Chat 与 Duplex Memory 运行时
|
| 107 |
+
│ ├── assets/ # 参考音色、Token2wav、演示视频
|
| 108 |
+
│ └── requirements.txt
|
| 109 |
+
├── Realtime-Venus-Audio/ # 音频模型
|
| 110 |
+
│ ├── model-*.safetensors # 分片模型权重
|
| 111 |
+
│ ├── config.json, *.py # 模型配置与自定义 Transformers 代码
|
| 112 |
+
│ └── assets/ # 参考音色、Token2wav、演示音频
|
| 113 |
+
├── assets/ # Brand resources (logo)
|
| 114 |
+
├── README.md
|
| 115 |
+
├── README_zh.md
|
| 116 |
+
└── LICENSE
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
以下示例会把生成的媒体写入 `output/`。重复实验时请使用新的文件名或输出目录。
|
| 120 |
+
|
| 121 |
+
## 6. 🛠️ 安装
|
| 122 |
+
|
| 123 |
+
需要 Python 3.10、CUDA 和 FFmpeg。先下载仓库(两个模型分别位于其子目录中),
|
| 124 |
+
再安装 Python 依赖:
|
| 125 |
+
|
| 126 |
+
```bash
|
| 127 |
+
huggingface-cli download inclusionAI/Realtime-Venus --local-dir .
|
| 128 |
+
# 或:modelscope download --model inclusionAI/Realtime-Venus --local_dir .
|
| 129 |
+
|
| 130 |
+
python -m pip install -r Realtime-Venus-Omni/requirements.txt
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
下文所有示例路径均相对于本仓库根目录。`from_pretrained` 无法直接指定 Hub
|
| 134 |
+
仓库的子目录,因此示例在下载完成后指向本地的 `Realtime-Venus-Omni/` 与
|
| 135 |
+
`Realtime-Venus-Audio/` 路径。
|
| 136 |
+
|
| 137 |
+
## 7. 🎙️ Realtime-Venus-Omni 使用方法
|
| 138 |
+
|
| 139 |
+
这些示例的可独立运行脚本见 GitHub 上的
|
| 140 |
+
[Omni cookbook](https://github.com/inclusionAI/Realtime-Venus/tree/main/frontend/Realtime-Venus-Omni)。
|
| 141 |
+
|
| 142 |
+
### 7.1 🧱 模型初始化
|
| 143 |
+
|
| 144 |
+
以下示例共用下方的模型初始化代码;请在新的 Python 进程中分别运行每个示例。Chat 与 Duplex 会自动加载默认参考音色。
|
| 145 |
+
|
| 146 |
+
<details>
|
| 147 |
+
<summary>点击展开 Omni 模型加载代码。</summary>
|
| 148 |
+
|
| 149 |
+
```python
|
| 150 |
+
from pathlib import Path
|
| 151 |
+
|
| 152 |
+
import torch
|
| 153 |
+
from transformers import AutoModel, set_seed
|
| 154 |
+
|
| 155 |
+
Path("output").mkdir(exist_ok=True)
|
| 156 |
+
set_seed(42)
|
| 157 |
+
print("Loading model ...")
|
| 158 |
+
model = AutoModel.from_pretrained(
|
| 159 |
+
"./Realtime-Venus-Omni", # 也可以使用子目录的绝对路径
|
| 160 |
+
trust_remote_code=True,
|
| 161 |
+
local_files_only=True,
|
| 162 |
+
attn_implementation="sdpa",
|
| 163 |
+
torch_dtype=torch.bfloat16,
|
| 164 |
+
)
|
| 165 |
+
model.eval().cuda()
|
| 166 |
+
print("Model loaded.")
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
</details>
|
| 170 |
+
|
| 171 |
+
### 7.2 🔊 全双工 Omni 模式
|
| 172 |
+
|
| 173 |
+
`model = model.as_duplex()` 将模型切换为全双工流式模式;`prepare()` 初始化会话;
|
| 174 |
+
随后每秒输入由一次 `streaming_prefill()` 与 `streaming_generate()` 处理;
|
| 175 |
+
`as_simplex()` 切换回离线模式。必须在导入 `minicpmo.utils` 前设置
|
| 176 |
+
`MAX_NUM_FRAMES`,否则超过 64 秒的视频会被默认帧数上限截断。
|
| 177 |
+
|
| 178 |
+
字幕字体说明:Duplex 示例会通过 FFmpeg/libass 将回复文本烧录到输出视频中,字体由
|
| 179 |
+
fontconfig 解析。若系统没有支持中日韩字符的字体,中文等非拉丁文字会显示为空白方框。
|
| 180 |
+
在任意 Linux 发行版中均可无需 root 权限安装字体并刷新缓存:
|
| 181 |
+
|
| 182 |
+
```bash
|
| 183 |
+
mkdir -p ~/.local/share/fonts
|
| 184 |
+
curl --fail --location --retry 3 \
|
| 185 |
+
--output ~/.local/share/fonts/NotoSansCJKsc-Regular.otf \
|
| 186 |
+
https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf
|
| 187 |
+
fc-cache -f
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
也可以通过包管理器安装:Debian/Ubuntu 使用 `apt install -y fonts-noto-cjk`;
|
| 191 |
+
RHEL/Alibaba Cloud Linux 使用
|
| 192 |
+
`yum install -y cjkuni-ukai-fonts cjkuni-uming-fonts`。无需修改模型代码。
|
| 193 |
+
|
| 194 |
+
#### 7.2.1 全双工 Chat
|
| 195 |
+
|
| 196 |
+
逐秒流式输入演示视频,并在 `question_times` 指定的秒数注入与 `questions` 一一对应的
|
| 197 |
+
文本问题。模型会持续聆听,并在回答时生成语音。
|
| 198 |
+
|
| 199 |
+
<details>
|
| 200 |
+
<summary>点击展开全双工 Chat 推理代码。</summary>
|
| 201 |
+
|
| 202 |
+
```python
|
| 203 |
+
import os
|
| 204 |
+
|
| 205 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 206 |
+
|
| 207 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 208 |
+
|
| 209 |
+
model = model.as_duplex() # 切换为全双工流式模式
|
| 210 |
+
model.prepare()
|
| 211 |
+
|
| 212 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 213 |
+
# 每个问题在对应的秒数注入
|
| 214 |
+
question_times = [60, 128]
|
| 215 |
+
questions = [
|
| 216 |
+
"What do you see in the video so far?",
|
| 217 |
+
"What is the color of the cooler labeled PRIME near the team bench?",
|
| 218 |
+
]
|
| 219 |
+
question_plan = dict(zip(question_times, questions))
|
| 220 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 221 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 222 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 223 |
+
)
|
| 224 |
+
print(f"Streaming {len(audios)} seconds; questions are injected at {question_times}.")
|
| 225 |
+
results, output_audio = [], []
|
| 226 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 227 |
+
model.streaming_prefill(
|
| 228 |
+
audio_waveform=audio,
|
| 229 |
+
frame_list=[frame] if frame is not None else None,
|
| 230 |
+
text_list=[question_plan[second]] if second in question_plan else None,
|
| 231 |
+
)
|
| 232 |
+
result = model.streaming_generate()
|
| 233 |
+
print(
|
| 234 |
+
f"[{second}/{len(audios)}]",
|
| 235 |
+
"listen..." if result["is_listen"] else f"speak> {result['text']}",
|
| 236 |
+
flush=True,
|
| 237 |
+
)
|
| 238 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 239 |
+
if result["audio_waveform"] is not None:
|
| 240 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 241 |
+
|
| 242 |
+
model = model.as_simplex()
|
| 243 |
+
print("Muxing the spoken responses into the output video ...")
|
| 244 |
+
generate_duplex_video(
|
| 245 |
+
video_path=video_path,
|
| 246 |
+
output_video_path="output/duplex_chat.mp4",
|
| 247 |
+
results_log=results,
|
| 248 |
+
timed_output_audio=output_audio,
|
| 249 |
+
)
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
</details>
|
| 253 |
+
|
| 254 |
+
#### 7.2.2 语音输入全双工 Chat
|
| 255 |
+
|
| 256 |
+
处理流程与上例相同,但问题已作为语音混入视频音轨(约第 3 秒,请求水烧开时提醒),
|
| 257 |
+
因此不注入文本,模型必须直接听取问题。
|
| 258 |
+
|
| 259 |
+
<details>
|
| 260 |
+
<summary>点击展开语音输入全双工 Chat 推理代码。</summary>
|
| 261 |
+
|
| 262 |
+
```python
|
| 263 |
+
import os
|
| 264 |
+
|
| 265 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 266 |
+
|
| 267 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 268 |
+
|
| 269 |
+
model = model.as_duplex() # 切换为全双工流式模式
|
| 270 |
+
model.prepare()
|
| 271 |
+
|
| 272 |
+
video_path = "Realtime-Venus-Omni/assets/speech_in.mp4"
|
| 273 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 274 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 275 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 276 |
+
)
|
| 277 |
+
print(f"Streaming {len(audios)} seconds; the spoken question is already in the audio track.")
|
| 278 |
+
results, output_audio = [], []
|
| 279 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 280 |
+
model.streaming_prefill(
|
| 281 |
+
audio_waveform=audio,
|
| 282 |
+
frame_list=[frame] if frame is not None else None,
|
| 283 |
+
)
|
| 284 |
+
result = model.streaming_generate()
|
| 285 |
+
print(
|
| 286 |
+
f"[{second}/{len(audios)}]",
|
| 287 |
+
"listen..." if result["is_listen"] else result["text"],
|
| 288 |
+
flush=True,
|
| 289 |
+
)
|
| 290 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 291 |
+
if result["audio_waveform"] is not None:
|
| 292 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 293 |
+
|
| 294 |
+
model = model.as_simplex()
|
| 295 |
+
print("Muxing the spoken responses into the output video ...")
|
| 296 |
+
generate_duplex_video(
|
| 297 |
+
video_path=video_path,
|
| 298 |
+
output_video_path="output/duplex_speech_in_chat.mp4",
|
| 299 |
+
results_log=results,
|
| 300 |
+
timed_output_audio=output_audio,
|
| 301 |
+
)
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
</details>
|
| 305 |
+
|
| 306 |
+
#### 7.2.3 Memory 全双工 Chat
|
| 307 |
+
|
| 308 |
+
在进入 Duplex 模式前调用 `model.use_memory(memory_minutes=40)`,即可启用长视频 Memory。
|
| 309 |
+
|
| 310 |
+
<details>
|
| 311 |
+
<summary>点击展开 Memory 全双工 Chat 推理代码。</summary>
|
| 312 |
+
|
| 313 |
+
```python
|
| 314 |
+
import os
|
| 315 |
+
|
| 316 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 317 |
+
|
| 318 |
+
from minicpmo.utils import get_video_frame_audio_segments, generate_duplex_video
|
| 319 |
+
|
| 320 |
+
model.use_memory(memory_minutes=40) # 启用长视频记忆
|
| 321 |
+
model = model.as_duplex() # 切换为全双工流式模式
|
| 322 |
+
model.prepare()
|
| 323 |
+
|
| 324 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 325 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 326 |
+
print(f"Extracting per-second audio and frames from {video_path} ...")
|
| 327 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 328 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 329 |
+
)
|
| 330 |
+
print(f"Streaming {len(audios)} seconds; the text question is injected at second 128.")
|
| 331 |
+
results, output_audio = [], []
|
| 332 |
+
for second, (frame, audio) in enumerate(zip(frames, audios), start=1):
|
| 333 |
+
model.streaming_prefill(
|
| 334 |
+
audio_waveform=audio,
|
| 335 |
+
frame_list=[frame] if frame is not None else None,
|
| 336 |
+
text_list=[question] if second == 128 else None,
|
| 337 |
+
)
|
| 338 |
+
result = model.streaming_generate()
|
| 339 |
+
print(
|
| 340 |
+
f"[{second}/{len(audios)}]",
|
| 341 |
+
"listen..." if result["is_listen"] else f"speak> {result['text']}",
|
| 342 |
+
flush=True,
|
| 343 |
+
)
|
| 344 |
+
results.append({"chunk_idx": second - 1, **result})
|
| 345 |
+
if result["audio_waveform"] is not None:
|
| 346 |
+
output_audio.append((second - 1, result["audio_waveform"]))
|
| 347 |
+
|
| 348 |
+
model = model.as_simplex()
|
| 349 |
+
print("Muxing the spoken responses into the output video ...")
|
| 350 |
+
generate_duplex_video(
|
| 351 |
+
video_path=video_path,
|
| 352 |
+
output_video_path="output/duplex_memory_chat.mp4",
|
| 353 |
+
results_log=results,
|
| 354 |
+
timed_output_audio=output_audio,
|
| 355 |
+
)
|
| 356 |
+
```
|
| 357 |
+
|
| 358 |
+
</details>
|
| 359 |
+
|
| 360 |
+
### 7.3 💬 半双工 Omni 模式
|
| 361 |
+
|
| 362 |
+
`model.chat(...)` 以完整视频为输入,逐轮回答问题;`model.init_tts()` 用于启用语音输出。
|
| 363 |
+
|
| 364 |
+
#### 7.3.1 离线 Chat
|
| 365 |
+
|
| 366 |
+
采样后的视频帧、逐秒音频和问题会一起传入一次 `chat()` 调用。128 帧上限
|
| 367 |
+
(`MAX_NUM_FRAMES`)用于控制视觉输入规模,`max_inp_length=32768` 用于设置输入
|
| 368 |
+
token 预算。完整音频仍会保留,因此即使进行视频帧采样,超长视频仍可能超过该预算。
|
| 369 |
+
|
| 370 |
+
<details>
|
| 371 |
+
<summary>点击展开离线 Chat 推理代码。</summary>
|
| 372 |
+
|
| 373 |
+
```python
|
| 374 |
+
import os
|
| 375 |
+
|
| 376 |
+
os.environ.setdefault("MAX_NUM_FRAMES", "128")
|
| 377 |
+
|
| 378 |
+
from minicpmo.utils import get_video_frame_audio_segments
|
| 379 |
+
|
| 380 |
+
model.init_tts() # 启用语音输出
|
| 381 |
+
|
| 382 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 383 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 384 |
+
print(f"Extracting audio and frames from {video_path} ...")
|
| 385 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 386 |
+
video_path, stack_frames=1
|
| 387 |
+
)
|
| 388 |
+
content = []
|
| 389 |
+
for frame, audio in zip(frames, audios):
|
| 390 |
+
if frame is not None:
|
| 391 |
+
content.append(frame)
|
| 392 |
+
content.append(audio)
|
| 393 |
+
content.append(question)
|
| 394 |
+
|
| 395 |
+
print("Running chat inference ...")
|
| 396 |
+
response = model.chat(
|
| 397 |
+
msgs=[{"role": "user", "content": content}],
|
| 398 |
+
max_new_tokens=4096,
|
| 399 |
+
max_inp_length=32768,
|
| 400 |
+
do_sample=True,
|
| 401 |
+
temperature=0.7,
|
| 402 |
+
use_image_id=False,
|
| 403 |
+
max_slice_nums=1,
|
| 404 |
+
use_tts_template=True,
|
| 405 |
+
enable_thinking=False,
|
| 406 |
+
omni_mode=True,
|
| 407 |
+
generate_audio=True,
|
| 408 |
+
output_audio_path="output/offline_chat.wav",
|
| 409 |
+
)
|
| 410 |
+
print(response)
|
| 411 |
+
```
|
| 412 |
+
|
| 413 |
+
</details>
|
| 414 |
+
|
| 415 |
+
#### 7.3.2 Memory 离线 Chat
|
| 416 |
+
|
| 417 |
+
在调用 Chat 前通过 `model.use_memory()` 启用 Memory;检索最多选择 96 个历史帧和
|
| 418 |
+
4 个近期帧,并为每个选中帧保留前后各 1 秒的音频。
|
| 419 |
+
|
| 420 |
+
<details>
|
| 421 |
+
<summary>点击展开 Memory 离线 Chat 推理代码。</summary>
|
| 422 |
+
|
| 423 |
+
```python
|
| 424 |
+
import os
|
| 425 |
+
|
| 426 |
+
os.environ["MAX_NUM_FRAMES"] = "100000"
|
| 427 |
+
|
| 428 |
+
from minicpmo.utils import get_video_frame_audio_segments
|
| 429 |
+
|
| 430 |
+
model.use_memory() # 启用长视频记忆
|
| 431 |
+
model.init_tts() # 启用语音输出
|
| 432 |
+
|
| 433 |
+
video_path = "Realtime-Venus-Omni/assets/sample_1_real.mp4"
|
| 434 |
+
question = "What is the color of the cooler labeled PRIME near the team bench?"
|
| 435 |
+
print(f"Extracting audio and frames from {video_path} ...")
|
| 436 |
+
frames, audios, _ = get_video_frame_audio_segments(
|
| 437 |
+
video_path, stack_frames=1, use_ffmpeg=True, adjust_audio_length=True
|
| 438 |
+
)
|
| 439 |
+
content = []
|
| 440 |
+
for frame, audio in zip(frames, audios):
|
| 441 |
+
if frame is not None:
|
| 442 |
+
content.append(frame)
|
| 443 |
+
content.append(audio)
|
| 444 |
+
content.append(question)
|
| 445 |
+
|
| 446 |
+
print("Running chat inference ...")
|
| 447 |
+
response = model.chat(
|
| 448 |
+
msgs=[{"role": "user", "content": content}],
|
| 449 |
+
max_new_tokens=4096,
|
| 450 |
+
max_inp_length=32768,
|
| 451 |
+
do_sample=True,
|
| 452 |
+
temperature=0.7,
|
| 453 |
+
use_image_id=False,
|
| 454 |
+
max_slice_nums=1,
|
| 455 |
+
use_tts_template=True,
|
| 456 |
+
enable_thinking=False,
|
| 457 |
+
omni_mode=True,
|
| 458 |
+
generate_audio=True,
|
| 459 |
+
output_audio_path="output/offline_memory_chat.wav",
|
| 460 |
+
)
|
| 461 |
+
print(response)
|
| 462 |
+
```
|
| 463 |
+
|
| 464 |
+
</details>
|
| 465 |
+
|
| 466 |
+
## 8. 🎧 Realtime-Venus-Audio 使用方法
|
| 467 |
+
|
| 468 |
+
这些示例的可独立运行脚本见 GitHub 上的
|
| 469 |
+
[Audio cookbook](https://github.com/inclusionAI/Realtime-Venus/tree/main/frontend/Realtime-Venus-Audio)。
|
| 470 |
+
|
| 471 |
+
Audio 模型以两种方式运行纯音频推理:轮次式 `model.chat`(文本回复)与
|
| 472 |
+
全双工流式 API(语音回复)。输入统一解码为 16 kHz 单声道音频,可来自任意
|
| 473 |
+
音频或视频文件。
|
| 474 |
+
|
| 475 |
+
### 8.1 🧱 模型初始化
|
| 476 |
+
|
| 477 |
+
下方代码以 `init_tts=True` 启用语音输出,同一个 `model` 可支撑后文两个示例;
|
| 478 |
+
纯文本对话可改用 `init_tts=False`,加载更快。
|
| 479 |
+
|
| 480 |
+
<details>
|
| 481 |
+
<summary>点击展开 Audio 模型加载代码。</summary>
|
| 482 |
+
|
| 483 |
+
```python
|
| 484 |
+
from pathlib import Path
|
| 485 |
+
|
| 486 |
+
import torch
|
| 487 |
+
from transformers import AutoModel, AutoTokenizer, set_seed
|
| 488 |
+
|
| 489 |
+
Path("output").mkdir(exist_ok=True)
|
| 490 |
+
set_seed(42)
|
| 491 |
+
print("Loading model ...")
|
| 492 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 493 |
+
"./Realtime-Venus-Audio", trust_remote_code=True, local_files_only=True,
|
| 494 |
+
fix_mistral_regex=True,
|
| 495 |
+
)
|
| 496 |
+
model = AutoModel.from_pretrained(
|
| 497 |
+
"./Realtime-Venus-Audio",
|
| 498 |
+
trust_remote_code=True,
|
| 499 |
+
local_files_only=True,
|
| 500 |
+
attn_implementation="sdpa",
|
| 501 |
+
torch_dtype=torch.bfloat16,
|
| 502 |
+
init_vision=False, # 纯音频用法
|
| 503 |
+
init_audio=True,
|
| 504 |
+
init_tts=True, # 语音输出;纯文本对话可设为 False
|
| 505 |
+
).eval().cuda()
|
| 506 |
+
print("Model loaded.")
|
| 507 |
+
```
|
| 508 |
+
|
| 509 |
+
</details>
|
| 510 |
+
|
| 511 |
+
### 8.2 💭 离线 Chat
|
| 512 |
+
|
| 513 |
+
对完整音频输入进行一轮确定性回答:音频(可加一条文本指令)随一次
|
| 514 |
+
`model.chat()` 调用传入。
|
| 515 |
+
|
| 516 |
+
<details>
|
| 517 |
+
<summary>点击展开离线 Chat 推理代码。</summary>
|
| 518 |
+
|
| 519 |
+
```python
|
| 520 |
+
import librosa
|
| 521 |
+
|
| 522 |
+
print("Loading audio ...")
|
| 523 |
+
audio, _ = librosa.load(
|
| 524 |
+
"Realtime-Venus-Audio/assets/case_offline.wav", sr=16000, mono=True
|
| 525 |
+
)
|
| 526 |
+
msgs = [
|
| 527 |
+
{"role": "system", "content": "You are a helpful assistant."},
|
| 528 |
+
{"role": "user", "content": [audio, "What is the speaker asking about?"]},
|
| 529 |
+
]
|
| 530 |
+
|
| 531 |
+
print("Running chat inference ...")
|
| 532 |
+
answer = model.chat(
|
| 533 |
+
msgs=msgs,
|
| 534 |
+
tokenizer=tokenizer,
|
| 535 |
+
do_sample=False,
|
| 536 |
+
max_new_tokens=2048,
|
| 537 |
+
enable_thinking=False,
|
| 538 |
+
use_tts_template=True,
|
| 539 |
+
generate_audio=False,
|
| 540 |
+
)
|
| 541 |
+
print(answer)
|
| 542 |
+
```
|
| 543 |
+
|
| 544 |
+
</details>
|
| 545 |
+
|
| 546 |
+
### 8.3 🎙️ 全双工 Chat
|
| 547 |
+
|
| 548 |
+
`model.as_duplex(generate_audio=True)` 切换为全双工流式模式:音频逐秒输入,
|
| 549 |
+
模型持续聆听、在回答时开口说话。示例在输入末尾追加 10 秒静音,让模型在
|
| 550 |
+
输入结束后说完回复,生成的语音写入 `output/audio_full_duplex.wav`。
|
| 551 |
+
|
| 552 |
+
<details>
|
| 553 |
+
<summary>点击展开全双工 Chat 推理代码。</summary>
|
| 554 |
+
|
| 555 |
+
```python
|
| 556 |
+
import librosa
|
| 557 |
+
import numpy as np
|
| 558 |
+
import soundfile as sf
|
| 559 |
+
|
| 560 |
+
duplex = model.as_duplex(generate_audio=True) # 全双工,带语音输出
|
| 561 |
+
duplex.prepare(prompt_wav_path="Realtime-Venus-Audio/assets/HT_ref_audio.wav")
|
| 562 |
+
|
| 563 |
+
audio, _ = librosa.load(
|
| 564 |
+
"Realtime-Venus-Audio/assets/case_duplex.wav", sr=16000, mono=True
|
| 565 |
+
)
|
| 566 |
+
audio = np.concatenate([audio, np.zeros(10 * 16000, dtype=np.float32)])
|
| 567 |
+
|
| 568 |
+
chunk_samples = int(duplex.CHUNK_MS * duplex.SAMPLE_RATE / 1000)
|
| 569 |
+
total_chunks = max(1, (len(audio) + chunk_samples - 1) // chunk_samples)
|
| 570 |
+
timed_audio = []
|
| 571 |
+
for chunk_index in range(total_chunks):
|
| 572 |
+
chunk = audio[chunk_index * chunk_samples:(chunk_index + 1) * chunk_samples]
|
| 573 |
+
if len(chunk) < chunk_samples:
|
| 574 |
+
chunk = np.pad(chunk, (0, chunk_samples - len(chunk)))
|
| 575 |
+
duplex.streaming_prefill(audio_waveform=chunk)
|
| 576 |
+
result = duplex.streaming_generate(
|
| 577 |
+
max_new_speak_tokens_per_chunk=20,
|
| 578 |
+
decode_mode="sampling",
|
| 579 |
+
temperature=0.7,
|
| 580 |
+
top_k=20,
|
| 581 |
+
top_p=0.8,
|
| 582 |
+
listen_prob_scale=1.0,
|
| 583 |
+
)
|
| 584 |
+
state = "listen" if result["is_listen"] else f"speak> {result['text']}"
|
| 585 |
+
print(f"[{chunk_index + 1}/{total_chunks}] {state}", flush=True)
|
| 586 |
+
if result["audio_waveform"] is not None and not result["is_listen"]:
|
| 587 |
+
timed_audio.append((chunk_index, result["audio_waveform"]))
|
| 588 |
+
|
| 589 |
+
# 按原始时间线拼接生成的语音(24 kHz)
|
| 590 |
+
sample_rate = 24000
|
| 591 |
+
total_samples = max(
|
| 592 |
+
t * sample_rate + len(np.asarray(w, dtype=np.float32).squeeze())
|
| 593 |
+
for t, w in timed_audio
|
| 594 |
+
)
|
| 595 |
+
output = np.zeros(total_samples, dtype=np.float32)
|
| 596 |
+
for t, waveform in timed_audio:
|
| 597 |
+
w = np.asarray(waveform, dtype=np.float32).squeeze()
|
| 598 |
+
output[t * sample_rate: t * sample_rate + len(w)] += w
|
| 599 |
+
sf.write("output/audio_full_duplex.wav", np.clip(output, -1.0, 1.0), sample_rate)
|
| 600 |
+
print("Saved generated speech to output/audio_full_duplex.wav")
|
| 601 |
+
```
|
| 602 |
+
|
| 603 |
+
</details>
|
| 604 |
+
|
| 605 |
+
## 9. 📝 引用
|
| 606 |
+
|
| 607 |
+
如果 Realtime-Venus 对你的工作有帮助,请引用技术报告:
|
| 608 |
+
|
| 609 |
+
```bibtex
|
| 610 |
+
@article{zhao2026realtime,
|
| 611 |
+
title={{Realtime-Venus}: A full-duplex interaction system with asynchronous delegation},
|
| 612 |
+
author={{Venus Team,Ant Group;Tsinghua University}},
|
| 613 |
+
journal={arXiv preprint arXiv:2609.13814},
|
| 614 |
+
year={2026}
|
| 615 |
+
}
|
| 616 |
+
```
|
| 617 |
+
|
| 618 |
+
## 10. 📄 许可证
|
| 619 |
+
|
| 620 |
+
本仓库采用 [Apache License 2.0](./LICENSE)。同时请查阅上游模型、第三方库以及使用本模型时
|
| 621 |
+
所涉及数据的许可证与可接受使用条款。
|
Realtime-Venus-Audio/NOTICE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Realtime-Venus-Audio
|
| 2 |
+
Copyright 2026 The Realtime-Venus Team. All rights reserved.
|
| 3 |
+
|
| 4 |
+
This product includes software derived from MiniCPM-o 4.5
|
| 5 |
+
(https://github.com/OpenBMB/MiniCPM-o), Copyright The OpenBMB Team,
|
| 6 |
+
licensed under the Apache License, Version 2.0.
|
| 7 |
+
|
| 8 |
+
Modifications focus on audio-only understanding and full-duplex speech
|
| 9 |
+
conversation without visual input.
|
Realtime-Venus-Audio/added_tokens.json
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</answer>": 151686,
|
| 3 |
+
"</backend>": 151751,
|
| 4 |
+
"</box>": 151674,
|
| 5 |
+
"</delegate>": 151749,
|
| 6 |
+
"</focus>": 151688,
|
| 7 |
+
"</image>": 151670,
|
| 8 |
+
"</image_id>": 151682,
|
| 9 |
+
"</image_save_to>": 151696,
|
| 10 |
+
"</line>": 151690,
|
| 11 |
+
"</perception>": 151692,
|
| 12 |
+
"</point>": 151678,
|
| 13 |
+
"</quad>": 151676,
|
| 14 |
+
"</ref>": 151672,
|
| 15 |
+
"</slice>": 151680,
|
| 16 |
+
"</source_image>": 151694,
|
| 17 |
+
"</think>": 151668,
|
| 18 |
+
"</tool_call>": 151658,
|
| 19 |
+
"</tool_response>": 151666,
|
| 20 |
+
"</unit>": 151684,
|
| 21 |
+
"<answer>": 151685,
|
| 22 |
+
"<backend>": 151750,
|
| 23 |
+
"<box>": 151673,
|
| 24 |
+
"<delegate>": 151748,
|
| 25 |
+
"<focus>": 151687,
|
| 26 |
+
"<image>": 151669,
|
| 27 |
+
"<image_id>": 151681,
|
| 28 |
+
"<image_save_to>": 151695,
|
| 29 |
+
"<line>": 151689,
|
| 30 |
+
"<perception>": 151691,
|
| 31 |
+
"<point>": 151677,
|
| 32 |
+
"<quad>": 151675,
|
| 33 |
+
"<ref>": 151671,
|
| 34 |
+
"<slice>": 151679,
|
| 35 |
+
"<source_image>": 151693,
|
| 36 |
+
"<think>": 151667,
|
| 37 |
+
"<tool_call>": 151657,
|
| 38 |
+
"<tool_response>": 151665,
|
| 39 |
+
"<unit>": 151683,
|
| 40 |
+
"<|audio_end|>": 151699,
|
| 41 |
+
"<|audio_start|>": 151697,
|
| 42 |
+
"<|audio|>": 151698,
|
| 43 |
+
"<|box_end|>": 151649,
|
| 44 |
+
"<|box_start|>": 151648,
|
| 45 |
+
"<|chunk_bos|>": 151719,
|
| 46 |
+
"<|chunk_eos|>": 151718,
|
| 47 |
+
"<|chunk_tts_bos|>": 151720,
|
| 48 |
+
"<|chunk_tts_eos|>": 151721,
|
| 49 |
+
"<|emotion_end|>": 151711,
|
| 50 |
+
"<|emotion_start|>": 151710,
|
| 51 |
+
"<|endoftext|>": 151643,
|
| 52 |
+
"<|file_sep|>": 151664,
|
| 53 |
+
"<|fim_middle|>": 151660,
|
| 54 |
+
"<|fim_pad|>": 151662,
|
| 55 |
+
"<|fim_prefix|>": 151659,
|
| 56 |
+
"<|fim_suffix|>": 151661,
|
| 57 |
+
"<|im_end|>": 151645,
|
| 58 |
+
"<|im_start|>": 151644,
|
| 59 |
+
"<|image_pad|>": 151655,
|
| 60 |
+
"<|interrupt|>": 151707,
|
| 61 |
+
"<|listen|>": 151705,
|
| 62 |
+
"<|object_ref_end|>": 151647,
|
| 63 |
+
"<|object_ref_start|>": 151646,
|
| 64 |
+
"<|pitch_end|>": 151715,
|
| 65 |
+
"<|pitch_start|>": 151714,
|
| 66 |
+
"<|quad_end|>": 151651,
|
| 67 |
+
"<|quad_start|>": 151650,
|
| 68 |
+
"<|repo_name|>": 151663,
|
| 69 |
+
"<|speak|>": 151706,
|
| 70 |
+
"<|speed_end|>": 151713,
|
| 71 |
+
"<|speed_start|>": 151712,
|
| 72 |
+
"<|spk_bos|>": 151700,
|
| 73 |
+
"<|spk_eos|>": 151702,
|
| 74 |
+
"<|spk|>": 151701,
|
| 75 |
+
"<|timbre_10|>": 151726,
|
| 76 |
+
"<|timbre_11|>": 151727,
|
| 77 |
+
"<|timbre_12|>": 151728,
|
| 78 |
+
"<|timbre_13|>": 151729,
|
| 79 |
+
"<|timbre_14|>": 151730,
|
| 80 |
+
"<|timbre_15|>": 151731,
|
| 81 |
+
"<|timbre_16|>": 151732,
|
| 82 |
+
"<|timbre_17|>": 151733,
|
| 83 |
+
"<|timbre_18|>": 151734,
|
| 84 |
+
"<|timbre_19|>": 151735,
|
| 85 |
+
"<|timbre_20|>": 151736,
|
| 86 |
+
"<|timbre_21|>": 151737,
|
| 87 |
+
"<|timbre_22|>": 151738,
|
| 88 |
+
"<|timbre_23|>": 151739,
|
| 89 |
+
"<|timbre_24|>": 151740,
|
| 90 |
+
"<|timbre_25|>": 151741,
|
| 91 |
+
"<|timbre_26|>": 151742,
|
| 92 |
+
"<|timbre_27|>": 151743,
|
| 93 |
+
"<|timbre_28|>": 151744,
|
| 94 |
+
"<|timbre_29|>": 151745,
|
| 95 |
+
"<|timbre_30|>": 151746,
|
| 96 |
+
"<|timbre_31|>": 151747,
|
| 97 |
+
"<|timbre_7|>": 151723,
|
| 98 |
+
"<|timbre_8|>": 151724,
|
| 99 |
+
"<|timbre_9|>": 151725,
|
| 100 |
+
"<|tts_bos|>": 151703,
|
| 101 |
+
"<|tts_eos|>": 151704,
|
| 102 |
+
"<|tts_pad|>": 151722,
|
| 103 |
+
"<|turn_bos|>": 151716,
|
| 104 |
+
"<|turn_eos|>": 151717,
|
| 105 |
+
"<|vad_end|>": 151709,
|
| 106 |
+
"<|vad_start|>": 151708,
|
| 107 |
+
"<|video_pad|>": 151656,
|
| 108 |
+
"<|vision_end|>": 151653,
|
| 109 |
+
"<|vision_pad|>": 151654,
|
| 110 |
+
"<|vision_start|>": 151652
|
| 111 |
+
}
|
Realtime-Venus-Audio/assets/HT_ref_audio.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cb8f06ba5080cdf548969138881fb8ad8b04e2516108f4e08ba0363b68b613ea
|
| 3 |
+
size 192590
|
Realtime-Venus-Audio/assets/case_duplex.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b5adc85ed4ee731615b3fc0adb328ffcbd4ba4841edad02cc8329c04f78e495f
|
| 3 |
+
size 1771710
|
Realtime-Venus-Audio/assets/case_offline.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cab2cfdc338706c26ea43c7de898ec67182456030f4680519d1e62c294723d57
|
| 3 |
+
size 245804
|
Realtime-Venus-Audio/assets/token2wav/campplus.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a6ac6a63997761ae2997373e2ee1c47040854b4b759ea41ec48e4e42df0f4d73
|
| 3 |
+
size 28303423
|
Realtime-Venus-Audio/assets/token2wav/flow.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:15ccff24256ff61537c7f8b51e025116b83405f3fb017b54b008fc97da115446
|
| 3 |
+
size 623466603
|
Realtime-Venus-Audio/assets/token2wav/flow.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flow: !new:cosyvoice2.flow.flow.CausalMaskedDiffWithXvec
|
| 2 |
+
input_size: 512
|
| 3 |
+
output_size: 80
|
| 4 |
+
spk_embed_dim: 192
|
| 5 |
+
output_type: 'mel'
|
| 6 |
+
vocab_size: 6561
|
| 7 |
+
encoder: !new:cosyvoice2.transformer.upsample_encoder_v2.UpsampleConformerEncoderV2
|
| 8 |
+
input_size: 512
|
| 9 |
+
output_size: 512
|
| 10 |
+
input_layer: 'linear'
|
| 11 |
+
pre_lookahead_len: 3
|
| 12 |
+
num_blocks: 6
|
| 13 |
+
num_up_blocks: 4
|
| 14 |
+
up_stride: 2
|
| 15 |
+
up_scale_factor: 2
|
| 16 |
+
attention_heads: 8
|
| 17 |
+
pos_enc_layer_type: 'rel_pos_espnet'
|
| 18 |
+
selfattention_layer_type: 'rel_selfattn'
|
| 19 |
+
key_bias: true
|
| 20 |
+
linear_units: 2048
|
| 21 |
+
dropout_rate: 0.1
|
| 22 |
+
positional_dropout_rate: 0.1
|
| 23 |
+
attention_dropout_rate: 0.1
|
| 24 |
+
normalize_before: True
|
| 25 |
+
decoder: !new:cosyvoice2.flow.flow_matching.CausalConditionalCFM
|
| 26 |
+
inference_cfg_rate: 0.7
|
| 27 |
+
estimator: !new:cosyvoice2.flow.decoder_dit.DiT
|
| 28 |
+
in_channels: 320
|
| 29 |
+
out_channels: 80
|
| 30 |
+
mlp_ratio: 4.0
|
| 31 |
+
depth: 16
|
| 32 |
+
num_heads: 8
|
| 33 |
+
head_dim: 64
|
| 34 |
+
hidden_size: 512
|
Realtime-Venus-Audio/assets/token2wav/hift.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3386cc880324d4e98e05987b99107f49e40ed925b8ecc87c1f4939432d429879
|
| 3 |
+
size 83390254
|
Realtime-Venus-Audio/assets/token2wav/speech_tokenizer_v2_25hz.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d43342aa12163a80bf07bffb94c9de2e120a8df2f9917cd2f642e7f4219c6f71
|
| 3 |
+
size 496082973
|
Realtime-Venus-Audio/chat_template.jinja
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- if tools %}
|
| 2 |
+
{{- '<|im_start|>system\n' }}
|
| 3 |
+
{%- if messages[0].role == 'system' %}
|
| 4 |
+
{{- messages[0].content + '\n\n' }}
|
| 5 |
+
{%- endif %}
|
| 6 |
+
{{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
| 7 |
+
{%- for tool in tools %}
|
| 8 |
+
{{- "\n" }}
|
| 9 |
+
{{- tool | tojson }}
|
| 10 |
+
{%- endfor %}
|
| 11 |
+
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
| 12 |
+
{%- else %}
|
| 13 |
+
{%- if messages[0].role == 'system' %}
|
| 14 |
+
{{- '<|im_start|>system\n' + messages[0].content + '<|im_end|>\n' }}
|
| 15 |
+
{%- endif %}
|
| 16 |
+
{%- endif %}
|
| 17 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 18 |
+
{%- for message in messages[::-1] %}
|
| 19 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 20 |
+
{%- if ns.multi_step_tool and message.role == "user" and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}
|
| 21 |
+
{%- set ns.multi_step_tool = false %}
|
| 22 |
+
{%- set ns.last_query_index = index %}
|
| 23 |
+
{%- endif %}
|
| 24 |
+
{%- endfor %}
|
| 25 |
+
{%- for message in messages %}
|
| 26 |
+
{%- if (message.role == "user") or (message.role == "system" and not loop.first) %}
|
| 27 |
+
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
| 28 |
+
{%- elif message.role == "assistant" %}
|
| 29 |
+
{%- set content = message.content %}
|
| 30 |
+
{%- set reasoning_content = '' %}
|
| 31 |
+
{%- if message.reasoning_content is defined and message.reasoning_content is not none %}
|
| 32 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 33 |
+
{%- else %}
|
| 34 |
+
{%- if '</think>' in message.content %}
|
| 35 |
+
{%- set content = message.content.split('</think>')[-1].lstrip('\n') %}
|
| 36 |
+
{%- set reasoning_content = message.content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 37 |
+
{%- endif %}
|
| 38 |
+
{%- endif %}
|
| 39 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 40 |
+
{%- if loop.last or (not loop.last and reasoning_content) %}
|
| 41 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content.strip('\n') + '\n</think>\n\n' + content.lstrip('\n') }}
|
| 42 |
+
{%- else %}
|
| 43 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- else %}
|
| 46 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 47 |
+
{%- endif %}
|
| 48 |
+
{%- if message.tool_calls %}
|
| 49 |
+
{%- for tool_call in message.tool_calls %}
|
| 50 |
+
{%- if (loop.first and content) or (not loop.first) %}
|
| 51 |
+
{{- '\n' }}
|
| 52 |
+
{%- endif %}
|
| 53 |
+
{%- if tool_call.function %}
|
| 54 |
+
{%- set tool_call = tool_call.function %}
|
| 55 |
+
{%- endif %}
|
| 56 |
+
{{- '<tool_call>\n{"name": "' }}
|
| 57 |
+
{{- tool_call.name }}
|
| 58 |
+
{{- '", "arguments": ' }}
|
| 59 |
+
{%- if tool_call.arguments is string %}
|
| 60 |
+
{{- tool_call.arguments }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{{- tool_call.arguments | tojson }}
|
| 63 |
+
{%- endif %}
|
| 64 |
+
{{- '}\n</tool_call>' }}
|
| 65 |
+
{%- endfor %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{{- '<|im_end|>\n' }}
|
| 68 |
+
{%- elif message.role == "tool" %}
|
| 69 |
+
{%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
|
| 70 |
+
{{- '<|im_start|>user' }}
|
| 71 |
+
{%- endif %}
|
| 72 |
+
{{- '\n<tool_response>\n' }}
|
| 73 |
+
{{- message.content }}
|
| 74 |
+
{{- '\n</tool_response>' }}
|
| 75 |
+
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
| 76 |
+
{{- '<|im_end|>\n' }}
|
| 77 |
+
{%- endif %}
|
| 78 |
+
{%- endif %}
|
| 79 |
+
{%- endfor %}
|
| 80 |
+
{%- if add_generation_prompt %}
|
| 81 |
+
{{- '<|im_start|>assistant\n' }}
|
| 82 |
+
{%- if enable_thinking is defined and enable_thinking is false %}
|
| 83 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 84 |
+
{%- endif %}
|
| 85 |
+
{%- if use_tts_template is defined and use_tts_template is true %}
|
| 86 |
+
{{- '<|tts_bos|>' }}
|
| 87 |
+
{%- endif %}
|
| 88 |
+
{%- endif %}
|
Realtime-Venus-Audio/config.json
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"MiniCPMO"
|
| 4 |
+
],
|
| 5 |
+
"attention_bias": false,
|
| 6 |
+
"attention_dropout": 0.0,
|
| 7 |
+
"audio_chunk_length": 1.0,
|
| 8 |
+
"audio_config": {
|
| 9 |
+
"_attn_implementation_autoset": true,
|
| 10 |
+
"_name_or_path": "openai/whisper-medium",
|
| 11 |
+
"activation_dropout": 0.0,
|
| 12 |
+
"activation_function": "gelu",
|
| 13 |
+
"apply_spec_augment": false,
|
| 14 |
+
"architectures": [
|
| 15 |
+
"MiniCPMWhisperEncoder"
|
| 16 |
+
],
|
| 17 |
+
"attention_dropout": 0.0,
|
| 18 |
+
"begin_suppress_tokens": [
|
| 19 |
+
220,
|
| 20 |
+
50257
|
| 21 |
+
],
|
| 22 |
+
"bos_token_id": 50257,
|
| 23 |
+
"classifier_proj_size": 256,
|
| 24 |
+
"d_model": 1024,
|
| 25 |
+
"decoder_attention_heads": 16,
|
| 26 |
+
"decoder_ffn_dim": 4096,
|
| 27 |
+
"decoder_layerdrop": 0.0,
|
| 28 |
+
"decoder_layers": 24,
|
| 29 |
+
"decoder_start_token_id": 50258,
|
| 30 |
+
"dropout": 0.0,
|
| 31 |
+
"dtype": "float32",
|
| 32 |
+
"encoder_attention_heads": 16,
|
| 33 |
+
"encoder_ffn_dim": 4096,
|
| 34 |
+
"encoder_layerdrop": 0.0,
|
| 35 |
+
"encoder_layers": 24,
|
| 36 |
+
"eos_token_id": 50257,
|
| 37 |
+
"forced_decoder_ids": [
|
| 38 |
+
[
|
| 39 |
+
1,
|
| 40 |
+
50259
|
| 41 |
+
],
|
| 42 |
+
[
|
| 43 |
+
2,
|
| 44 |
+
50359
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
3,
|
| 48 |
+
50363
|
| 49 |
+
]
|
| 50 |
+
],
|
| 51 |
+
"init_std": 0.02,
|
| 52 |
+
"mask_feature_length": 10,
|
| 53 |
+
"mask_feature_min_masks": 0,
|
| 54 |
+
"mask_feature_prob": 0.0,
|
| 55 |
+
"mask_time_length": 10,
|
| 56 |
+
"mask_time_min_masks": 2,
|
| 57 |
+
"mask_time_prob": 0.05,
|
| 58 |
+
"max_length": 448,
|
| 59 |
+
"max_source_positions": 1500,
|
| 60 |
+
"max_target_positions": 448,
|
| 61 |
+
"median_filter_width": 7,
|
| 62 |
+
"model_type": "whisper",
|
| 63 |
+
"num_hidden_layers": 24,
|
| 64 |
+
"num_mel_bins": 80,
|
| 65 |
+
"pad_token_id": 50257,
|
| 66 |
+
"scale_embedding": false,
|
| 67 |
+
"suppress_tokens": [
|
| 68 |
+
1,
|
| 69 |
+
2,
|
| 70 |
+
7,
|
| 71 |
+
8,
|
| 72 |
+
9,
|
| 73 |
+
10,
|
| 74 |
+
14,
|
| 75 |
+
25,
|
| 76 |
+
26,
|
| 77 |
+
27,
|
| 78 |
+
28,
|
| 79 |
+
29,
|
| 80 |
+
31,
|
| 81 |
+
58,
|
| 82 |
+
59,
|
| 83 |
+
60,
|
| 84 |
+
61,
|
| 85 |
+
62,
|
| 86 |
+
63,
|
| 87 |
+
90,
|
| 88 |
+
91,
|
| 89 |
+
92,
|
| 90 |
+
93,
|
| 91 |
+
359,
|
| 92 |
+
503,
|
| 93 |
+
522,
|
| 94 |
+
542,
|
| 95 |
+
873,
|
| 96 |
+
893,
|
| 97 |
+
902,
|
| 98 |
+
918,
|
| 99 |
+
922,
|
| 100 |
+
931,
|
| 101 |
+
1350,
|
| 102 |
+
1853,
|
| 103 |
+
1982,
|
| 104 |
+
2460,
|
| 105 |
+
2627,
|
| 106 |
+
3246,
|
| 107 |
+
3253,
|
| 108 |
+
3268,
|
| 109 |
+
3536,
|
| 110 |
+
3846,
|
| 111 |
+
3961,
|
| 112 |
+
4183,
|
| 113 |
+
4667,
|
| 114 |
+
6585,
|
| 115 |
+
6647,
|
| 116 |
+
7273,
|
| 117 |
+
9061,
|
| 118 |
+
9383,
|
| 119 |
+
10428,
|
| 120 |
+
10929,
|
| 121 |
+
11938,
|
| 122 |
+
12033,
|
| 123 |
+
12331,
|
| 124 |
+
12562,
|
| 125 |
+
13793,
|
| 126 |
+
14157,
|
| 127 |
+
14635,
|
| 128 |
+
15265,
|
| 129 |
+
15618,
|
| 130 |
+
16553,
|
| 131 |
+
16604,
|
| 132 |
+
18362,
|
| 133 |
+
18956,
|
| 134 |
+
20075,
|
| 135 |
+
21675,
|
| 136 |
+
22520,
|
| 137 |
+
26130,
|
| 138 |
+
26161,
|
| 139 |
+
26435,
|
| 140 |
+
28279,
|
| 141 |
+
29464,
|
| 142 |
+
31650,
|
| 143 |
+
32302,
|
| 144 |
+
32470,
|
| 145 |
+
36865,
|
| 146 |
+
42863,
|
| 147 |
+
47425,
|
| 148 |
+
49870,
|
| 149 |
+
50254,
|
| 150 |
+
50258,
|
| 151 |
+
50358,
|
| 152 |
+
50359,
|
| 153 |
+
50360,
|
| 154 |
+
50361,
|
| 155 |
+
50362
|
| 156 |
+
],
|
| 157 |
+
"use_cache": true,
|
| 158 |
+
"use_weighted_layer_sum": false,
|
| 159 |
+
"vocab_size": 51865
|
| 160 |
+
},
|
| 161 |
+
"audio_pool_step": 5,
|
| 162 |
+
"auto_map": {
|
| 163 |
+
"AutoConfig": "configuration_minicpmo.MiniCPMOConfig",
|
| 164 |
+
"AutoModel": "modeling_minicpmo.MiniCPMO",
|
| 165 |
+
"AutoModelForCausalLM": "modeling_minicpmo.MiniCPMO"
|
| 166 |
+
},
|
| 167 |
+
"batch_vision_input": true,
|
| 168 |
+
"bos_token_id": 151643,
|
| 169 |
+
"drop_vision_last_layer": false,
|
| 170 |
+
"dtype": "bfloat16",
|
| 171 |
+
"eos_token_id": 151645,
|
| 172 |
+
"head_dim": 128,
|
| 173 |
+
"hidden_act": "silu",
|
| 174 |
+
"hidden_size": 4096,
|
| 175 |
+
"image_size": 448,
|
| 176 |
+
"init_audio": true,
|
| 177 |
+
"init_tts": true,
|
| 178 |
+
"init_vision": true,
|
| 179 |
+
"initializer_range": 0.02,
|
| 180 |
+
"intermediate_size": 12288,
|
| 181 |
+
"layer_types": [
|
| 182 |
+
"full_attention",
|
| 183 |
+
"full_attention",
|
| 184 |
+
"full_attention",
|
| 185 |
+
"full_attention",
|
| 186 |
+
"full_attention",
|
| 187 |
+
"full_attention",
|
| 188 |
+
"full_attention",
|
| 189 |
+
"full_attention",
|
| 190 |
+
"full_attention",
|
| 191 |
+
"full_attention",
|
| 192 |
+
"full_attention",
|
| 193 |
+
"full_attention",
|
| 194 |
+
"full_attention",
|
| 195 |
+
"full_attention",
|
| 196 |
+
"full_attention",
|
| 197 |
+
"full_attention",
|
| 198 |
+
"full_attention",
|
| 199 |
+
"full_attention",
|
| 200 |
+
"full_attention",
|
| 201 |
+
"full_attention",
|
| 202 |
+
"full_attention",
|
| 203 |
+
"full_attention",
|
| 204 |
+
"full_attention",
|
| 205 |
+
"full_attention",
|
| 206 |
+
"full_attention",
|
| 207 |
+
"full_attention",
|
| 208 |
+
"full_attention",
|
| 209 |
+
"full_attention",
|
| 210 |
+
"full_attention",
|
| 211 |
+
"full_attention",
|
| 212 |
+
"full_attention",
|
| 213 |
+
"full_attention",
|
| 214 |
+
"full_attention",
|
| 215 |
+
"full_attention",
|
| 216 |
+
"full_attention",
|
| 217 |
+
"full_attention"
|
| 218 |
+
],
|
| 219 |
+
"listen_speak_type": "asr",
|
| 220 |
+
"max_position_embeddings": 40960,
|
| 221 |
+
"max_window_layers": 36,
|
| 222 |
+
"model_type": "minicpmo",
|
| 223 |
+
"num_attention_heads": 32,
|
| 224 |
+
"num_hidden_layers": 36,
|
| 225 |
+
"num_key_value_heads": 8,
|
| 226 |
+
"patch_size": 14,
|
| 227 |
+
"query_num": 64,
|
| 228 |
+
"rms_norm_eps": 1e-06,
|
| 229 |
+
"rope_scaling": null,
|
| 230 |
+
"rope_theta": 1000000,
|
| 231 |
+
"slice_config": {
|
| 232 |
+
"max_slice_nums": 1,
|
| 233 |
+
"model_type": "minicpmv",
|
| 234 |
+
"patch_size": 14,
|
| 235 |
+
"scale_resolution": 448
|
| 236 |
+
},
|
| 237 |
+
"slice_mode": true,
|
| 238 |
+
"sliding_window": null,
|
| 239 |
+
"stream_input": true,
|
| 240 |
+
"tie_word_embeddings": false,
|
| 241 |
+
"transformers_version": "4.57.6",
|
| 242 |
+
"tts_config": {
|
| 243 |
+
"_attn_implementation_autoset": true,
|
| 244 |
+
"attention_type": "full_attention",
|
| 245 |
+
"attn_implementation": "eager",
|
| 246 |
+
"audio_bos_token_id": 151687,
|
| 247 |
+
"audio_tokenizer_sample_rate": 16000,
|
| 248 |
+
"audio_tokenizer_type": "s3tokenizer",
|
| 249 |
+
"aug_layer_loss_weight": false,
|
| 250 |
+
"aug_loss_weight": false,
|
| 251 |
+
"backbone_model": "llama",
|
| 252 |
+
"condition_type": "hidden_text_merge",
|
| 253 |
+
"cosyvoice_config_path": null,
|
| 254 |
+
"cosyvoice_model_dir": null,
|
| 255 |
+
"filter_tts_loss": false,
|
| 256 |
+
"hidden_act": "silu",
|
| 257 |
+
"hidden_size": 768,
|
| 258 |
+
"interleaved": false,
|
| 259 |
+
"intermediate_size": 3072,
|
| 260 |
+
"llm_dim": 4096,
|
| 261 |
+
"llm_dim_model_base": 256,
|
| 262 |
+
"llm_down_scale": false,
|
| 263 |
+
"llm_hidden_size": 4096,
|
| 264 |
+
"llm_intermediate_size": 768,
|
| 265 |
+
"long_weight": 0.1,
|
| 266 |
+
"max_position_embeddings": 4096,
|
| 267 |
+
"model_type": "minicpmtts",
|
| 268 |
+
"normalize_projected_hidden": true,
|
| 269 |
+
"num_attention_heads": 12,
|
| 270 |
+
"num_audio_tokens": 6562,
|
| 271 |
+
"num_hidden_layers": 20,
|
| 272 |
+
"num_key_value_heads": 12,
|
| 273 |
+
"num_mel_bins": 100,
|
| 274 |
+
"num_text_tokens": 152064,
|
| 275 |
+
"num_vq": 1,
|
| 276 |
+
"projector_type": "mlp",
|
| 277 |
+
"recomputed_chunks": 1,
|
| 278 |
+
"s3_stream_chunk_size": 25,
|
| 279 |
+
"s3_stream_generate": false,
|
| 280 |
+
"s3_stream_n_timesteps": 10,
|
| 281 |
+
"s3_stream_prelook_size": 3,
|
| 282 |
+
"short_weight": 0.1,
|
| 283 |
+
"streaming": false,
|
| 284 |
+
"streaming_audio_chunk_size": 50,
|
| 285 |
+
"streaming_sliding_window": false,
|
| 286 |
+
"streaming_sliding_window_audio_frame_rate": 50,
|
| 287 |
+
"streaming_sliding_window_audio_init_text_length": 10,
|
| 288 |
+
"streaming_sliding_window_audio_window_size": 300,
|
| 289 |
+
"streaming_sliding_window_average_speed": 5,
|
| 290 |
+
"streaming_sliding_window_fast_speed": 7,
|
| 291 |
+
"streaming_sliding_window_max_text_len": 500,
|
| 292 |
+
"streaming_sliding_window_slow_speed": 3,
|
| 293 |
+
"streaming_sliding_window_text_window_size": 50,
|
| 294 |
+
"streaming_text_chunk_max": 7,
|
| 295 |
+
"streaming_text_chunk_min": 3,
|
| 296 |
+
"streaming_text_reserved_len": 300,
|
| 297 |
+
"text_eos_token_id": 151692,
|
| 298 |
+
"tts_filter_loss_fix": false,
|
| 299 |
+
"use_llm_hidden_state": false,
|
| 300 |
+
"use_text": true,
|
| 301 |
+
"window_size": 2
|
| 302 |
+
},
|
| 303 |
+
"use_cache": true,
|
| 304 |
+
"use_image_id": true,
|
| 305 |
+
"use_sliding_window": false,
|
| 306 |
+
"version": "4.5",
|
| 307 |
+
"vision_batch_size": 16,
|
| 308 |
+
"vision_config": {
|
| 309 |
+
"_attn_implementation_autoset": true,
|
| 310 |
+
"attention_dropout": 0.0,
|
| 311 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 312 |
+
"hidden_size": 1152,
|
| 313 |
+
"image_size": 980,
|
| 314 |
+
"intermediate_size": 4304,
|
| 315 |
+
"layer_norm_eps": 1e-06,
|
| 316 |
+
"model_type": "siglip_vision_model",
|
| 317 |
+
"num_attention_heads": 16,
|
| 318 |
+
"num_channels": 3,
|
| 319 |
+
"num_hidden_layers": 27,
|
| 320 |
+
"patch_size": 14
|
| 321 |
+
},
|
| 322 |
+
"vocab_size": 151752
|
| 323 |
+
}
|
Realtime-Venus-Audio/configuration_minicpmo.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# Copyright 2026 The OpenBMB Team. All rights reserved.
|
| 4 |
+
# Modifications Copyright 2026 The Realtime-Venus Team.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
from typing import Union
|
| 20 |
+
|
| 21 |
+
from transformers import PretrainedConfig
|
| 22 |
+
from transformers import Qwen3Config
|
| 23 |
+
from transformers import WhisperConfig
|
| 24 |
+
from transformers.utils import logging
|
| 25 |
+
|
| 26 |
+
from .modeling_navit_siglip import SiglipVisionConfig
|
| 27 |
+
|
| 28 |
+
logger = logging.get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class MiniCPMVSliceConfig(PretrainedConfig):
|
| 32 |
+
model_type = "minicpmv"
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
patch_size=14,
|
| 37 |
+
max_slice_nums=9,
|
| 38 |
+
scale_resolution=448,
|
| 39 |
+
**kwargs,
|
| 40 |
+
):
|
| 41 |
+
super().__init__(**kwargs)
|
| 42 |
+
self.patch_size = patch_size
|
| 43 |
+
self.max_slice_nums = max_slice_nums
|
| 44 |
+
self.scale_resolution = scale_resolution
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig":
|
| 48 |
+
cls._set_token_in_kwargs(kwargs)
|
| 49 |
+
|
| 50 |
+
config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
|
| 51 |
+
|
| 52 |
+
if config_dict.get("model_type") == "minicpmv":
|
| 53 |
+
config_dict = config_dict["slice_config"]
|
| 54 |
+
|
| 55 |
+
if "model_type" in config_dict and hasattr(cls, "model_type") and config_dict["model_type"] != cls.model_type:
|
| 56 |
+
logger.warning(
|
| 57 |
+
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
| 58 |
+
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
return cls.from_dict(config_dict, **kwargs)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class MiniCPMTTSConfig(PretrainedConfig):
|
| 65 |
+
model_type = "minicpmtts"
|
| 66 |
+
|
| 67 |
+
def __init__(
|
| 68 |
+
self,
|
| 69 |
+
llm_dim: int = 2560,
|
| 70 |
+
llm_intermediate_size: int = 768,
|
| 71 |
+
llm_down_scale: bool = False,
|
| 72 |
+
llm_dim_model_base: int = 256,
|
| 73 |
+
projector_type: str = "mlp",
|
| 74 |
+
hidden_act: str = "silu",
|
| 75 |
+
aug_loss_weight: bool = False,
|
| 76 |
+
aug_layer_loss_weight: bool = False,
|
| 77 |
+
filter_tts_loss: bool = False,
|
| 78 |
+
tts_filter_loss_fix: bool = False,
|
| 79 |
+
long_weight: float = 0.1,
|
| 80 |
+
short_weight: float = 0.1,
|
| 81 |
+
hidden_size: int = 768,
|
| 82 |
+
intermediate_size: int = 3072,
|
| 83 |
+
num_attention_heads: int = 12,
|
| 84 |
+
num_hidden_layers: int = 20,
|
| 85 |
+
num_key_value_heads: int = 12,
|
| 86 |
+
max_position_embeddings: int = 4096,
|
| 87 |
+
num_audio_tokens: int = 4097,
|
| 88 |
+
num_text_tokens: int = 21178,
|
| 89 |
+
num_mel_bins: int = 100,
|
| 90 |
+
num_vq: int = 1,
|
| 91 |
+
use_llm_hidden_state: bool = False,
|
| 92 |
+
audio_bos_token_id: int = 21132,
|
| 93 |
+
text_eos_token_id: int = 21133,
|
| 94 |
+
use_text: bool = True,
|
| 95 |
+
streaming: bool = False,
|
| 96 |
+
streaming_text_chunk_min: int = 3,
|
| 97 |
+
streaming_text_chunk_max: int = 7,
|
| 98 |
+
streaming_text_reserved_len: int = 300,
|
| 99 |
+
streaming_audio_chunk_size: int = 50,
|
| 100 |
+
attn_implementation: str = "sdpa",
|
| 101 |
+
condition_type: str = "llm_hidden",
|
| 102 |
+
backbone_model: str = "llama",
|
| 103 |
+
audio_tokenizer_type: str = "wavtokenizer",
|
| 104 |
+
audio_tokenizer_sample_rate: int = 24000,
|
| 105 |
+
streaming_sliding_window: bool = False,
|
| 106 |
+
streaming_sliding_window_max_text_len: int = 500,
|
| 107 |
+
streaming_sliding_window_average_speed: int = 5,
|
| 108 |
+
streaming_sliding_window_fast_speed: int = 7,
|
| 109 |
+
streaming_sliding_window_slow_speed: int = 3,
|
| 110 |
+
streaming_sliding_window_audio_frame_rate: int = 50,
|
| 111 |
+
streaming_sliding_window_audio_init_text_length: int = 10,
|
| 112 |
+
streaming_sliding_window_audio_window_size: int = 300,
|
| 113 |
+
normalize_projected_hidden: bool = False,
|
| 114 |
+
interleaved: bool = False,
|
| 115 |
+
attention_type: str = "sliding_recompute",
|
| 116 |
+
recomputed_chunks: int = 1,
|
| 117 |
+
window_size: int = 2,
|
| 118 |
+
**kwargs,
|
| 119 |
+
):
|
| 120 |
+
super().__init__(**kwargs)
|
| 121 |
+
|
| 122 |
+
self.llm_dim = llm_dim
|
| 123 |
+
self.llm_hidden_size = llm_dim
|
| 124 |
+
self.llm_intermediate_size = llm_intermediate_size
|
| 125 |
+
self.llm_down_scale = llm_down_scale
|
| 126 |
+
self.llm_dim_model_base = llm_dim_model_base
|
| 127 |
+
self.projector_type = projector_type
|
| 128 |
+
self.aug_loss_weight = aug_loss_weight
|
| 129 |
+
self.aug_layer_loss_weight = aug_layer_loss_weight
|
| 130 |
+
self.tts_filter_loss_fix = tts_filter_loss_fix
|
| 131 |
+
self.filter_tts_loss = filter_tts_loss
|
| 132 |
+
self.long_weight = long_weight
|
| 133 |
+
self.short_weight = short_weight
|
| 134 |
+
self.hidden_act = hidden_act
|
| 135 |
+
|
| 136 |
+
self.hidden_size = hidden_size
|
| 137 |
+
self.intermediate_size = intermediate_size
|
| 138 |
+
self.num_attention_heads = num_attention_heads
|
| 139 |
+
self.num_hidden_layers = num_hidden_layers
|
| 140 |
+
self.num_key_value_heads = num_key_value_heads
|
| 141 |
+
self.max_position_embeddings = max_position_embeddings
|
| 142 |
+
self.num_audio_tokens = num_audio_tokens
|
| 143 |
+
self.num_text_tokens = num_text_tokens
|
| 144 |
+
self.num_mel_bins = num_mel_bins
|
| 145 |
+
self.num_vq = num_vq
|
| 146 |
+
self.use_llm_hidden_state = use_llm_hidden_state
|
| 147 |
+
self.audio_bos_token_id = audio_bos_token_id
|
| 148 |
+
self.text_eos_token_id = text_eos_token_id
|
| 149 |
+
self.use_text = use_text
|
| 150 |
+
self.streaming = streaming
|
| 151 |
+
self.streaming_text_chunk_min = streaming_text_chunk_min
|
| 152 |
+
self.streaming_text_chunk_max = streaming_text_chunk_max
|
| 153 |
+
self.streaming_text_reserved_len = streaming_text_reserved_len
|
| 154 |
+
self.streaming_audio_chunk_size = streaming_audio_chunk_size
|
| 155 |
+
self.attn_implementation = attn_implementation
|
| 156 |
+
self.condition_type = condition_type
|
| 157 |
+
self.backbone_model = backbone_model
|
| 158 |
+
self.audio_tokenizer_type = audio_tokenizer_type
|
| 159 |
+
self.audio_tokenizer_sample_rate = audio_tokenizer_sample_rate
|
| 160 |
+
|
| 161 |
+
self.streaming_sliding_window = streaming_sliding_window
|
| 162 |
+
self.streaming_sliding_window_max_text_len = streaming_sliding_window_max_text_len
|
| 163 |
+
self.streaming_sliding_window_average_speed = streaming_sliding_window_average_speed
|
| 164 |
+
self.streaming_sliding_window_fast_speed = streaming_sliding_window_fast_speed
|
| 165 |
+
self.streaming_sliding_window_slow_speed = streaming_sliding_window_slow_speed
|
| 166 |
+
self.streaming_sliding_window_audio_frame_rate = streaming_sliding_window_audio_frame_rate
|
| 167 |
+
self.streaming_sliding_window_audio_init_text_length = streaming_sliding_window_audio_init_text_length
|
| 168 |
+
self.streaming_sliding_window_audio_window_size = streaming_sliding_window_audio_window_size
|
| 169 |
+
|
| 170 |
+
self.normalize_projected_hidden = normalize_projected_hidden
|
| 171 |
+
|
| 172 |
+
self.interleaved = interleaved
|
| 173 |
+
self.attention_type = attention_type
|
| 174 |
+
self.recomputed_chunks = recomputed_chunks
|
| 175 |
+
self.window_size = window_size
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
class MiniCPMOConfig(Qwen3Config):
|
| 179 |
+
model_type = "minicpmo"
|
| 180 |
+
keys_to_ignore_at_inference = ["past_key_values"]
|
| 181 |
+
|
| 182 |
+
default_vision_config = {
|
| 183 |
+
"hidden_size": 1152,
|
| 184 |
+
"image_size": 980,
|
| 185 |
+
"intermediate_size": 4304,
|
| 186 |
+
"model_type": "siglip",
|
| 187 |
+
"num_attention_heads": 16,
|
| 188 |
+
"num_hidden_layers": 27,
|
| 189 |
+
"patch_size": 14,
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
def __init__(
|
| 193 |
+
self,
|
| 194 |
+
use_cache=True,
|
| 195 |
+
query_num=64,
|
| 196 |
+
image_size=448,
|
| 197 |
+
drop_vision_last_layer=True,
|
| 198 |
+
batch_vision_input=True,
|
| 199 |
+
slice_config=None,
|
| 200 |
+
vision_config=None,
|
| 201 |
+
audio_config=None,
|
| 202 |
+
tts_config=None,
|
| 203 |
+
use_image_id=True,
|
| 204 |
+
vision_batch_size=16,
|
| 205 |
+
audio_pool_step=5,
|
| 206 |
+
audio_chunk_length=1.0,
|
| 207 |
+
stream_input=False,
|
| 208 |
+
listen_speak_type="asr",
|
| 209 |
+
init_vision=True,
|
| 210 |
+
init_audio=True,
|
| 211 |
+
init_tts=True,
|
| 212 |
+
**kwargs,
|
| 213 |
+
):
|
| 214 |
+
self.use_cache = use_cache
|
| 215 |
+
self.query_num = query_num
|
| 216 |
+
self.image_size = image_size
|
| 217 |
+
self.drop_vision_last_layer = drop_vision_last_layer
|
| 218 |
+
self.batch_vision_input = batch_vision_input
|
| 219 |
+
self.use_image_id = use_image_id
|
| 220 |
+
self.vision_batch_size = vision_batch_size
|
| 221 |
+
self.audio_pool_step = audio_pool_step
|
| 222 |
+
self.audio_chunk_length = audio_chunk_length
|
| 223 |
+
self.stream_input = stream_input
|
| 224 |
+
self.listen_speak_type = listen_speak_type
|
| 225 |
+
|
| 226 |
+
self.init_vision = init_vision
|
| 227 |
+
self.init_audio = init_audio
|
| 228 |
+
self.init_tts = init_tts
|
| 229 |
+
|
| 230 |
+
if slice_config is None:
|
| 231 |
+
self.slice_config = MiniCPMVSliceConfig(max_slice_nums=1)
|
| 232 |
+
else:
|
| 233 |
+
self.slice_config = MiniCPMVSliceConfig(**slice_config)
|
| 234 |
+
self.slice_mode = True
|
| 235 |
+
|
| 236 |
+
# same as HuggingFaceM4/siglip-so400m-14-980-flash-attn2-navit add tgt_sizes
|
| 237 |
+
if vision_config is None:
|
| 238 |
+
self.vision_config = SiglipVisionConfig(**self.default_vision_config)
|
| 239 |
+
logger.info("vision_config is None, using default vision config")
|
| 240 |
+
elif isinstance(vision_config, dict):
|
| 241 |
+
self.vision_config = SiglipVisionConfig(**vision_config)
|
| 242 |
+
elif isinstance(vision_config, SiglipVisionConfig):
|
| 243 |
+
self.vision_config = vision_config
|
| 244 |
+
|
| 245 |
+
if audio_config is None:
|
| 246 |
+
self.audio_config = WhisperConfig()
|
| 247 |
+
elif isinstance(audio_config, dict):
|
| 248 |
+
self.audio_config = WhisperConfig(**audio_config)
|
| 249 |
+
elif isinstance(audio_config, WhisperConfig):
|
| 250 |
+
self.audio_config = audio_config
|
| 251 |
+
|
| 252 |
+
if tts_config is None:
|
| 253 |
+
self.tts_config = MiniCPMTTSConfig()
|
| 254 |
+
elif isinstance(tts_config, dict):
|
| 255 |
+
self.tts_config = MiniCPMTTSConfig(**tts_config)
|
| 256 |
+
elif isinstance(tts_config, MiniCPMTTSConfig):
|
| 257 |
+
self.tts_config = tts_config
|
| 258 |
+
|
| 259 |
+
self.patch_size = self.vision_config.patch_size
|
| 260 |
+
|
| 261 |
+
super().__init__(**kwargs)
|
Realtime-Venus-Audio/generation_config.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
151645,
|
| 6 |
+
151643
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 151643,
|
| 9 |
+
"temperature": 0.6,
|
| 10 |
+
"top_k": 20,
|
| 11 |
+
"top_p": 0.95,
|
| 12 |
+
"transformers_version": "4.57.6"
|
| 13 |
+
}
|
Realtime-Venus-Audio/merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Realtime-Venus-Audio/model-00001-of-00004.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dc11f3b0ed783e85efc3ac8acf43fefd7d86fd1e6837209ba2722cf6d3b6ccc7
|
| 3 |
+
size 4900750800
|
Realtime-Venus-Audio/model-00002-of-00004.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bb0d68f6a34d45256516730cc07a30a674948bc444481784bc2de89eca275766
|
| 3 |
+
size 4915960936
|
Realtime-Venus-Audio/model-00003-of-00004.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6377dca09d445c0343690dad23f86e1b2084ca010206d4c3fbb94e43711f9040
|
| 3 |
+
size 4983069056
|
Realtime-Venus-Audio/model-00004-of-00004.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:188d68d0d1771083cbf7478e82c4493eae3e4e0816759d6005d16e2a150acecf
|
| 3 |
+
size 3944022140
|
Realtime-Venus-Audio/model.safetensors.index.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Realtime-Venus-Audio/modeling_minicpmo.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Realtime-Venus-Audio/modeling_navit_siglip.py
ADDED
|
@@ -0,0 +1,981 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# coding=utf-8
|
| 2 |
+
# Copyright 2024 Google AI and The HuggingFace 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 |
+
"""PyTorch Siglip model."""
|
| 16 |
+
# Copied from HuggingFaceM4/siglip-so400m-14-980-flash-attn2-navit and add tgt_sizes
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
import math
|
| 20 |
+
import os
|
| 21 |
+
import warnings
|
| 22 |
+
from dataclasses import dataclass
|
| 23 |
+
from typing import Optional
|
| 24 |
+
from typing import Tuple
|
| 25 |
+
from typing import Union
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
import torch.utils.checkpoint
|
| 31 |
+
from torch import nn
|
| 32 |
+
from torch.nn.init import _calculate_fan_in_and_fan_out
|
| 33 |
+
from transformers.activations import ACT2FN
|
| 34 |
+
from transformers.configuration_utils import PretrainedConfig
|
| 35 |
+
from transformers.modeling_attn_mask_utils import _prepare_4d_attention_mask
|
| 36 |
+
from transformers.modeling_outputs import BaseModelOutput
|
| 37 |
+
from transformers.modeling_outputs import BaseModelOutputWithPooling
|
| 38 |
+
from transformers.modeling_utils import PreTrainedModel
|
| 39 |
+
from transformers.utils import add_start_docstrings
|
| 40 |
+
from transformers.utils import add_start_docstrings_to_model_forward
|
| 41 |
+
from transformers.utils import is_flash_attn_2_available
|
| 42 |
+
from transformers.utils import logging
|
| 43 |
+
from transformers.utils import ModelOutput
|
| 44 |
+
from transformers.utils import replace_return_docstrings
|
| 45 |
+
|
| 46 |
+
logger = logging.get_logger(__name__)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class SiglipVisionConfig(PretrainedConfig):
|
| 50 |
+
r"""
|
| 51 |
+
This is the configuration class to store the configuration of a [`SiglipVisionModel`]. It is used to instantiate a
|
| 52 |
+
Siglip vision encoder according to the specified arguments, defining the model architecture. Instantiating a
|
| 53 |
+
configuration with the defaults will yield a similar configuration to that of the vision encoder of the Siglip
|
| 54 |
+
[google/siglip-base-patch16-224](https://huggingface.co/google/siglip-base-patch16-224) architecture.
|
| 55 |
+
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
| 56 |
+
documentation from [`PretrainedConfig`] for more information.
|
| 57 |
+
Args:
|
| 58 |
+
hidden_size (`int`, *optional*, defaults to 768):
|
| 59 |
+
Dimensionality of the encoder layers and the pooler layer.
|
| 60 |
+
intermediate_size (`int`, *optional*, defaults to 3072):
|
| 61 |
+
Dimensionality of the "intermediate" (i.e., feed-forward) layer in the Transformer encoder.
|
| 62 |
+
num_hidden_layers (`int`, *optional*, defaults to 12):
|
| 63 |
+
Number of hidden layers in the Transformer encoder.
|
| 64 |
+
num_attention_heads (`int`, *optional*, defaults to 12):
|
| 65 |
+
Number of attention heads for each attention layer in the Transformer encoder.
|
| 66 |
+
num_channels (`int`, *optional*, defaults to 3):
|
| 67 |
+
Number of channels in the input images.
|
| 68 |
+
image_size (`int`, *optional*, defaults to 224):
|
| 69 |
+
The size (resolution) of each image.
|
| 70 |
+
patch_size (`int`, *optional*, defaults to 16):
|
| 71 |
+
The size (resolution) of each patch.
|
| 72 |
+
hidden_act (`str` or `function`, *optional*, defaults to `"gelu_pytorch_tanh"`):
|
| 73 |
+
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
| 74 |
+
`"relu"`, `"selu"` and `"gelu_new"` ``"quick_gelu"` are supported.
|
| 75 |
+
layer_norm_eps (`float`, *optional*, defaults to 1e-06):
|
| 76 |
+
The epsilon used by the layer normalization layers.
|
| 77 |
+
attention_dropout (`float`, *optional*, defaults to 0.0):
|
| 78 |
+
The dropout ratio for the attention probabilities.
|
| 79 |
+
Example:
|
| 80 |
+
```python
|
| 81 |
+
>>> from transformers import SiglipVisionConfig, SiglipVisionModel
|
| 82 |
+
>>> # Initializing a SiglipVisionConfig with google/siglip-base-patch16-224 style configuration
|
| 83 |
+
>>> configuration = SiglipVisionConfig()
|
| 84 |
+
>>> # Initializing a SiglipVisionModel (with random weights) from the google/siglip-base-patch16-224 style configuration
|
| 85 |
+
>>> model = SiglipVisionModel(configuration)
|
| 86 |
+
>>> # Accessing the model configuration
|
| 87 |
+
>>> configuration = model.config
|
| 88 |
+
```"""
|
| 89 |
+
|
| 90 |
+
model_type = "siglip_vision_model"
|
| 91 |
+
|
| 92 |
+
def __init__(
|
| 93 |
+
self,
|
| 94 |
+
hidden_size=768,
|
| 95 |
+
intermediate_size=3072,
|
| 96 |
+
num_hidden_layers=12,
|
| 97 |
+
num_attention_heads=12,
|
| 98 |
+
num_channels=3,
|
| 99 |
+
image_size=224,
|
| 100 |
+
patch_size=16,
|
| 101 |
+
hidden_act="gelu_pytorch_tanh",
|
| 102 |
+
layer_norm_eps=1e-6,
|
| 103 |
+
attention_dropout=0.0,
|
| 104 |
+
**kwargs,
|
| 105 |
+
):
|
| 106 |
+
super().__init__(**kwargs)
|
| 107 |
+
|
| 108 |
+
self.hidden_size = hidden_size
|
| 109 |
+
self.intermediate_size = intermediate_size
|
| 110 |
+
self.num_hidden_layers = num_hidden_layers
|
| 111 |
+
self.num_attention_heads = num_attention_heads
|
| 112 |
+
self.num_channels = num_channels
|
| 113 |
+
self.patch_size = patch_size
|
| 114 |
+
self.image_size = image_size
|
| 115 |
+
self.attention_dropout = attention_dropout
|
| 116 |
+
self.layer_norm_eps = layer_norm_eps
|
| 117 |
+
self.hidden_act = hidden_act
|
| 118 |
+
|
| 119 |
+
@classmethod
|
| 120 |
+
def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig":
|
| 121 |
+
cls._set_token_in_kwargs(kwargs)
|
| 122 |
+
|
| 123 |
+
config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
|
| 124 |
+
|
| 125 |
+
# get the vision config dict if we are loading from SiglipConfig
|
| 126 |
+
if config_dict.get("model_type") == "siglip":
|
| 127 |
+
config_dict = config_dict["vision_config"]
|
| 128 |
+
|
| 129 |
+
if "model_type" in config_dict and hasattr(cls, "model_type") and config_dict["model_type"] != cls.model_type:
|
| 130 |
+
logger.warning(
|
| 131 |
+
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
| 132 |
+
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
return cls.from_dict(config_dict, **kwargs)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
_CHECKPOINT_FOR_DOC = "google/siglip-base-patch16-224"
|
| 139 |
+
|
| 140 |
+
SIGLIP_PRETRAINED_MODEL_ARCHIVE_LIST = [
|
| 141 |
+
"google/siglip-base-patch16-224",
|
| 142 |
+
# See all SigLIP models at https://huggingface.co/models?filter=siglip
|
| 143 |
+
]
|
| 144 |
+
|
| 145 |
+
if is_flash_attn_2_available():
|
| 146 |
+
from flash_attn import flash_attn_func
|
| 147 |
+
from flash_attn import flash_attn_varlen_func
|
| 148 |
+
from flash_attn.bert_padding import index_first_axis # noqa
|
| 149 |
+
from flash_attn.bert_padding import pad_input
|
| 150 |
+
from flash_attn.bert_padding import unpad_input
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
# Copied from transformers.models.llama.modeling_llama._get_unpad_data
|
| 154 |
+
def _get_unpad_data(attention_mask):
|
| 155 |
+
seqlens_in_batch = attention_mask.sum(dim=-1, dtype=torch.int32)
|
| 156 |
+
indices = torch.nonzero(attention_mask.flatten(), as_tuple=False).flatten()
|
| 157 |
+
max_seqlen_in_batch = seqlens_in_batch.max().item()
|
| 158 |
+
cu_seqlens = F.pad(torch.cumsum(seqlens_in_batch, dim=0, dtype=torch.torch.int32), (1, 0))
|
| 159 |
+
return (
|
| 160 |
+
indices,
|
| 161 |
+
cu_seqlens,
|
| 162 |
+
max_seqlen_in_batch,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def _trunc_normal_(tensor, mean, std, a, b):
|
| 167 |
+
# Cut & paste from PyTorch official master until it's in a few official releases - RW
|
| 168 |
+
# Method based on https://people.sc.fsu.edu/~jburkardt/presentations/truncated_normal.pdf
|
| 169 |
+
def norm_cdf(x):
|
| 170 |
+
# Computes standard normal cumulative distribution function
|
| 171 |
+
return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0
|
| 172 |
+
|
| 173 |
+
if (mean < a - 2 * std) or (mean > b + 2 * std):
|
| 174 |
+
warnings.warn(
|
| 175 |
+
"mean is more than 2 std from [a, b] in nn.init.trunc_normal_. "
|
| 176 |
+
"The distribution of values may be incorrect.",
|
| 177 |
+
stacklevel=2,
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
# Values are generated by using a truncated uniform distribution and
|
| 181 |
+
# then using the inverse CDF for the normal distribution.
|
| 182 |
+
# Get upper and lower cdf values
|
| 183 |
+
l = norm_cdf((a - mean) / std)
|
| 184 |
+
u = norm_cdf((b - mean) / std)
|
| 185 |
+
|
| 186 |
+
# Uniformly fill tensor with values from [l, u], then translate to
|
| 187 |
+
# [2l-1, 2u-1].
|
| 188 |
+
tensor.uniform_(2 * l - 1, 2 * u - 1)
|
| 189 |
+
|
| 190 |
+
# Use inverse cdf transform for normal distribution to get truncated
|
| 191 |
+
# standard normal
|
| 192 |
+
if tensor.dtype in [torch.float16, torch.bfloat16]:
|
| 193 |
+
# The `erfinv_` op is not (yet?) defined in float16+cpu, bfloat16+gpu
|
| 194 |
+
og_dtype = tensor.dtype
|
| 195 |
+
tensor = tensor.to(torch.float32)
|
| 196 |
+
tensor.erfinv_()
|
| 197 |
+
tensor = tensor.to(og_dtype)
|
| 198 |
+
else:
|
| 199 |
+
tensor.erfinv_()
|
| 200 |
+
|
| 201 |
+
# Transform to proper mean, std
|
| 202 |
+
tensor.mul_(std * math.sqrt(2.0))
|
| 203 |
+
tensor.add_(mean)
|
| 204 |
+
|
| 205 |
+
# Clamp to ensure it's in the proper range
|
| 206 |
+
if tensor.dtype == torch.float16:
|
| 207 |
+
# The `clamp_` op is not (yet?) defined in float16+cpu
|
| 208 |
+
tensor = tensor.to(torch.float32)
|
| 209 |
+
tensor.clamp_(min=a, max=b)
|
| 210 |
+
tensor = tensor.to(torch.float16)
|
| 211 |
+
else:
|
| 212 |
+
tensor.clamp_(min=a, max=b)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
def trunc_normal_tf_(
|
| 216 |
+
tensor: torch.Tensor,
|
| 217 |
+
mean: float = 0.0,
|
| 218 |
+
std: float = 1.0,
|
| 219 |
+
a: float = -2.0,
|
| 220 |
+
b: float = 2.0,
|
| 221 |
+
) -> torch.Tensor:
|
| 222 |
+
"""Fills the input Tensor with values drawn from a truncated
|
| 223 |
+
normal distribution. The values are effectively drawn from the
|
| 224 |
+
normal distribution :math:`\\mathcal{N}(\text{mean}, \text{std}^2)`
|
| 225 |
+
with values outside :math:`[a, b]` redrawn until they are within
|
| 226 |
+
the bounds. The method used for generating the random values works
|
| 227 |
+
best when :math:`a \\leq \text{mean} \\leq b`.
|
| 228 |
+
NOTE: this 'tf' variant behaves closer to Tensorflow / JAX impl where the
|
| 229 |
+
bounds [a, b] are applied when sampling the normal distribution with mean=0, std=1.0
|
| 230 |
+
and the result is subsquently scaled and shifted by the mean and std args.
|
| 231 |
+
Args:
|
| 232 |
+
tensor: an n-dimensional `torch.Tensor`
|
| 233 |
+
mean: the mean of the normal distribution
|
| 234 |
+
std: the standard deviation of the normal distribution
|
| 235 |
+
a: the minimum cutoff value
|
| 236 |
+
b: the maximum cutoff value
|
| 237 |
+
"""
|
| 238 |
+
with torch.no_grad():
|
| 239 |
+
_trunc_normal_(tensor, 0, 1.0, a, b)
|
| 240 |
+
tensor.mul_(std).add_(mean)
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def variance_scaling_(tensor, scale=1.0, mode="fan_in", distribution="normal"):
|
| 244 |
+
fan_in, fan_out = _calculate_fan_in_and_fan_out(tensor)
|
| 245 |
+
if mode == "fan_in":
|
| 246 |
+
denom = fan_in
|
| 247 |
+
elif mode == "fan_out":
|
| 248 |
+
denom = fan_out
|
| 249 |
+
elif mode == "fan_avg":
|
| 250 |
+
denom = (fan_in + fan_out) / 2
|
| 251 |
+
|
| 252 |
+
variance = scale / denom
|
| 253 |
+
|
| 254 |
+
if distribution == "truncated_normal":
|
| 255 |
+
# constant is stddev of standard normal truncated to (-2, 2)
|
| 256 |
+
trunc_normal_tf_(tensor, std=math.sqrt(variance) / 0.87962566103423978)
|
| 257 |
+
elif distribution == "normal":
|
| 258 |
+
with torch.no_grad():
|
| 259 |
+
tensor.normal_(std=math.sqrt(variance))
|
| 260 |
+
elif distribution == "uniform":
|
| 261 |
+
bound = math.sqrt(3 * variance)
|
| 262 |
+
with torch.no_grad():
|
| 263 |
+
tensor.uniform_(-bound, bound)
|
| 264 |
+
else:
|
| 265 |
+
raise ValueError(f"invalid distribution {distribution}")
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
def lecun_normal_(tensor):
|
| 269 |
+
variance_scaling_(tensor, mode="fan_in", distribution="truncated_normal")
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def default_flax_embed_init(tensor):
|
| 273 |
+
variance_scaling_(tensor, mode="fan_in", distribution="normal")
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
@dataclass
|
| 277 |
+
# Copied from transformers.models.clip.modeling_clip.CLIPVisionModelOutput with CLIP->Siglip
|
| 278 |
+
class SiglipVisionModelOutput(ModelOutput):
|
| 279 |
+
"""
|
| 280 |
+
Base class for vision model's outputs that also contains image embeddings of the pooling of the last hidden states.
|
| 281 |
+
Args:
|
| 282 |
+
image_embeds (`torch.FloatTensor` of shape `(batch_size, output_dim)` *optional* returned when model is initialized with `with_projection=True`):
|
| 283 |
+
The image embeddings obtained by applying the projection layer to the pooler_output.
|
| 284 |
+
last_hidden_state (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`):
|
| 285 |
+
Sequence of hidden-states at the output of the last layer of the model.
|
| 286 |
+
hidden_states (`tuple(torch.FloatTensor)`, *optional*, returned when `output_hidden_states=True` is passed or when `config.output_hidden_states=True`):
|
| 287 |
+
Tuple of `torch.FloatTensor` (one for the output of the embeddings, if the model has an embedding layer, +
|
| 288 |
+
one for the output of each layer) of shape `(batch_size, sequence_length, hidden_size)`.
|
| 289 |
+
Hidden-states of the model at the output of each layer plus the optional initial embedding outputs.
|
| 290 |
+
attentions (`tuple(torch.FloatTensor)`, *optional*, returned when `output_attentions=True` is passed or when `config.output_attentions=True`):
|
| 291 |
+
Tuple of `torch.FloatTensor` (one for each layer) of shape `(batch_size, num_heads, sequence_length,
|
| 292 |
+
sequence_length)`.
|
| 293 |
+
Attentions weights after the attention softmax, used to compute the weighted average in the self-attention
|
| 294 |
+
heads.
|
| 295 |
+
"""
|
| 296 |
+
|
| 297 |
+
image_embeds: Optional[torch.FloatTensor] = None
|
| 298 |
+
last_hidden_state: torch.FloatTensor = None
|
| 299 |
+
hidden_states: Optional[Tuple[torch.FloatTensor]] = None
|
| 300 |
+
attentions: Optional[Tuple[torch.FloatTensor]] = None
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
class SiglipVisionEmbeddings(nn.Module):
|
| 304 |
+
def __init__(self, config: SiglipVisionConfig):
|
| 305 |
+
super().__init__()
|
| 306 |
+
self.config = config
|
| 307 |
+
self.embed_dim = config.hidden_size
|
| 308 |
+
self.image_size = config.image_size
|
| 309 |
+
self.patch_size = config.patch_size
|
| 310 |
+
|
| 311 |
+
self.patch_embedding = nn.Conv2d(
|
| 312 |
+
in_channels=config.num_channels,
|
| 313 |
+
out_channels=self.embed_dim,
|
| 314 |
+
kernel_size=self.patch_size,
|
| 315 |
+
stride=self.patch_size,
|
| 316 |
+
padding="valid",
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
+
self.num_patches_per_side = self.image_size // self.patch_size
|
| 320 |
+
self.num_patches = self.num_patches_per_side**2
|
| 321 |
+
self.num_positions = self.num_patches
|
| 322 |
+
self.position_embedding = nn.Embedding(self.num_positions, self.embed_dim)
|
| 323 |
+
|
| 324 |
+
def forward(
|
| 325 |
+
self,
|
| 326 |
+
pixel_values: torch.FloatTensor,
|
| 327 |
+
patch_attention_mask: torch.BoolTensor,
|
| 328 |
+
tgt_sizes: Optional[torch.IntTensor] = None,
|
| 329 |
+
) -> torch.Tensor:
|
| 330 |
+
batch_size = pixel_values.size(0)
|
| 331 |
+
|
| 332 |
+
patch_embeds = self.patch_embedding(pixel_values)
|
| 333 |
+
embeddings = patch_embeds.flatten(2).transpose(1, 2)
|
| 334 |
+
|
| 335 |
+
max_im_h, max_im_w = pixel_values.size(2), pixel_values.size(3)
|
| 336 |
+
max_nb_patches_h, max_nb_patches_w = (
|
| 337 |
+
max_im_h // self.patch_size,
|
| 338 |
+
max_im_w // self.patch_size,
|
| 339 |
+
)
|
| 340 |
+
boundaries = torch.arange(1 / self.num_patches_per_side, 1.0, 1 / self.num_patches_per_side)
|
| 341 |
+
position_ids = torch.full(
|
| 342 |
+
size=(
|
| 343 |
+
batch_size,
|
| 344 |
+
max_nb_patches_h * max_nb_patches_w,
|
| 345 |
+
),
|
| 346 |
+
fill_value=0,
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
for batch_idx, p_attn_mask in enumerate(patch_attention_mask):
|
| 350 |
+
if tgt_sizes is not None:
|
| 351 |
+
nb_patches_h = tgt_sizes[batch_idx][0]
|
| 352 |
+
nb_patches_w = tgt_sizes[batch_idx][1]
|
| 353 |
+
else:
|
| 354 |
+
nb_patches_h = p_attn_mask[:, 0].sum()
|
| 355 |
+
nb_patches_w = p_attn_mask[0].sum()
|
| 356 |
+
|
| 357 |
+
fractional_coords_h = torch.arange(0, 1 - 1e-6, 1 / nb_patches_h)
|
| 358 |
+
fractional_coords_w = torch.arange(0, 1 - 1e-6, 1 / nb_patches_w)
|
| 359 |
+
|
| 360 |
+
bucket_coords_h = torch.bucketize(fractional_coords_h, boundaries, right=True)
|
| 361 |
+
bucket_coords_w = torch.bucketize(fractional_coords_w, boundaries, right=True)
|
| 362 |
+
|
| 363 |
+
pos_ids = (bucket_coords_h[:, None] * self.num_patches_per_side + bucket_coords_w).flatten()
|
| 364 |
+
position_ids[batch_idx][p_attn_mask.view(-1).cpu()] = pos_ids
|
| 365 |
+
|
| 366 |
+
position_ids = position_ids.to(self.position_embedding.weight.device)
|
| 367 |
+
|
| 368 |
+
embeddings = embeddings + self.position_embedding(position_ids)
|
| 369 |
+
return embeddings
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
class SiglipAttention(nn.Module):
|
| 373 |
+
"""Multi-headed attention from 'Attention Is All You Need' paper"""
|
| 374 |
+
|
| 375 |
+
# Copied from transformers.models.clip.modeling_clip.CLIPAttention.__init__
|
| 376 |
+
def __init__(self, config):
|
| 377 |
+
super().__init__()
|
| 378 |
+
self.config = config
|
| 379 |
+
self.embed_dim = config.hidden_size
|
| 380 |
+
self.num_heads = config.num_attention_heads
|
| 381 |
+
self.head_dim = self.embed_dim // self.num_heads
|
| 382 |
+
if self.head_dim * self.num_heads != self.embed_dim:
|
| 383 |
+
raise ValueError(
|
| 384 |
+
f"embed_dim must be divisible by num_heads (got `embed_dim`: {self.embed_dim} and `num_heads`:"
|
| 385 |
+
f" {self.num_heads})."
|
| 386 |
+
)
|
| 387 |
+
self.scale = self.head_dim**-0.5
|
| 388 |
+
self.dropout = config.attention_dropout
|
| 389 |
+
|
| 390 |
+
self.k_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 391 |
+
self.v_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 392 |
+
self.q_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 393 |
+
self.out_proj = nn.Linear(self.embed_dim, self.embed_dim)
|
| 394 |
+
|
| 395 |
+
def forward(
|
| 396 |
+
self,
|
| 397 |
+
hidden_states: torch.Tensor,
|
| 398 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 399 |
+
output_attentions: Optional[bool] = False,
|
| 400 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
|
| 401 |
+
"""Input shape: Batch x Time x Channel"""
|
| 402 |
+
|
| 403 |
+
batch_size, q_len, _ = hidden_states.size()
|
| 404 |
+
|
| 405 |
+
query_states = self.q_proj(hidden_states)
|
| 406 |
+
key_states = self.k_proj(hidden_states)
|
| 407 |
+
value_states = self.v_proj(hidden_states)
|
| 408 |
+
|
| 409 |
+
query_states = query_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 410 |
+
key_states = key_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 411 |
+
value_states = value_states.view(batch_size, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 412 |
+
|
| 413 |
+
k_v_seq_len = key_states.shape[-2]
|
| 414 |
+
attn_weights = torch.matmul(query_states, key_states.transpose(2, 3)) * self.scale
|
| 415 |
+
|
| 416 |
+
if attn_weights.size() != (batch_size, self.num_heads, q_len, k_v_seq_len):
|
| 417 |
+
raise ValueError(
|
| 418 |
+
f"Attention weights should be of size {(batch_size, self.num_heads, q_len, k_v_seq_len)}, but is"
|
| 419 |
+
f" {attn_weights.size()}"
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
if attention_mask is not None:
|
| 423 |
+
if attention_mask.size() != (batch_size, 1, q_len, k_v_seq_len):
|
| 424 |
+
raise ValueError(
|
| 425 |
+
f"Attention mask should be of size {(batch_size, 1, q_len, k_v_seq_len)}, but is {attention_mask.size()}"
|
| 426 |
+
)
|
| 427 |
+
attn_weights = attn_weights + attention_mask
|
| 428 |
+
|
| 429 |
+
# upcast attention to fp32
|
| 430 |
+
attn_weights = nn.functional.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query_states.dtype)
|
| 431 |
+
attn_weights = nn.functional.dropout(attn_weights, p=self.dropout, training=self.training)
|
| 432 |
+
attn_output = torch.matmul(attn_weights, value_states)
|
| 433 |
+
|
| 434 |
+
if attn_output.size() != (batch_size, self.num_heads, q_len, self.head_dim):
|
| 435 |
+
raise ValueError(
|
| 436 |
+
f"`attn_output` should be of size {(batch_size, self.num_heads, q_len, self.head_dim)}, but is"
|
| 437 |
+
f" {attn_output.size()}"
|
| 438 |
+
)
|
| 439 |
+
|
| 440 |
+
attn_output = attn_output.transpose(1, 2).contiguous()
|
| 441 |
+
attn_output = attn_output.reshape(batch_size, q_len, self.embed_dim)
|
| 442 |
+
|
| 443 |
+
attn_output = self.out_proj(attn_output)
|
| 444 |
+
|
| 445 |
+
return attn_output, attn_weights
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
class SiglipFlashAttention2(SiglipAttention):
|
| 449 |
+
"""
|
| 450 |
+
Llama flash attention module. This module inherits from `LlamaAttention` as the weights of the module stays
|
| 451 |
+
untouched. The only required change would be on the forward pass where it needs to correctly call the public API of
|
| 452 |
+
flash attention and deal with padding tokens in case the input contains any of them.
|
| 453 |
+
"""
|
| 454 |
+
|
| 455 |
+
def __init__(self, *args, **kwargs):
|
| 456 |
+
super().__init__(*args, **kwargs)
|
| 457 |
+
self.is_causal = False # Hack to make sure we don't use a causal mask
|
| 458 |
+
|
| 459 |
+
def forward(
|
| 460 |
+
self,
|
| 461 |
+
hidden_states: torch.Tensor,
|
| 462 |
+
attention_mask: Optional[torch.LongTensor] = None,
|
| 463 |
+
position_ids: Optional[torch.LongTensor] = None,
|
| 464 |
+
past_key_value: Optional[Tuple[torch.Tensor]] = None,
|
| 465 |
+
output_attentions: bool = False,
|
| 466 |
+
use_cache: bool = False,
|
| 467 |
+
**kwargs,
|
| 468 |
+
) -> Tuple[torch.Tensor, Optional[torch.Tensor], Optional[Tuple[torch.Tensor]]]:
|
| 469 |
+
output_attentions = False
|
| 470 |
+
|
| 471 |
+
bsz, q_len, _ = hidden_states.size()
|
| 472 |
+
|
| 473 |
+
query_states = self.q_proj(hidden_states)
|
| 474 |
+
key_states = self.k_proj(hidden_states)
|
| 475 |
+
value_states = self.v_proj(hidden_states)
|
| 476 |
+
|
| 477 |
+
# Flash attention requires the input to have the shape
|
| 478 |
+
# batch_size x seq_length x head_dim x hidden_dim
|
| 479 |
+
# therefore we just need to keep the original shape
|
| 480 |
+
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 481 |
+
key_states = key_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 482 |
+
value_states = value_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
|
| 483 |
+
|
| 484 |
+
kv_seq_len = key_states.shape[-2]
|
| 485 |
+
if past_key_value is not None:
|
| 486 |
+
kv_seq_len += past_key_value.get_usable_length(kv_seq_len, self.layer_idx)
|
| 487 |
+
# cos, sin = self.rotary_emb(value_states, seq_len=kv_seq_len)
|
| 488 |
+
# query_states, key_states = apply_rotary_pos_emb(query_states, key_states, cos, sin, position_ids)
|
| 489 |
+
|
| 490 |
+
# if past_key_value is not None:
|
| 491 |
+
# cache_kwargs = {"sin": sin, "cos": cos} # Specific to RoPE models
|
| 492 |
+
# key_states, value_states = past_key_value.update(key_states, value_states, self.layer_idx, cache_kwargs)
|
| 493 |
+
|
| 494 |
+
# TODO: These transpose are quite inefficient but Flash Attention requires the layout [batch_size, sequence_length, num_heads, head_dim]. We would need to refactor the KV cache
|
| 495 |
+
# to be able to avoid many of these transpose/reshape/view.
|
| 496 |
+
query_states = query_states.transpose(1, 2)
|
| 497 |
+
key_states = key_states.transpose(1, 2)
|
| 498 |
+
value_states = value_states.transpose(1, 2)
|
| 499 |
+
|
| 500 |
+
dropout_rate = self.dropout if self.training else 0.0
|
| 501 |
+
|
| 502 |
+
# In PEFT, usually we cast the layer norms in float32 for training stability reasons
|
| 503 |
+
# therefore the input hidden states gets silently casted in float32. Hence, we need
|
| 504 |
+
# cast them back in the correct dtype just to be sure everything works as expected.
|
| 505 |
+
# This might slowdown training & inference so it is recommended to not cast the LayerNorms
|
| 506 |
+
# in fp32. (LlamaRMSNorm handles it correctly)
|
| 507 |
+
|
| 508 |
+
input_dtype = query_states.dtype
|
| 509 |
+
if input_dtype == torch.float32:
|
| 510 |
+
if torch.is_autocast_enabled():
|
| 511 |
+
target_dtype = torch.get_autocast_gpu_dtype()
|
| 512 |
+
# Handle the case where the model is quantized
|
| 513 |
+
elif hasattr(self.config, "_pre_quantization_dtype"):
|
| 514 |
+
target_dtype = self.config._pre_quantization_dtype
|
| 515 |
+
else:
|
| 516 |
+
target_dtype = self.q_proj.weight.dtype
|
| 517 |
+
|
| 518 |
+
logger.warning_once(
|
| 519 |
+
"The input hidden states seems to be silently casted in float32, this might be related to the fact"
|
| 520 |
+
" you have upcasted embedding or layer norm layers in float32. We will cast back the input in"
|
| 521 |
+
f" {target_dtype}."
|
| 522 |
+
)
|
| 523 |
+
|
| 524 |
+
query_states = query_states.to(target_dtype)
|
| 525 |
+
key_states = key_states.to(target_dtype)
|
| 526 |
+
value_states = value_states.to(target_dtype)
|
| 527 |
+
|
| 528 |
+
attn_output = self._flash_attention_forward(
|
| 529 |
+
query_states,
|
| 530 |
+
key_states,
|
| 531 |
+
value_states,
|
| 532 |
+
attention_mask,
|
| 533 |
+
q_len,
|
| 534 |
+
dropout=dropout_rate,
|
| 535 |
+
)
|
| 536 |
+
|
| 537 |
+
attn_output = attn_output.reshape(bsz, q_len, self.embed_dim).contiguous()
|
| 538 |
+
attn_output = self.out_proj(attn_output)
|
| 539 |
+
|
| 540 |
+
if not output_attentions:
|
| 541 |
+
attn_weights = None
|
| 542 |
+
|
| 543 |
+
return attn_output, attn_weights
|
| 544 |
+
|
| 545 |
+
def _flash_attention_forward(
|
| 546 |
+
self,
|
| 547 |
+
query_states,
|
| 548 |
+
key_states,
|
| 549 |
+
value_states,
|
| 550 |
+
attention_mask,
|
| 551 |
+
query_length,
|
| 552 |
+
dropout=0.0,
|
| 553 |
+
softmax_scale=None,
|
| 554 |
+
):
|
| 555 |
+
"""
|
| 556 |
+
Calls the forward method of Flash Attention - if the input hidden states contain at least one padding token
|
| 557 |
+
first unpad the input, then computes the attention scores and pad the final attention scores.
|
| 558 |
+
Args:
|
| 559 |
+
query_states (`torch.Tensor`):
|
| 560 |
+
Input query states to be passed to Flash Attention API
|
| 561 |
+
key_states (`torch.Tensor`):
|
| 562 |
+
Input key states to be passed to Flash Attention API
|
| 563 |
+
value_states (`torch.Tensor`):
|
| 564 |
+
Input value states to be passed to Flash Attention API
|
| 565 |
+
attention_mask (`torch.Tensor`):
|
| 566 |
+
The padding mask - corresponds to a tensor of size `(batch_size, seq_len)` where 0 stands for the
|
| 567 |
+
position of padding tokens and 1 for the position of non-padding tokens.
|
| 568 |
+
dropout (`int`, *optional*):
|
| 569 |
+
Attention dropout
|
| 570 |
+
softmax_scale (`float`, *optional*):
|
| 571 |
+
The scaling of QK^T before applying softmax. Default to 1 / sqrt(head_dim)
|
| 572 |
+
"""
|
| 573 |
+
|
| 574 |
+
# TODO: Remove the `query_length != 1` check once Flash Attention for RoCm is bumped to 2.1. For details, please see the comment in LlamaFlashAttention2 __init__.
|
| 575 |
+
causal = self.is_causal and query_length != 1
|
| 576 |
+
|
| 577 |
+
# Contains at least one padding token in the sequence
|
| 578 |
+
if attention_mask is not None:
|
| 579 |
+
batch_size = query_states.shape[0]
|
| 580 |
+
(
|
| 581 |
+
query_states,
|
| 582 |
+
key_states,
|
| 583 |
+
value_states,
|
| 584 |
+
indices_q,
|
| 585 |
+
cu_seq_lens,
|
| 586 |
+
max_seq_lens,
|
| 587 |
+
) = self._upad_input(query_states, key_states, value_states, attention_mask, query_length)
|
| 588 |
+
|
| 589 |
+
cu_seqlens_q, cu_seqlens_k = cu_seq_lens
|
| 590 |
+
max_seqlen_in_batch_q, max_seqlen_in_batch_k = max_seq_lens
|
| 591 |
+
|
| 592 |
+
attn_output_unpad = flash_attn_varlen_func(
|
| 593 |
+
query_states,
|
| 594 |
+
key_states,
|
| 595 |
+
value_states,
|
| 596 |
+
cu_seqlens_q=cu_seqlens_q,
|
| 597 |
+
cu_seqlens_k=cu_seqlens_k,
|
| 598 |
+
max_seqlen_q=max_seqlen_in_batch_q,
|
| 599 |
+
max_seqlen_k=max_seqlen_in_batch_k,
|
| 600 |
+
dropout_p=dropout,
|
| 601 |
+
softmax_scale=softmax_scale,
|
| 602 |
+
causal=causal,
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
+
attn_output = pad_input(attn_output_unpad, indices_q, batch_size, query_length)
|
| 606 |
+
else:
|
| 607 |
+
attn_output = flash_attn_func(
|
| 608 |
+
query_states,
|
| 609 |
+
key_states,
|
| 610 |
+
value_states,
|
| 611 |
+
dropout,
|
| 612 |
+
softmax_scale=softmax_scale,
|
| 613 |
+
causal=causal,
|
| 614 |
+
)
|
| 615 |
+
|
| 616 |
+
return attn_output
|
| 617 |
+
|
| 618 |
+
def _upad_input(self, query_layer, key_layer, value_layer, attention_mask, query_length):
|
| 619 |
+
indices_k, cu_seqlens_k, max_seqlen_in_batch_k = _get_unpad_data(attention_mask)
|
| 620 |
+
batch_size, kv_seq_len, num_key_value_heads, head_dim = key_layer.shape
|
| 621 |
+
|
| 622 |
+
key_layer = index_first_axis(
|
| 623 |
+
key_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim),
|
| 624 |
+
indices_k,
|
| 625 |
+
)
|
| 626 |
+
value_layer = index_first_axis(
|
| 627 |
+
value_layer.reshape(batch_size * kv_seq_len, num_key_value_heads, head_dim),
|
| 628 |
+
indices_k,
|
| 629 |
+
)
|
| 630 |
+
if query_length == kv_seq_len:
|
| 631 |
+
query_layer = index_first_axis(
|
| 632 |
+
query_layer.reshape(batch_size * kv_seq_len, self.num_heads, head_dim),
|
| 633 |
+
indices_k,
|
| 634 |
+
)
|
| 635 |
+
cu_seqlens_q = cu_seqlens_k
|
| 636 |
+
max_seqlen_in_batch_q = max_seqlen_in_batch_k
|
| 637 |
+
indices_q = indices_k
|
| 638 |
+
elif query_length == 1:
|
| 639 |
+
max_seqlen_in_batch_q = 1
|
| 640 |
+
cu_seqlens_q = torch.arange(
|
| 641 |
+
batch_size + 1, dtype=torch.int32, device=query_layer.device
|
| 642 |
+
) # There is a memcpy here, that is very bad.
|
| 643 |
+
indices_q = cu_seqlens_q[:-1]
|
| 644 |
+
query_layer = query_layer.squeeze(1)
|
| 645 |
+
else:
|
| 646 |
+
# The -q_len: slice assumes left padding.
|
| 647 |
+
attention_mask = attention_mask[:, -query_length:]
|
| 648 |
+
query_layer, indices_q, cu_seqlens_q, max_seqlen_in_batch_q = unpad_input(query_layer, attention_mask)
|
| 649 |
+
|
| 650 |
+
return (
|
| 651 |
+
query_layer,
|
| 652 |
+
key_layer,
|
| 653 |
+
value_layer,
|
| 654 |
+
indices_q,
|
| 655 |
+
(cu_seqlens_q, cu_seqlens_k),
|
| 656 |
+
(max_seqlen_in_batch_q, max_seqlen_in_batch_k),
|
| 657 |
+
)
|
| 658 |
+
|
| 659 |
+
|
| 660 |
+
# Copied from transformers.models.clip.modeling_clip.CLIPMLP with CLIP->Siglip
|
| 661 |
+
class SiglipMLP(nn.Module):
|
| 662 |
+
def __init__(self, config):
|
| 663 |
+
super().__init__()
|
| 664 |
+
self.config = config
|
| 665 |
+
self.activation_fn = ACT2FN[config.hidden_act]
|
| 666 |
+
self.fc1 = nn.Linear(config.hidden_size, config.intermediate_size)
|
| 667 |
+
self.fc2 = nn.Linear(config.intermediate_size, config.hidden_size)
|
| 668 |
+
|
| 669 |
+
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 670 |
+
hidden_states = self.fc1(hidden_states)
|
| 671 |
+
hidden_states = self.activation_fn(hidden_states)
|
| 672 |
+
hidden_states = self.fc2(hidden_states)
|
| 673 |
+
return hidden_states
|
| 674 |
+
|
| 675 |
+
|
| 676 |
+
# Copied from transformers.models.clip.modeling_clip.CLIPEncoderLayer with CLIP->Siglip
|
| 677 |
+
class SiglipEncoderLayer(nn.Module):
|
| 678 |
+
def __init__(self, config: SiglipVisionConfig):
|
| 679 |
+
super().__init__()
|
| 680 |
+
self.embed_dim = config.hidden_size
|
| 681 |
+
self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
|
| 682 |
+
self.self_attn = SiglipAttention(config) if not self._use_flash_attention_2 else SiglipFlashAttention2(config)
|
| 683 |
+
self.layer_norm1 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
|
| 684 |
+
self.mlp = SiglipMLP(config)
|
| 685 |
+
self.layer_norm2 = nn.LayerNorm(self.embed_dim, eps=config.layer_norm_eps)
|
| 686 |
+
|
| 687 |
+
def forward(
|
| 688 |
+
self,
|
| 689 |
+
hidden_states: torch.Tensor,
|
| 690 |
+
attention_mask: torch.Tensor,
|
| 691 |
+
output_attentions: Optional[bool] = False,
|
| 692 |
+
) -> Tuple[torch.FloatTensor]:
|
| 693 |
+
"""
|
| 694 |
+
Args:
|
| 695 |
+
hidden_states (`torch.FloatTensor`):
|
| 696 |
+
Input to the layer of shape `(batch, seq_len, embed_dim)`.
|
| 697 |
+
attention_mask (`torch.FloatTensor`):
|
| 698 |
+
Attention mask of shape `(batch, 1, q_len, k_v_seq_len)` where padding elements are indicated by very large negative values.
|
| 699 |
+
output_attentions (`bool`, *optional*, defaults to `False`):
|
| 700 |
+
Whether or not to return the attentions tensors of all attention layers. See `attentions` under
|
| 701 |
+
returned tensors for more detail.
|
| 702 |
+
"""
|
| 703 |
+
residual = hidden_states
|
| 704 |
+
|
| 705 |
+
hidden_states = self.layer_norm1(hidden_states)
|
| 706 |
+
hidden_states, attn_weights = self.self_attn(
|
| 707 |
+
hidden_states=hidden_states,
|
| 708 |
+
attention_mask=attention_mask,
|
| 709 |
+
output_attentions=output_attentions,
|
| 710 |
+
)
|
| 711 |
+
hidden_states = residual + hidden_states
|
| 712 |
+
|
| 713 |
+
residual = hidden_states
|
| 714 |
+
hidden_states = self.layer_norm2(hidden_states)
|
| 715 |
+
hidden_states = self.mlp(hidden_states)
|
| 716 |
+
hidden_states = residual + hidden_states
|
| 717 |
+
|
| 718 |
+
outputs = (hidden_states,)
|
| 719 |
+
|
| 720 |
+
if output_attentions:
|
| 721 |
+
outputs += (attn_weights,)
|
| 722 |
+
|
| 723 |
+
return outputs
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
class SiglipPreTrainedModel(PreTrainedModel):
|
| 727 |
+
"""
|
| 728 |
+
An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
|
| 729 |
+
models.
|
| 730 |
+
"""
|
| 731 |
+
|
| 732 |
+
config_class = SiglipVisionConfig
|
| 733 |
+
base_model_prefix = "siglip"
|
| 734 |
+
supports_gradient_checkpointing = True
|
| 735 |
+
|
| 736 |
+
def _init_weights(self, module):
|
| 737 |
+
"""Initialize the weights"""
|
| 738 |
+
|
| 739 |
+
if isinstance(module, SiglipVisionEmbeddings):
|
| 740 |
+
width = self.config.hidden_size
|
| 741 |
+
nn.init.normal_(module.position_embedding.weight, std=1 / np.sqrt(width))
|
| 742 |
+
elif isinstance(module, nn.Embedding):
|
| 743 |
+
default_flax_embed_init(module.weight)
|
| 744 |
+
elif isinstance(module, SiglipAttention):
|
| 745 |
+
nn.init.normal_(module.q_proj.weight)
|
| 746 |
+
nn.init.normal_(module.k_proj.weight)
|
| 747 |
+
nn.init.normal_(module.v_proj.weight)
|
| 748 |
+
nn.init.normal_(module.out_proj.weight)
|
| 749 |
+
nn.init.zeros_(module.q_proj.bias)
|
| 750 |
+
nn.init.zeros_(module.k_proj.bias)
|
| 751 |
+
nn.init.zeros_(module.v_proj.bias)
|
| 752 |
+
nn.init.zeros_(module.out_proj.bias)
|
| 753 |
+
elif isinstance(module, SiglipMLP):
|
| 754 |
+
nn.init.normal_(module.fc1.weight)
|
| 755 |
+
nn.init.normal_(module.fc2.weight)
|
| 756 |
+
nn.init.normal_(module.fc1.bias, std=1e-6)
|
| 757 |
+
nn.init.normal_(module.fc2.bias, std=1e-6)
|
| 758 |
+
elif isinstance(module, (nn.Linear, nn.Conv2d)):
|
| 759 |
+
lecun_normal_(module.weight)
|
| 760 |
+
if module.bias is not None:
|
| 761 |
+
nn.init.zeros_(module.bias)
|
| 762 |
+
elif isinstance(module, nn.LayerNorm):
|
| 763 |
+
module.bias.data.zero_()
|
| 764 |
+
module.weight.data.fill_(1.0)
|
| 765 |
+
|
| 766 |
+
|
| 767 |
+
SIGLIP_START_DOCSTRING = r"""
|
| 768 |
+
This model inherits from [`PreTrainedModel`]. Check the superclass documentation for the generic methods the
|
| 769 |
+
library implements for all its model (such as downloading or saving, resizing the input embeddings, pruning heads
|
| 770 |
+
etc.)
|
| 771 |
+
This model is also a PyTorch [torch.nn.Module](https://pytorch.org/docs/stable/nn.html#torch.nn.Module) subclass.
|
| 772 |
+
Use it as a regular PyTorch Module and refer to the PyTorch documentation for all matter related to general usage
|
| 773 |
+
and behavior.
|
| 774 |
+
Parameters:
|
| 775 |
+
config ([`SiglipVisionConfig`]): Model configuration class with all the parameters of the model.
|
| 776 |
+
Initializing with a config file does not load the weights associated with the model, only the
|
| 777 |
+
configuration. Check out the [`~PreTrainedModel.from_pretrained`] method to load the model weights.
|
| 778 |
+
"""
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
SIGLIP_VISION_INPUTS_DOCSTRING = r"""
|
| 782 |
+
Args:
|
| 783 |
+
pixel_values (`torch.FloatTensor` of shape `(batch_size, num_channels, height, width)`):
|
| 784 |
+
Pixel values. Padding will be ignored by default should you provide it. Pixel values can be obtained using
|
| 785 |
+
[`AutoImageProcessor`]. See [`CLIPImageProcessor.__call__`] for details.
|
| 786 |
+
output_attentions (`bool`, *optional*):
|
| 787 |
+
Whether or not to return the attentions tensors of all attention layers. See `attentions` under returned
|
| 788 |
+
tensors for more detail.
|
| 789 |
+
output_hidden_states (`bool`, *optional*):
|
| 790 |
+
Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors for
|
| 791 |
+
more detail.
|
| 792 |
+
return_dict (`bool`, *optional*):
|
| 793 |
+
Whether or not to return a [`~utils.ModelOutput`] instead of a plain tuple.
|
| 794 |
+
"""
|
| 795 |
+
|
| 796 |
+
|
| 797 |
+
# Copied from transformers.models.clip.modeling_clip.CLIPEncoder with CLIP->Siglip
|
| 798 |
+
class SiglipEncoder(nn.Module):
|
| 799 |
+
"""
|
| 800 |
+
Transformer encoder consisting of `config.num_hidden_layers` self attention layers. Each layer is a
|
| 801 |
+
[`SiglipEncoderLayer`].
|
| 802 |
+
Args:
|
| 803 |
+
config: SiglipConfig
|
| 804 |
+
"""
|
| 805 |
+
|
| 806 |
+
def __init__(self, config: SiglipVisionConfig):
|
| 807 |
+
super().__init__()
|
| 808 |
+
self.config = config
|
| 809 |
+
self.layers = nn.ModuleList([SiglipEncoderLayer(config) for _ in range(config.num_hidden_layers)])
|
| 810 |
+
self.gradient_checkpointing = False
|
| 811 |
+
|
| 812 |
+
# Ignore copy
|
| 813 |
+
def forward(
|
| 814 |
+
self,
|
| 815 |
+
inputs_embeds,
|
| 816 |
+
attention_mask: Optional[torch.Tensor] = None,
|
| 817 |
+
output_attentions: Optional[bool] = None,
|
| 818 |
+
output_hidden_states: Optional[bool] = None,
|
| 819 |
+
return_dict: Optional[bool] = None,
|
| 820 |
+
) -> Union[Tuple, BaseModelOutput]:
|
| 821 |
+
r"""
|
| 822 |
+
Args:
|
| 823 |
+
inputs_embeds (`torch.FloatTensor` of shape `(batch_size, sequence_length, hidden_size)`):
|
| 824 |
+
Optionally, instead of passing `input_ids` you can choose to directly pass an embedded representation.
|
| 825 |
+
This is useful if you want more control over how to convert `input_ids` indices into associated vectors
|
| 826 |
+
than the model's internal embedding lookup matrix.
|
| 827 |
+
attention_mask (`torch.Tensor` of shape `(batch_size, sequence_length)`, *optional*):
|
| 828 |
+
Mask to avoid performing attention on padding token indices. Mask values selected in `[0, 1]`:
|
| 829 |
+
- 1 for tokens that are **not masked**,
|
| 830 |
+
- 0 for tokens that are **masked**.
|
| 831 |
+
[What are attention masks?](../glossary#attention-mask)
|
| 832 |
+
output_attentions (`bool`, *optional*):
|
| 833 |
+
Whether or not to return the attentions tensors of all attention layers. See `attentions` under
|
| 834 |
+
returned tensors for more detail.
|
| 835 |
+
output_hidden_states (`bool`, *optional*):
|
| 836 |
+
Whether or not to return the hidden states of all layers. See `hidden_states` under returned tensors
|
| 837 |
+
for more detail.
|
| 838 |
+
return_dict (`bool`, *optional*):
|
| 839 |
+
Whether or not to return a [`~utils.ModelOutput`] instead of a plain tuple.
|
| 840 |
+
"""
|
| 841 |
+
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 842 |
+
output_hidden_states = (
|
| 843 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 844 |
+
)
|
| 845 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 846 |
+
|
| 847 |
+
encoder_states = () if output_hidden_states else None
|
| 848 |
+
all_attentions = () if output_attentions else None
|
| 849 |
+
|
| 850 |
+
hidden_states = inputs_embeds
|
| 851 |
+
for encoder_layer in self.layers:
|
| 852 |
+
if output_hidden_states:
|
| 853 |
+
encoder_states = encoder_states + (hidden_states,)
|
| 854 |
+
if self.gradient_checkpointing and self.training:
|
| 855 |
+
layer_outputs = self._gradient_checkpointing_func(
|
| 856 |
+
encoder_layer.__call__,
|
| 857 |
+
hidden_states,
|
| 858 |
+
attention_mask,
|
| 859 |
+
output_attentions,
|
| 860 |
+
)
|
| 861 |
+
else:
|
| 862 |
+
layer_outputs = encoder_layer(
|
| 863 |
+
hidden_states,
|
| 864 |
+
attention_mask,
|
| 865 |
+
output_attentions=output_attentions,
|
| 866 |
+
)
|
| 867 |
+
|
| 868 |
+
hidden_states = layer_outputs[0]
|
| 869 |
+
|
| 870 |
+
if output_attentions:
|
| 871 |
+
all_attentions = all_attentions + (layer_outputs[1],)
|
| 872 |
+
|
| 873 |
+
if output_hidden_states:
|
| 874 |
+
encoder_states = encoder_states + (hidden_states,)
|
| 875 |
+
|
| 876 |
+
if not return_dict:
|
| 877 |
+
return tuple(v for v in [hidden_states, encoder_states, all_attentions] if v is not None)
|
| 878 |
+
return BaseModelOutput(
|
| 879 |
+
last_hidden_state=hidden_states,
|
| 880 |
+
hidden_states=encoder_states,
|
| 881 |
+
attentions=all_attentions,
|
| 882 |
+
)
|
| 883 |
+
|
| 884 |
+
|
| 885 |
+
@add_start_docstrings(
|
| 886 |
+
"""The vision model from SigLIP without any head or projection on top.""",
|
| 887 |
+
SIGLIP_START_DOCSTRING,
|
| 888 |
+
)
|
| 889 |
+
class SiglipVisionTransformer(SiglipPreTrainedModel):
|
| 890 |
+
config_class = SiglipVisionConfig
|
| 891 |
+
main_input_name = "pixel_values"
|
| 892 |
+
_supports_flash_attn_2 = True
|
| 893 |
+
_no_split_modules = []
|
| 894 |
+
|
| 895 |
+
def __init__(self, config: SiglipVisionConfig):
|
| 896 |
+
super().__init__(config)
|
| 897 |
+
self.config = config
|
| 898 |
+
embed_dim = config.hidden_size
|
| 899 |
+
|
| 900 |
+
self.embeddings = SiglipVisionEmbeddings(config)
|
| 901 |
+
self.encoder = SiglipEncoder(config)
|
| 902 |
+
self.post_layernorm = nn.LayerNorm(embed_dim, eps=config.layer_norm_eps)
|
| 903 |
+
self._use_flash_attention_2 = config._attn_implementation == "flash_attention_2"
|
| 904 |
+
|
| 905 |
+
# Initialize weights and apply final processing
|
| 906 |
+
self.post_init()
|
| 907 |
+
|
| 908 |
+
def get_input_embeddings(self) -> nn.Module:
|
| 909 |
+
return self.embeddings.patch_embedding
|
| 910 |
+
|
| 911 |
+
@add_start_docstrings_to_model_forward(SIGLIP_VISION_INPUTS_DOCSTRING)
|
| 912 |
+
@replace_return_docstrings(output_type=BaseModelOutputWithPooling, config_class=SiglipVisionConfig)
|
| 913 |
+
def forward(
|
| 914 |
+
self,
|
| 915 |
+
pixel_values,
|
| 916 |
+
patch_attention_mask: Optional[torch.BoolTensor] = None,
|
| 917 |
+
tgt_sizes: Optional[torch.IntTensor] = None,
|
| 918 |
+
output_attentions: Optional[bool] = None,
|
| 919 |
+
output_hidden_states: Optional[bool] = None,
|
| 920 |
+
return_dict: Optional[bool] = None,
|
| 921 |
+
) -> Union[Tuple, BaseModelOutputWithPooling]:
|
| 922 |
+
r"""
|
| 923 |
+
Returns:
|
| 924 |
+
"""
|
| 925 |
+
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
| 926 |
+
output_hidden_states = (
|
| 927 |
+
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
| 928 |
+
)
|
| 929 |
+
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
| 930 |
+
|
| 931 |
+
batch_size = pixel_values.size(0)
|
| 932 |
+
if patch_attention_mask is None:
|
| 933 |
+
patch_attention_mask = torch.ones(
|
| 934 |
+
size=(
|
| 935 |
+
batch_size,
|
| 936 |
+
pixel_values.size(2) // self.config.patch_size,
|
| 937 |
+
pixel_values.size(3) // self.config.patch_size,
|
| 938 |
+
),
|
| 939 |
+
dtype=torch.bool,
|
| 940 |
+
device=pixel_values.device,
|
| 941 |
+
)
|
| 942 |
+
|
| 943 |
+
hidden_states = self.embeddings(
|
| 944 |
+
pixel_values=pixel_values,
|
| 945 |
+
patch_attention_mask=patch_attention_mask,
|
| 946 |
+
tgt_sizes=tgt_sizes,
|
| 947 |
+
)
|
| 948 |
+
|
| 949 |
+
patch_attention_mask = patch_attention_mask.view(batch_size, -1)
|
| 950 |
+
# The call to `_upad_input` in `_flash_attention_forward` is expensive
|
| 951 |
+
# So when the `patch_attention_mask` is full of 1s (i.e. attending to the whole sequence),
|
| 952 |
+
# avoiding passing the attention_mask, which is equivalent to attending to the full sequence
|
| 953 |
+
if not torch.any(~patch_attention_mask):
|
| 954 |
+
attention_mask = None
|
| 955 |
+
else:
|
| 956 |
+
attention_mask = (
|
| 957 |
+
_prepare_4d_attention_mask(patch_attention_mask, hidden_states.dtype)
|
| 958 |
+
if not self._use_flash_attention_2
|
| 959 |
+
else patch_attention_mask
|
| 960 |
+
)
|
| 961 |
+
|
| 962 |
+
encoder_outputs = self.encoder(
|
| 963 |
+
inputs_embeds=hidden_states,
|
| 964 |
+
attention_mask=attention_mask,
|
| 965 |
+
output_attentions=output_attentions,
|
| 966 |
+
output_hidden_states=output_hidden_states,
|
| 967 |
+
return_dict=return_dict,
|
| 968 |
+
)
|
| 969 |
+
|
| 970 |
+
last_hidden_state = encoder_outputs[0]
|
| 971 |
+
last_hidden_state = self.post_layernorm(last_hidden_state)
|
| 972 |
+
|
| 973 |
+
if not return_dict:
|
| 974 |
+
return (last_hidden_state, None) + encoder_outputs[1:]
|
| 975 |
+
|
| 976 |
+
return BaseModelOutputWithPooling(
|
| 977 |
+
last_hidden_state=last_hidden_state,
|
| 978 |
+
pooler_output=None,
|
| 979 |
+
hidden_states=encoder_outputs.hidden_states,
|
| 980 |
+
attentions=encoder_outputs.attentions,
|
| 981 |
+
)
|
Realtime-Venus-Audio/preprocessor_config.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_pool_step": 5,
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoFeatureExtractor": "processing_minicpmo.MiniCPMAAudioProcessor",
|
| 5 |
+
"AutoImageProcessor": "processing_minicpmo.MiniCPMVImageProcessor",
|
| 6 |
+
"AutoProcessor": "processing_minicpmo.MiniCPMOProcessor"
|
| 7 |
+
},
|
| 8 |
+
"chunk_length": 30,
|
| 9 |
+
"dither": 0.0,
|
| 10 |
+
"dynamic_log_norm": true,
|
| 11 |
+
"dynamic_range_db": 8.0,
|
| 12 |
+
"feature_extractor_type": "MiniCPMAAudioProcessor",
|
| 13 |
+
"feature_size": 80,
|
| 14 |
+
"hop_length": 160,
|
| 15 |
+
"im_end": "</image>",
|
| 16 |
+
"im_id_end": "</image_id>",
|
| 17 |
+
"im_id_start": "<image_id>",
|
| 18 |
+
"im_start": "<image>",
|
| 19 |
+
"image_feature_size": 64,
|
| 20 |
+
"image_processor_type": "MiniCPMVImageProcessor",
|
| 21 |
+
"log_floor_db": -10.0,
|
| 22 |
+
"max_slice_nums": 9,
|
| 23 |
+
"n_fft": 400,
|
| 24 |
+
"n_samples": 480000,
|
| 25 |
+
"nb_max_frames": 3000,
|
| 26 |
+
"norm_mean": [
|
| 27 |
+
0.5,
|
| 28 |
+
0.5,
|
| 29 |
+
0.5
|
| 30 |
+
],
|
| 31 |
+
"norm_std": [
|
| 32 |
+
0.5,
|
| 33 |
+
0.5,
|
| 34 |
+
0.5
|
| 35 |
+
],
|
| 36 |
+
"padding_side": "right",
|
| 37 |
+
"padding_value": 0.0,
|
| 38 |
+
"patch_size": 14,
|
| 39 |
+
"processor_class": "MiniCPMOProcessor",
|
| 40 |
+
"return_attention_mask": false,
|
| 41 |
+
"sampling_rate": 16000,
|
| 42 |
+
"scale_resolution": 448,
|
| 43 |
+
"slice_end": "</slice>",
|
| 44 |
+
"slice_mode": true,
|
| 45 |
+
"slice_start": "<slice>",
|
| 46 |
+
"unk": "<unk>",
|
| 47 |
+
"use_image_id": true,
|
| 48 |
+
"version": 4.5
|
| 49 |
+
}
|
Realtime-Venus-Audio/processing_minicpmo.py
ADDED
|
@@ -0,0 +1,1666 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# Copyright 2026 The OpenBMB Team. All rights reserved.
|
| 4 |
+
# Modifications Copyright 2026 The Realtime-Venus Team.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
import copy
|
| 19 |
+
import math
|
| 20 |
+
import re
|
| 21 |
+
from typing import Any
|
| 22 |
+
from typing import Dict
|
| 23 |
+
from typing import List
|
| 24 |
+
from typing import Optional
|
| 25 |
+
from typing import Tuple
|
| 26 |
+
from typing import Union
|
| 27 |
+
|
| 28 |
+
import numpy as np
|
| 29 |
+
import torch
|
| 30 |
+
from PIL import Image
|
| 31 |
+
from transformers import AutoImageProcessor
|
| 32 |
+
from transformers.audio_utils import spectrogram
|
| 33 |
+
from transformers.audio_utils import window_function
|
| 34 |
+
from transformers.image_processing_utils import BaseImageProcessor
|
| 35 |
+
from transformers.image_processing_utils import BatchFeature
|
| 36 |
+
from transformers.image_transforms import to_channel_dimension_format
|
| 37 |
+
from transformers.image_utils import ChannelDimension
|
| 38 |
+
from transformers.image_utils import ImageInput
|
| 39 |
+
from transformers.image_utils import infer_channel_dimension_format
|
| 40 |
+
from transformers.image_utils import is_torch_tensor
|
| 41 |
+
from transformers.image_utils import to_numpy_array
|
| 42 |
+
from transformers.image_utils import valid_images
|
| 43 |
+
from transformers.models.whisper.feature_extraction_whisper import WhisperFeatureExtractor
|
| 44 |
+
from transformers.processing_utils import ProcessorMixin
|
| 45 |
+
from transformers.tokenization_utils_base import PreTokenizedInput
|
| 46 |
+
from transformers.tokenization_utils_base import TextInput
|
| 47 |
+
from transformers.utils import is_torch_device
|
| 48 |
+
from transformers.utils import is_torch_dtype
|
| 49 |
+
from transformers.utils import requires_backends
|
| 50 |
+
from transformers.utils import TensorType
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def recursive_converter(converter, value):
|
| 54 |
+
if isinstance(value, list):
|
| 55 |
+
new_value = []
|
| 56 |
+
for v in value:
|
| 57 |
+
new_value += [recursive_converter(converter, v)]
|
| 58 |
+
return new_value
|
| 59 |
+
else:
|
| 60 |
+
return converter(value)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class MiniCPMOBatchFeature(BatchFeature):
|
| 64 |
+
"""Extend from BatchFeature for supporting various image size"""
|
| 65 |
+
|
| 66 |
+
def __init__(self, data: Optional[Dict[str, Any]] = None, tensor_type: Union[None, str, TensorType] = None):
|
| 67 |
+
super().__init__(data)
|
| 68 |
+
self.convert_to_tensors(tensor_type=tensor_type)
|
| 69 |
+
|
| 70 |
+
def convert_to_tensors(self, tensor_type: Optional[Union[str, TensorType]] = None):
|
| 71 |
+
if tensor_type is None:
|
| 72 |
+
return self
|
| 73 |
+
|
| 74 |
+
is_tensor, as_tensor = self._get_is_as_tensor_fns(tensor_type)
|
| 75 |
+
|
| 76 |
+
def converter(value):
|
| 77 |
+
try:
|
| 78 |
+
if not is_tensor(value):
|
| 79 |
+
tensor = as_tensor(value)
|
| 80 |
+
return tensor
|
| 81 |
+
except: # noqa E722
|
| 82 |
+
if key == "overflowing_values":
|
| 83 |
+
raise ValueError("Unable to create tensor returning overflowing values of different lengths. ")
|
| 84 |
+
raise ValueError(
|
| 85 |
+
"Unable to create tensor, you should probably activate padding "
|
| 86 |
+
"with 'padding=True' to have batched tensors with the same length."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
for key, value in self.items():
|
| 90 |
+
self[key] = recursive_converter(converter, value)
|
| 91 |
+
return self
|
| 92 |
+
|
| 93 |
+
def to(self, *args, **kwargs) -> "MiniCPMOBatchFeature":
|
| 94 |
+
requires_backends(self, ["torch"])
|
| 95 |
+
import torch
|
| 96 |
+
|
| 97 |
+
def cast_tensor(v):
|
| 98 |
+
if not torch.is_tensor(v):
|
| 99 |
+
return v
|
| 100 |
+
|
| 101 |
+
if torch.is_floating_point(v):
|
| 102 |
+
return v.to(*args, **kwargs)
|
| 103 |
+
elif device is not None:
|
| 104 |
+
return v.to(device=device)
|
| 105 |
+
else:
|
| 106 |
+
return v
|
| 107 |
+
|
| 108 |
+
new_data = {}
|
| 109 |
+
device = kwargs.get("device")
|
| 110 |
+
if device is None and len(args) > 0:
|
| 111 |
+
arg = args[0]
|
| 112 |
+
if is_torch_dtype(arg):
|
| 113 |
+
pass
|
| 114 |
+
elif isinstance(arg, str) or is_torch_device(arg) or isinstance(arg, int):
|
| 115 |
+
device = arg
|
| 116 |
+
else:
|
| 117 |
+
raise ValueError(f"Attempting to cast a BatchFeature to type {str(arg)}. This is not supported.")
|
| 118 |
+
|
| 119 |
+
# We cast only floating point tensors to avoid issues with tokenizers casting `LongTensor` to `FloatTensor`
|
| 120 |
+
for k, v in self.items():
|
| 121 |
+
new_data[k] = recursive_converter(cast_tensor, v)
|
| 122 |
+
self.data = new_data
|
| 123 |
+
return self
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
class MiniCPMVImageProcessor(BaseImageProcessor):
|
| 127 |
+
model_input_names = ["pixel_values"]
|
| 128 |
+
|
| 129 |
+
def __init__(self, max_slice_nums=9, scale_resolution=448, patch_size=14, **kwargs):
|
| 130 |
+
super().__init__(**kwargs)
|
| 131 |
+
self.max_slice_nums = max_slice_nums
|
| 132 |
+
self.scale_resolution = scale_resolution
|
| 133 |
+
self.patch_size = patch_size
|
| 134 |
+
self.use_image_id = kwargs.pop("use_image_id", False)
|
| 135 |
+
self.image_feature_size = kwargs.pop("image_feature_size", 64)
|
| 136 |
+
self.im_start_token = kwargs.pop("im_start", "<image>")
|
| 137 |
+
self.im_end_token = kwargs.pop("im_end", "</image>")
|
| 138 |
+
self.slice_start_token = kwargs.pop("slice_start", "<slice>")
|
| 139 |
+
self.slice_end_token = kwargs.pop("slice_end", "</slice>")
|
| 140 |
+
self.unk_token = kwargs.pop("unk", "<unk>")
|
| 141 |
+
self.im_id_start = kwargs.pop("im_id_start", "<image_id>")
|
| 142 |
+
self.im_id_end = kwargs.pop("im_id_end", "</image_id>")
|
| 143 |
+
self.slice_mode = kwargs.pop("slice_mode", True)
|
| 144 |
+
|
| 145 |
+
self.mean = np.array(kwargs.pop("norm_mean", [0.5, 0.5, 0.5]))
|
| 146 |
+
self.std = np.array(kwargs.pop("norm_std", [0.5, 0.5, 0.5]))
|
| 147 |
+
self.version = kwargs.pop("version", 2.0)
|
| 148 |
+
|
| 149 |
+
@staticmethod
|
| 150 |
+
def ensure_divide(length, patch_size):
|
| 151 |
+
return max(round(length / patch_size) * patch_size, patch_size)
|
| 152 |
+
|
| 153 |
+
def find_best_resize(self, original_size, scale_resolution, patch_size, allow_upscale=False):
|
| 154 |
+
width, height = original_size
|
| 155 |
+
if (width * height > scale_resolution * scale_resolution) or allow_upscale:
|
| 156 |
+
r = width / height
|
| 157 |
+
height = int(scale_resolution / math.sqrt(r))
|
| 158 |
+
width = int(height * r)
|
| 159 |
+
best_width = self.ensure_divide(width, patch_size)
|
| 160 |
+
best_height = self.ensure_divide(height, patch_size)
|
| 161 |
+
return best_width, best_height
|
| 162 |
+
|
| 163 |
+
def get_refine_size(self, original_size, grid, scale_resolution, patch_size, allow_upscale=False):
|
| 164 |
+
width, height = original_size
|
| 165 |
+
grid_x, grid_y = grid
|
| 166 |
+
|
| 167 |
+
refine_width = self.ensure_divide(width, grid_x)
|
| 168 |
+
refine_height = self.ensure_divide(height, grid_y)
|
| 169 |
+
|
| 170 |
+
grid_width = refine_width / grid_x
|
| 171 |
+
grid_height = refine_height / grid_y
|
| 172 |
+
|
| 173 |
+
best_grid_size = self.find_best_resize(
|
| 174 |
+
(grid_width, grid_height), scale_resolution, patch_size, allow_upscale=allow_upscale
|
| 175 |
+
)
|
| 176 |
+
refine_size = (best_grid_size[0] * grid_x, best_grid_size[1] * grid_y)
|
| 177 |
+
return refine_size
|
| 178 |
+
|
| 179 |
+
@staticmethod
|
| 180 |
+
def split_to_patches(image, grid):
|
| 181 |
+
patches = []
|
| 182 |
+
width, height = image.size
|
| 183 |
+
grid_x = int(width / grid[0])
|
| 184 |
+
grid_y = int(height / grid[1])
|
| 185 |
+
for i in range(0, height, grid_y):
|
| 186 |
+
images = []
|
| 187 |
+
for j in range(0, width, grid_x):
|
| 188 |
+
box = (j, i, j + grid_x, i + grid_y)
|
| 189 |
+
patch = image.crop(box)
|
| 190 |
+
images.append(patch)
|
| 191 |
+
patches.append(images)
|
| 192 |
+
return patches
|
| 193 |
+
|
| 194 |
+
def slice_image(self, image, max_slice_nums=9, scale_resolution=448, patch_size=14, never_split=False):
|
| 195 |
+
original_size = image.size
|
| 196 |
+
source_image = None
|
| 197 |
+
best_grid = self.get_sliced_grid(original_size, max_slice_nums, never_split)
|
| 198 |
+
patches = []
|
| 199 |
+
|
| 200 |
+
if best_grid is None:
|
| 201 |
+
# dont need to slice, upsample
|
| 202 |
+
best_size = self.find_best_resize(original_size, scale_resolution, patch_size, allow_upscale=True)
|
| 203 |
+
source_image = image.resize(best_size, resample=Image.Resampling.BICUBIC)
|
| 204 |
+
else:
|
| 205 |
+
# source image, down-sampling and ensure divided by patch_size
|
| 206 |
+
best_resize = self.find_best_resize(original_size, scale_resolution, patch_size)
|
| 207 |
+
source_image = image.copy().resize(best_resize, resample=Image.Resampling.BICUBIC)
|
| 208 |
+
refine_size = self.get_refine_size(
|
| 209 |
+
original_size, best_grid, scale_resolution, patch_size, allow_upscale=True
|
| 210 |
+
)
|
| 211 |
+
refine_image = image.resize(refine_size, resample=Image.Resampling.BICUBIC)
|
| 212 |
+
patches = self.split_to_patches(refine_image, best_grid)
|
| 213 |
+
|
| 214 |
+
return source_image, patches, best_grid
|
| 215 |
+
|
| 216 |
+
def get_grid_placeholder(self, grid):
|
| 217 |
+
if grid is None:
|
| 218 |
+
return ""
|
| 219 |
+
slice_image_placeholder = (
|
| 220 |
+
self.slice_start_token + self.unk_token * self.image_feature_size + self.slice_end_token
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
cols = grid[0]
|
| 224 |
+
rows = grid[1]
|
| 225 |
+
slices = []
|
| 226 |
+
for i in range(rows):
|
| 227 |
+
lines = []
|
| 228 |
+
for j in range(cols):
|
| 229 |
+
lines.append(slice_image_placeholder)
|
| 230 |
+
slices.append("".join(lines))
|
| 231 |
+
|
| 232 |
+
slice_placeholder = "\n".join(slices)
|
| 233 |
+
return slice_placeholder
|
| 234 |
+
|
| 235 |
+
def get_image_id_placeholder(self, idx=0):
|
| 236 |
+
return f"{self.im_id_start}{idx}{self.im_id_end}"
|
| 237 |
+
|
| 238 |
+
def get_sliced_images(self, image, max_slice_nums=None):
|
| 239 |
+
slice_images = []
|
| 240 |
+
|
| 241 |
+
if not self.slice_mode:
|
| 242 |
+
return [image]
|
| 243 |
+
|
| 244 |
+
max_slice_nums = self.max_slice_nums if max_slice_nums is None else int(max_slice_nums)
|
| 245 |
+
assert max_slice_nums > 0
|
| 246 |
+
source_image, patches, sliced_grid = self.slice_image(
|
| 247 |
+
image, max_slice_nums, self.scale_resolution, self.patch_size # default: 9 # default: 448 # default: 14
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
slice_images.append(source_image)
|
| 251 |
+
if len(patches) > 0:
|
| 252 |
+
for i in range(len(patches)):
|
| 253 |
+
for j in range(len(patches[0])):
|
| 254 |
+
slice_images.append(patches[i][j])
|
| 255 |
+
return slice_images
|
| 256 |
+
|
| 257 |
+
def get_sliced_grid(self, image_size, max_slice_nums, nerver_split=False):
|
| 258 |
+
original_width, original_height = image_size
|
| 259 |
+
log_ratio = math.log(original_width / original_height)
|
| 260 |
+
ratio = original_width * original_height / (self.scale_resolution * self.scale_resolution)
|
| 261 |
+
multiple = min(math.ceil(ratio), max_slice_nums)
|
| 262 |
+
if multiple <= 1 or nerver_split:
|
| 263 |
+
return None
|
| 264 |
+
candidate_split_grids_nums = []
|
| 265 |
+
for i in [multiple - 1, multiple, multiple + 1]:
|
| 266 |
+
if i == 1 or i > max_slice_nums:
|
| 267 |
+
continue
|
| 268 |
+
candidate_split_grids_nums.append(i)
|
| 269 |
+
|
| 270 |
+
candidate_grids = []
|
| 271 |
+
for split_grids_nums in candidate_split_grids_nums:
|
| 272 |
+
m = 1
|
| 273 |
+
while m <= split_grids_nums:
|
| 274 |
+
if split_grids_nums % m == 0:
|
| 275 |
+
candidate_grids.append([m, split_grids_nums // m])
|
| 276 |
+
m += 1
|
| 277 |
+
|
| 278 |
+
best_grid = [1, 1]
|
| 279 |
+
min_error = float("inf")
|
| 280 |
+
for grid in candidate_grids:
|
| 281 |
+
error = abs(log_ratio - math.log(grid[0] / grid[1]))
|
| 282 |
+
if error < min_error:
|
| 283 |
+
best_grid = grid
|
| 284 |
+
min_error = error
|
| 285 |
+
|
| 286 |
+
return best_grid
|
| 287 |
+
|
| 288 |
+
def get_slice_image_placeholder(self, image_size, image_idx=0, max_slice_nums=None, use_image_id=None):
|
| 289 |
+
max_slice_nums = self.max_slice_nums if max_slice_nums is None else int(max_slice_nums)
|
| 290 |
+
assert max_slice_nums > 0
|
| 291 |
+
grid = self.get_sliced_grid(image_size=image_size, max_slice_nums=max_slice_nums)
|
| 292 |
+
|
| 293 |
+
image_placeholder = self.im_start_token + self.unk_token * self.image_feature_size + self.im_end_token
|
| 294 |
+
use_image_id = self.use_image_id if use_image_id is None else bool(use_image_id)
|
| 295 |
+
if use_image_id:
|
| 296 |
+
final_placeholder = self.get_image_id_placeholder(image_idx) + image_placeholder
|
| 297 |
+
else:
|
| 298 |
+
final_placeholder = image_placeholder
|
| 299 |
+
|
| 300 |
+
if self.slice_mode:
|
| 301 |
+
final_placeholder = final_placeholder + self.get_grid_placeholder(grid=grid)
|
| 302 |
+
return final_placeholder
|
| 303 |
+
|
| 304 |
+
@staticmethod
|
| 305 |
+
def to_pil_image(image, rescale=None) -> Image.Image:
|
| 306 |
+
"""Converts `image` to a PIL Image. Optionally rescales it and puts the channel dimension back
|
| 307 |
+
as the last axis if needed.
|
| 308 |
+
|
| 309 |
+
Args:
|
| 310 |
+
image (`Image.Image` or `numpy.ndarray` or `torch.Tensor`):
|
| 311 |
+
The image to convert to the PIL Image format.
|
| 312 |
+
rescale (`bool`, *optional*):
|
| 313 |
+
whether to apply the scaling factor (to make pixel values integers between 0 and 255). Will
|
| 314 |
+
default to `True` if the image type is a floating type, `False` otherwise.
|
| 315 |
+
"""
|
| 316 |
+
if isinstance(image, Image.Image):
|
| 317 |
+
return image
|
| 318 |
+
if is_torch_tensor(image):
|
| 319 |
+
image = image.numpy()
|
| 320 |
+
|
| 321 |
+
if isinstance(image, np.ndarray):
|
| 322 |
+
if rescale is None:
|
| 323 |
+
# rescale default to the array being of floating type.
|
| 324 |
+
rescale = isinstance(image.flat[0], np.floating)
|
| 325 |
+
# If the channel as been moved to first dim, we put it back at the end.
|
| 326 |
+
if image.ndim == 3 and image.shape[0] in [1, 3]:
|
| 327 |
+
image = image.transpose(1, 2, 0)
|
| 328 |
+
if rescale:
|
| 329 |
+
image = image * 255
|
| 330 |
+
image = image.astype(np.uint8)
|
| 331 |
+
return Image.fromarray(image)
|
| 332 |
+
return image
|
| 333 |
+
|
| 334 |
+
def reshape_by_patch(self, image):
|
| 335 |
+
image = torch.from_numpy(image)
|
| 336 |
+
patch_size = self.patch_size
|
| 337 |
+
patches = torch.nn.functional.unfold(image, (patch_size, patch_size), stride=(patch_size, patch_size))
|
| 338 |
+
|
| 339 |
+
patches = patches.reshape(image.size(0), patch_size, patch_size, -1)
|
| 340 |
+
patches = patches.permute(0, 1, 3, 2).reshape(image.size(0), patch_size, -1)
|
| 341 |
+
return patches.numpy()
|
| 342 |
+
|
| 343 |
+
def preprocess(
|
| 344 |
+
self,
|
| 345 |
+
images: Union[Image.Image, List[Image.Image], List[List[Image.Image]]],
|
| 346 |
+
do_pad: Optional[bool] = True,
|
| 347 |
+
max_slice_nums: int = None,
|
| 348 |
+
return_tensors: Optional[Union[str, TensorType]] = None,
|
| 349 |
+
**kwargs,
|
| 350 |
+
) -> MiniCPMOBatchFeature:
|
| 351 |
+
if isinstance(images, Image.Image):
|
| 352 |
+
images_list = [[images]]
|
| 353 |
+
elif isinstance(images[0], Image.Image):
|
| 354 |
+
images_list = [images]
|
| 355 |
+
else:
|
| 356 |
+
images_list = images
|
| 357 |
+
|
| 358 |
+
new_images_list = []
|
| 359 |
+
image_sizes_list = []
|
| 360 |
+
tgt_sizes_list = []
|
| 361 |
+
|
| 362 |
+
for _images in images_list:
|
| 363 |
+
if _images is None or len(_images) == 0:
|
| 364 |
+
new_images_list.append([])
|
| 365 |
+
image_sizes_list.append([])
|
| 366 |
+
tgt_sizes_list.append([])
|
| 367 |
+
continue
|
| 368 |
+
if not valid_images(_images):
|
| 369 |
+
raise ValueError(
|
| 370 |
+
"Invalid image type. Must be of type PIL.Image.Image, numpy.ndarray, "
|
| 371 |
+
"torch.Tensor, tf.Tensor or jax.ndarray."
|
| 372 |
+
)
|
| 373 |
+
|
| 374 |
+
_images = [self.to_pil_image(image).convert("RGB") for image in _images]
|
| 375 |
+
input_data_format = infer_channel_dimension_format(np.array(_images[0]))
|
| 376 |
+
|
| 377 |
+
new_images = []
|
| 378 |
+
image_sizes = [image.size for image in _images]
|
| 379 |
+
tgt_sizes = []
|
| 380 |
+
for image in _images:
|
| 381 |
+
image_patches = self.get_sliced_images(image, max_slice_nums)
|
| 382 |
+
image_patches = [to_numpy_array(image).astype(np.float32) / 255 for image in image_patches]
|
| 383 |
+
image_patches = [
|
| 384 |
+
self.normalize(image=image, mean=self.mean, std=self.std, input_data_format=input_data_format)
|
| 385 |
+
for image in image_patches
|
| 386 |
+
]
|
| 387 |
+
image_patches = [
|
| 388 |
+
to_channel_dimension_format(image, ChannelDimension.FIRST, input_channel_dim=input_data_format)
|
| 389 |
+
for image in image_patches
|
| 390 |
+
]
|
| 391 |
+
for slice_image in image_patches:
|
| 392 |
+
new_images.append(self.reshape_by_patch(slice_image))
|
| 393 |
+
tgt_sizes.append(
|
| 394 |
+
np.array((slice_image.shape[1] // self.patch_size, slice_image.shape[2] // self.patch_size))
|
| 395 |
+
)
|
| 396 |
+
|
| 397 |
+
if tgt_sizes:
|
| 398 |
+
tgt_sizes = np.vstack(tgt_sizes)
|
| 399 |
+
|
| 400 |
+
new_images_list.append(new_images)
|
| 401 |
+
image_sizes_list.append(image_sizes)
|
| 402 |
+
tgt_sizes_list.append(tgt_sizes)
|
| 403 |
+
return MiniCPMOBatchFeature(
|
| 404 |
+
data={"pixel_values": new_images_list, "image_sizes": image_sizes_list, "tgt_sizes": tgt_sizes_list},
|
| 405 |
+
tensor_type=return_tensors,
|
| 406 |
+
)
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
AutoImageProcessor.register("MiniCPMVImageProcessor", MiniCPMVImageProcessor)
|
| 410 |
+
|
| 411 |
+
|
| 412 |
+
def chunk_audio(audio: np.ndarray, max_duration_seconds: int = 30, sample_rate: int = 16000) -> List[np.ndarray]:
|
| 413 |
+
"""split long audio into chunks
|
| 414 |
+
|
| 415 |
+
Args:
|
| 416 |
+
audio:
|
| 417 |
+
max_duration_seconds:
|
| 418 |
+
sample_rate:
|
| 419 |
+
|
| 420 |
+
Returns:
|
| 421 |
+
chunks
|
| 422 |
+
"""
|
| 423 |
+
max_len = int(max_duration_seconds * sample_rate)
|
| 424 |
+
|
| 425 |
+
if len(audio) <= max_len:
|
| 426 |
+
return [audio]
|
| 427 |
+
|
| 428 |
+
chunks = []
|
| 429 |
+
for i in range(0, len(audio), max_len):
|
| 430 |
+
chunk = audio[i : i + max_len]
|
| 431 |
+
chunks.append(chunk)
|
| 432 |
+
|
| 433 |
+
return chunks
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def process_audio_batch(
|
| 437 |
+
audios: Union[np.ndarray, List[np.ndarray], List[List[np.ndarray]]],
|
| 438 |
+
feature_extractor,
|
| 439 |
+
sampling_rate: int = 16000,
|
| 440 |
+
max_duration_seconds: int = 30,
|
| 441 |
+
return_attention_mask: bool = True,
|
| 442 |
+
) -> Tuple[torch.Tensor, List[torch.Tensor]]:
|
| 443 |
+
"""extract audio mel features
|
| 444 |
+
|
| 445 |
+
Args:
|
| 446 |
+
audios:
|
| 447 |
+
feature_extractor: WhisperFeatureExtractor
|
| 448 |
+
sampling_rate:
|
| 449 |
+
max_duration_seconds:
|
| 450 |
+
return_attention_mask:
|
| 451 |
+
|
| 452 |
+
Returns:
|
| 453 |
+
(audio_features, audio_feature_lens)
|
| 454 |
+
audio_features: [batch_size, n_mels, max_frames]
|
| 455 |
+
audio_feature_lens:
|
| 456 |
+
"""
|
| 457 |
+
if isinstance(audios, np.ndarray):
|
| 458 |
+
audios_list = [[audios]]
|
| 459 |
+
elif len(audios) > 0 and isinstance(audios[0], np.ndarray):
|
| 460 |
+
audios_list = [audios]
|
| 461 |
+
else:
|
| 462 |
+
audios_list = audios
|
| 463 |
+
|
| 464 |
+
audio_features_all = []
|
| 465 |
+
audio_feature_lens_list = []
|
| 466 |
+
|
| 467 |
+
for batch_audios in audios_list:
|
| 468 |
+
batch_lens = []
|
| 469 |
+
|
| 470 |
+
for audio in batch_audios:
|
| 471 |
+
chunks = chunk_audio(audio, max_duration_seconds, sampling_rate)
|
| 472 |
+
|
| 473 |
+
for chunk in chunks:
|
| 474 |
+
audio_input = feature_extractor(
|
| 475 |
+
chunk,
|
| 476 |
+
sampling_rate=sampling_rate,
|
| 477 |
+
return_tensors="pt",
|
| 478 |
+
padding="max_length",
|
| 479 |
+
return_attention_mask=return_attention_mask,
|
| 480 |
+
)
|
| 481 |
+
|
| 482 |
+
audio_feature = audio_input["input_features"] # [1, 80, frames]
|
| 483 |
+
|
| 484 |
+
if return_attention_mask:
|
| 485 |
+
actual_len = audio_input["attention_mask"].sum(dim=1) # Tensor([frames])
|
| 486 |
+
audio_feature = audio_feature[:, :, : actual_len[0]]
|
| 487 |
+
batch_lens.append(actual_len[0])
|
| 488 |
+
else:
|
| 489 |
+
batch_lens.append(torch.tensor(audio_feature.shape[2]))
|
| 490 |
+
|
| 491 |
+
audio_features_all.append(audio_feature.squeeze(0)) # [80, frames]
|
| 492 |
+
|
| 493 |
+
if len(batch_lens) > 0:
|
| 494 |
+
audio_feature_lens_list.append(torch.hstack(batch_lens))
|
| 495 |
+
else:
|
| 496 |
+
audio_feature_lens_list.append(torch.tensor([]))
|
| 497 |
+
|
| 498 |
+
# pad to same length
|
| 499 |
+
if audio_features_all:
|
| 500 |
+
audio_features = torch.nn.utils.rnn.pad_sequence(
|
| 501 |
+
[feat.transpose(0, 1) for feat in audio_features_all], batch_first=True, padding_value=0.0
|
| 502 |
+
).transpose(
|
| 503 |
+
1, 2
|
| 504 |
+
) # [batch, 80, max_frames]
|
| 505 |
+
else:
|
| 506 |
+
audio_features = torch.tensor([])
|
| 507 |
+
|
| 508 |
+
return audio_features, audio_feature_lens_list
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def regroup_audio_features(
|
| 512 |
+
audio_features: torch.Tensor, audio_feature_lens: List[torch.Tensor], regroup_seconds: int, fps: int = 100
|
| 513 |
+
) -> Tuple[torch.Tensor, List[torch.Tensor]]:
|
| 514 |
+
"""regroup audio features to fixed duration
|
| 515 |
+
|
| 516 |
+
Args:
|
| 517 |
+
audio_features: [batch, n_mels, frames]
|
| 518 |
+
audio_feature_lens: each batch's actual length
|
| 519 |
+
regroup_seconds: regroup duration (seconds)
|
| 520 |
+
fps: frames per second
|
| 521 |
+
|
| 522 |
+
Returns:
|
| 523 |
+
(regrouped_features, regrouped_lens)
|
| 524 |
+
"""
|
| 525 |
+
# flatten to continuous frames sequence
|
| 526 |
+
all_lens = []
|
| 527 |
+
for lens in audio_feature_lens:
|
| 528 |
+
if isinstance(lens, torch.Tensor):
|
| 529 |
+
all_lens.extend(lens.tolist())
|
| 530 |
+
elif isinstance(lens, list):
|
| 531 |
+
all_lens.extend([int(x) for x in lens])
|
| 532 |
+
|
| 533 |
+
if len(all_lens) == 0:
|
| 534 |
+
return torch.tensor([]), []
|
| 535 |
+
|
| 536 |
+
# concatenate all valid features
|
| 537 |
+
flat_slices = [audio_features[i, :, :L] for i, L in enumerate(all_lens)] # [n_mels, L]
|
| 538 |
+
|
| 539 |
+
if len(flat_slices) == 1:
|
| 540 |
+
full_feat = flat_slices[0]
|
| 541 |
+
else:
|
| 542 |
+
full_feat = torch.cat(flat_slices, dim=1) # [n_mels, total_frames]
|
| 543 |
+
|
| 544 |
+
# split to fixed frames
|
| 545 |
+
frames_per_seg = int(regroup_seconds * fps)
|
| 546 |
+
segments = []
|
| 547 |
+
|
| 548 |
+
for start in range(0, full_feat.size(1), frames_per_seg):
|
| 549 |
+
seg = full_feat[:, start : start + frames_per_seg]
|
| 550 |
+
if seg.size(1) > 0:
|
| 551 |
+
segments.append(seg)
|
| 552 |
+
|
| 553 |
+
if len(segments) == 0:
|
| 554 |
+
return torch.tensor([]), []
|
| 555 |
+
|
| 556 |
+
# pad and convert to batch
|
| 557 |
+
seg_lens = [s.size(1) for s in segments]
|
| 558 |
+
segs_transposed = [s.transpose(0, 1) for s in segments]
|
| 559 |
+
|
| 560 |
+
padded = torch.nn.utils.rnn.pad_sequence(segs_transposed, batch_first=True, padding_value=0.0) # [N, max_T, n_mels]
|
| 561 |
+
|
| 562 |
+
padded = padded.transpose(1, 2) # [N, n_mels, max_T]
|
| 563 |
+
lens_tensor = torch.tensor(seg_lens, dtype=torch.int32, device=padded.device)
|
| 564 |
+
|
| 565 |
+
return padded, [lens_tensor]
|
| 566 |
+
|
| 567 |
+
|
| 568 |
+
class MiniCPMAAudioProcessor(WhisperFeatureExtractor):
|
| 569 |
+
"""
|
| 570 |
+
On top of WhisperFeatureExtractor:
|
| 571 |
+
- support dynamic_log_norm (original max-8dB, adjustable dynamic_range_db)
|
| 572 |
+
- or fixed log_floor_db (e.g. -10dB)
|
| 573 |
+
- this is because we need to do streaming scheme, in which we can't do dynamic setting
|
| 574 |
+
- this can be modified in the middle, through set_dynamic_log_norm
|
| 575 |
+
Two paths (torch / numpy) keep consistent clipping and scaling order:
|
| 576 |
+
log10 -> (dynamic/fixed lower limit clipping) -> (+4)/4
|
| 577 |
+
"""
|
| 578 |
+
|
| 579 |
+
def __init__(
|
| 580 |
+
self,
|
| 581 |
+
*args,
|
| 582 |
+
dynamic_log_norm: bool = True,
|
| 583 |
+
dynamic_range_db: float = 8.0,
|
| 584 |
+
log_floor_db: float = -10.0,
|
| 585 |
+
**kwargs,
|
| 586 |
+
):
|
| 587 |
+
super().__init__(*args, **kwargs)
|
| 588 |
+
self.dynamic_log_norm = bool(dynamic_log_norm)
|
| 589 |
+
self.dynamic_range_db = float(dynamic_range_db)
|
| 590 |
+
self.log_floor_db = float(log_floor_db)
|
| 591 |
+
|
| 592 |
+
def set_spac_log_norm(
|
| 593 |
+
self,
|
| 594 |
+
dynamic_range_db: Optional[float] = None,
|
| 595 |
+
log_floor_db: Optional[float] = None,
|
| 596 |
+
*,
|
| 597 |
+
inplace: bool = True,
|
| 598 |
+
) -> "MiniCPMAAudioProcessor":
|
| 599 |
+
"""Hot update dynamic/fixed lower limit strategy.
|
| 600 |
+
|
| 601 |
+
Args:
|
| 602 |
+
enabled: True=use dynamic threshold (max - dynamic_range_db), False=use fixed lower limit log_floor_db.
|
| 603 |
+
None means keep unchanged.
|
| 604 |
+
dynamic_range_db: dynamic range (dB), only effective when enabled=True. None means keep unchanged.
|
| 605 |
+
log_floor_db: fixed log floor (dB, usually <= 0), only effective when enabled=False. None means keep unchanged.
|
| 606 |
+
inplace: True directly modify current instance; False return a shallow copy and modify on it.
|
| 607 |
+
|
| 608 |
+
Returns:
|
| 609 |
+
self or new instance (when inplace=False).
|
| 610 |
+
"""
|
| 611 |
+
|
| 612 |
+
target = self if inplace else copy.copy(self)
|
| 613 |
+
|
| 614 |
+
if dynamic_range_db is not None:
|
| 615 |
+
val = float(dynamic_range_db)
|
| 616 |
+
if val < 0:
|
| 617 |
+
raise ValueError("dynamic_range_db must be >= 0.")
|
| 618 |
+
target.dynamic_log_norm = True # explicitly set the value to dynamic mode
|
| 619 |
+
target.dynamic_range_db = val
|
| 620 |
+
|
| 621 |
+
if log_floor_db is not None:
|
| 622 |
+
val = float(log_floor_db)
|
| 623 |
+
# usually log10(mel) maximum is not more than ~0dB, floor should be <= 0; here do loose validation
|
| 624 |
+
if val > 0:
|
| 625 |
+
raise ValueError("log_floor_db should be <= 0 (log10 scale).")
|
| 626 |
+
target.dynamic_log_norm = False # explicitly set the value to fixed lower limit mode
|
| 627 |
+
target.log_floor_db = val
|
| 628 |
+
|
| 629 |
+
return target
|
| 630 |
+
|
| 631 |
+
def _np_extract_fbank_features(self, waveform_batch: np.ndarray, device: str) -> np.ndarray:
|
| 632 |
+
"""NumPy version consistent with upstream, but replace max-8dB with configurable dynamic/fixed lower limit clipping."""
|
| 633 |
+
if device != "cpu":
|
| 634 |
+
raise ValueError(
|
| 635 |
+
f"Got device `{device}` for feature extraction, but feature extraction on CUDA accelerator "
|
| 636 |
+
"devices requires torch. Set device='cpu' or install torch."
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
log_spec_batch: List[np.ndarray] = []
|
| 640 |
+
for waveform in waveform_batch:
|
| 641 |
+
# generate log10 Mel
|
| 642 |
+
log_spec = spectrogram(
|
| 643 |
+
waveform,
|
| 644 |
+
window_function(self.n_fft, "hann"),
|
| 645 |
+
frame_length=self.n_fft,
|
| 646 |
+
hop_length=self.hop_length,
|
| 647 |
+
power=2.0,
|
| 648 |
+
dither=self.dither,
|
| 649 |
+
mel_filters=self.mel_filters,
|
| 650 |
+
log_mel="log10",
|
| 651 |
+
)
|
| 652 |
+
# consistent with upstream: remove the last frame
|
| 653 |
+
log_spec = log_spec[:, :-1]
|
| 654 |
+
|
| 655 |
+
# dynamic/fixed clipping
|
| 656 |
+
if self.dynamic_log_norm:
|
| 657 |
+
threshold = log_spec.max() - self.dynamic_range_db
|
| 658 |
+
log_spec = np.maximum(log_spec, threshold)
|
| 659 |
+
else:
|
| 660 |
+
log_spec = np.maximum(log_spec, self.log_floor_db)
|
| 661 |
+
|
| 662 |
+
# consistent with Whisper linear scaling
|
| 663 |
+
log_spec = (log_spec + 4.0) / 4.0
|
| 664 |
+
|
| 665 |
+
log_spec_batch.append(log_spec)
|
| 666 |
+
|
| 667 |
+
return np.array(log_spec_batch)
|
| 668 |
+
|
| 669 |
+
def _torch_extract_fbank_features(self, waveform: np.ndarray, device: str = "cpu") -> np.ndarray:
|
| 670 |
+
if torch is None:
|
| 671 |
+
raise RuntimeError("PyTorch is not installed, cannot compute STFT on GPU.")
|
| 672 |
+
|
| 673 |
+
waveform = torch.from_numpy(waveform).to(device, torch.float32)
|
| 674 |
+
window = torch.hann_window(self.n_fft, device=device)
|
| 675 |
+
|
| 676 |
+
if self.dither != 0.0:
|
| 677 |
+
waveform = waveform + self.dither * torch.randn_like(waveform)
|
| 678 |
+
|
| 679 |
+
stft = torch.stft(waveform, n_fft=self.n_fft, hop_length=self.hop_length, window=window, return_complex=True)
|
| 680 |
+
magnitudes = stft[..., :-1].abs() ** 2
|
| 681 |
+
|
| 682 |
+
mel_filters = torch.from_numpy(self.mel_filters).to(device, torch.float32) # [n_mels, 1+n_fft//2]
|
| 683 |
+
mel_spec = mel_filters.T @ magnitudes # [..., n_mels, T]
|
| 684 |
+
|
| 685 |
+
log_spec = torch.clamp(mel_spec, min=1e-10).log10() # <= 0
|
| 686 |
+
|
| 687 |
+
if self.dynamic_log_norm:
|
| 688 |
+
if waveform.dim() == 2:
|
| 689 |
+
max_val_t = log_spec.max(dim=2, keepdim=True)[0] # over T
|
| 690 |
+
max_val_bt = max_val_t.max(dim=1, keepdim=True)[0] # over mel
|
| 691 |
+
threshold = max_val_bt - self.dynamic_range_db
|
| 692 |
+
log_spec = torch.maximum(log_spec, threshold)
|
| 693 |
+
else:
|
| 694 |
+
threshold = log_spec.max() - self.dynamic_range_db
|
| 695 |
+
log_spec = torch.maximum(log_spec, threshold)
|
| 696 |
+
else:
|
| 697 |
+
floor_tensor = torch.tensor(self.log_floor_db, dtype=log_spec.dtype, device=log_spec.device)
|
| 698 |
+
log_spec = torch.maximum(log_spec, floor_tensor)
|
| 699 |
+
|
| 700 |
+
log_spec = (log_spec + 4.0) / 4.0
|
| 701 |
+
|
| 702 |
+
if device != "cpu":
|
| 703 |
+
log_spec = log_spec.detach().cpu()
|
| 704 |
+
return log_spec.numpy()
|
| 705 |
+
|
| 706 |
+
def process(self, *args, **kwargs):
|
| 707 |
+
"""Alias of __call__ for convenience."""
|
| 708 |
+
return self.__call__(*args, **kwargs)
|
| 709 |
+
|
| 710 |
+
|
| 711 |
+
class StreamingMelProcessorExact:
|
| 712 |
+
"""Strictly offline equivalent streaming Mel processor.
|
| 713 |
+
|
| 714 |
+
- accumulate all historical audio into buffer; use the same feature_extractor to calculate the entire mel after each addition.
|
| 715 |
+
- only output "stable" frames: the frame center does not depend on future (right) context, i.e. center + n_fft//2 <= current buffer length.
|
| 716 |
+
- output the last batch of frames at the end (flush), ensuring complete consistency with offline full-calculation.
|
| 717 |
+
|
| 718 |
+
Cost: Each call performs feature extraction on the accumulated buffer (can be optimized to incremental if needed).
|
| 719 |
+
"""
|
| 720 |
+
|
| 721 |
+
def __init__(
|
| 722 |
+
self,
|
| 723 |
+
feature_extractor: MiniCPMAAudioProcessor,
|
| 724 |
+
chunk_ms: int = 100,
|
| 725 |
+
first_chunk_ms: Optional[int] = None,
|
| 726 |
+
sample_rate: int = 16000,
|
| 727 |
+
n_fft: int = 400,
|
| 728 |
+
hop_length: int = 160,
|
| 729 |
+
n_mels: int = 80,
|
| 730 |
+
cnn_redundancy_ms: int = 10, # (given in ms, usually 10ms=1 frame)
|
| 731 |
+
# sliding window parameters
|
| 732 |
+
enable_sliding_window: bool = False, # whether to enable sliding window
|
| 733 |
+
slide_trigger_seconds: float = 30.0, # trigger threshold for sliding window in seconds
|
| 734 |
+
slide_stride_seconds: float = 10.0, # stride for sliding window in seconds
|
| 735 |
+
):
|
| 736 |
+
self.feature_extractor = feature_extractor
|
| 737 |
+
self.chunk_ms = chunk_ms
|
| 738 |
+
self.first_chunk_ms = first_chunk_ms if first_chunk_ms is not None else chunk_ms
|
| 739 |
+
self.sample_rate = sample_rate
|
| 740 |
+
self.n_fft = n_fft
|
| 741 |
+
self.hop_length = hop_length
|
| 742 |
+
self.n_mels = n_mels
|
| 743 |
+
|
| 744 |
+
self.chunk_samples = int(round(chunk_ms * sample_rate / 1000))
|
| 745 |
+
self.chunk_frames = self.chunk_samples // hop_length
|
| 746 |
+
# align to hop_length to avoid frame boundary issues
|
| 747 |
+
hop = self.hop_length
|
| 748 |
+
raw_first_samples = int(round(self.first_chunk_ms * sample_rate / 1000))
|
| 749 |
+
aligned_first = max(hop, (raw_first_samples // hop) * hop)
|
| 750 |
+
self.first_chunk_samples = aligned_first
|
| 751 |
+
self.half_window = n_fft // 2 # required right context
|
| 752 |
+
|
| 753 |
+
# redundancy frames (in frames), <=1 frame: 10ms → 1 frame
|
| 754 |
+
self.cnn_redundancy_ms = cnn_redundancy_ms
|
| 755 |
+
self.cnn_redundancy_samples = int(cnn_redundancy_ms * sample_rate / 1000)
|
| 756 |
+
self.cnn_redundancy_frames = max(0, self.cnn_redundancy_samples // hop_length)
|
| 757 |
+
|
| 758 |
+
# sliding window configuration (Trigger mode)
|
| 759 |
+
self.enable_sliding_window = enable_sliding_window
|
| 760 |
+
self.trigger_seconds = slide_trigger_seconds
|
| 761 |
+
self.slide_seconds = slide_stride_seconds
|
| 762 |
+
|
| 763 |
+
# shift/base (global frame coordinates)
|
| 764 |
+
self.left_samples_dropped = 0 # samples dropped from the left
|
| 765 |
+
self.base_T = 0 # index of the "global frame" corresponding to mel_full[:, :, 0]
|
| 766 |
+
|
| 767 |
+
self.reset()
|
| 768 |
+
|
| 769 |
+
def reset(self):
|
| 770 |
+
self.buffer = np.zeros(0, dtype=np.float32)
|
| 771 |
+
self.last_emitted_T = 0
|
| 772 |
+
self.total_samples_processed = 0
|
| 773 |
+
self.chunk_count = 0
|
| 774 |
+
self.is_first = True
|
| 775 |
+
self.left_samples_dropped = 0
|
| 776 |
+
self.base_T = 0
|
| 777 |
+
|
| 778 |
+
def get_chunk_size(self) -> int:
|
| 779 |
+
return self.first_chunk_samples if self.is_first else self.chunk_samples
|
| 780 |
+
|
| 781 |
+
def get_expected_output_frames(self) -> int:
|
| 782 |
+
raise NotImplementedError("get_expected_output_frames is not implemented")
|
| 783 |
+
|
| 784 |
+
def _extract_full(self) -> torch.Tensor:
|
| 785 |
+
# when buffer length is less than n_fft, Whisper's internal STFT will raise an error in center=True and pad mode
|
| 786 |
+
# (pad is greater than input length). At this time, there is no stable frame to output, so return empty features directly.
|
| 787 |
+
if len(self.buffer) < self.n_fft:
|
| 788 |
+
raise ValueError(f"buffer length is shorter than n_fft {len(self.buffer)} < {self.n_fft}")
|
| 789 |
+
# if buffer length is less than 5s, use set_spac_log_norm(log_floor_db=-10) or the last cached result
|
| 790 |
+
if len(self.buffer) < 5 * self.sample_rate:
|
| 791 |
+
# TODO: here the best is to do some experiments to choose the best one, now this is selected through experience, can see MiniCPMAAudioProcessor's main implementation
|
| 792 |
+
self.feature_extractor.set_spac_log_norm(log_floor_db=-10)
|
| 793 |
+
# if buffer length is greater than 5s, use set_spac_log_norm(dynamic_range_db=8)
|
| 794 |
+
else:
|
| 795 |
+
self.feature_extractor.set_spac_log_norm(dynamic_range_db=8)
|
| 796 |
+
feats = self.feature_extractor(
|
| 797 |
+
self.buffer,
|
| 798 |
+
sampling_rate=self.sample_rate,
|
| 799 |
+
return_tensors="pt",
|
| 800 |
+
padding=False,
|
| 801 |
+
)
|
| 802 |
+
return feats.input_features # [1, 80, T]
|
| 803 |
+
|
| 804 |
+
def _stable_frames_count(self) -> int:
|
| 805 |
+
# number of stable frames = floor((len(buffer) - half_window) / hop) + 1, minimum is 0
|
| 806 |
+
L = int(self.buffer.shape[0])
|
| 807 |
+
if L <= 0:
|
| 808 |
+
return 0
|
| 809 |
+
if L < self.half_window:
|
| 810 |
+
return 0
|
| 811 |
+
return max(0, (L - self.half_window) // self.hop_length + 1)
|
| 812 |
+
|
| 813 |
+
def _maybe_slide_buffer(self):
|
| 814 |
+
"""Trigger mode sliding window: when the buffer reaches the trigger threshold, slide a fixed length window."""
|
| 815 |
+
if not self.enable_sliding_window:
|
| 816 |
+
return
|
| 817 |
+
|
| 818 |
+
sr = self.sample_rate
|
| 819 |
+
hop = self.hop_length
|
| 820 |
+
L = len(self.buffer)
|
| 821 |
+
|
| 822 |
+
# convert seconds to samples
|
| 823 |
+
trigger_samples = int(self.trigger_seconds * sr)
|
| 824 |
+
stride_samples = int(self.slide_seconds * sr)
|
| 825 |
+
|
| 826 |
+
# check if the trigger threshold is reached
|
| 827 |
+
if L < trigger_samples:
|
| 828 |
+
return
|
| 829 |
+
|
| 830 |
+
# calculate the number of samples to drop (fixed sliding stride_samples)
|
| 831 |
+
drop = stride_samples
|
| 832 |
+
|
| 833 |
+
# cannot drop the left context that is still needed for subsequent emission
|
| 834 |
+
# in trigger mode, we only need to protect the minimum necessary data
|
| 835 |
+
# i.e. ensure that we do not discard frames that may be needed in the future
|
| 836 |
+
last_emitted_local = self.last_emitted_T - self.base_T
|
| 837 |
+
|
| 838 |
+
# only protect necessary context (e.g. the most recent 1 second data)
|
| 839 |
+
min_keep_seconds = 1.0 # keep at least 1 second of data to ensure continuity
|
| 840 |
+
min_keep_samples = int(min_keep_seconds * sr)
|
| 841 |
+
|
| 842 |
+
# guard_samples are the minimum samples we must keep
|
| 843 |
+
guard_samples = min(min_keep_samples, L - drop)
|
| 844 |
+
|
| 845 |
+
# limit: do not exceed the safe boundary; and align hop
|
| 846 |
+
max_allowed_drop = max(0, L - guard_samples)
|
| 847 |
+
drop = min(drop, max_allowed_drop)
|
| 848 |
+
drop = (drop // hop) * hop
|
| 849 |
+
|
| 850 |
+
if drop <= 0:
|
| 851 |
+
return
|
| 852 |
+
|
| 853 |
+
# truly drop & update base
|
| 854 |
+
self.buffer = self.buffer[drop:]
|
| 855 |
+
self.left_samples_dropped += drop
|
| 856 |
+
self.base_T += drop // hop
|
| 857 |
+
|
| 858 |
+
def process(self, audio_chunk: np.ndarray, is_last_chunk: bool = False) -> Tuple[torch.Tensor, Dict]:
|
| 859 |
+
self.chunk_count += 1
|
| 860 |
+
# append to buffer
|
| 861 |
+
if len(self.buffer) == 0:
|
| 862 |
+
self.buffer = audio_chunk.astype(np.float32, copy=True)
|
| 863 |
+
else:
|
| 864 |
+
self.buffer = np.concatenate([self.buffer, audio_chunk.astype(np.float32, copy=True)])
|
| 865 |
+
|
| 866 |
+
# sliding window processing
|
| 867 |
+
self._maybe_slide_buffer()
|
| 868 |
+
|
| 869 |
+
# full extraction (for the current window)
|
| 870 |
+
mel_full = self._extract_full()
|
| 871 |
+
T_full = mel_full.shape[-1] # local frames in the current window
|
| 872 |
+
stable_T = min(T_full, self._stable_frames_count()) # local stable frames
|
| 873 |
+
stable_T_global = self.base_T + stable_T # map to global frame coordinates
|
| 874 |
+
|
| 875 |
+
# plan the core frames for the current emission (global coordinates)
|
| 876 |
+
core_start_g = self.last_emitted_T
|
| 877 |
+
core_end_g = core_start_g + self.chunk_frames
|
| 878 |
+
required_stable_g = core_end_g + self.cnn_redundancy_frames
|
| 879 |
+
|
| 880 |
+
if stable_T_global >= required_stable_g or is_last_chunk:
|
| 881 |
+
emit_start_g = max(0, core_start_g - self.cnn_redundancy_frames)
|
| 882 |
+
emit_end_g = core_end_g + self.cnn_redundancy_frames
|
| 883 |
+
|
| 884 |
+
# global -> local index
|
| 885 |
+
emit_start = max(0, emit_start_g - self.base_T)
|
| 886 |
+
emit_end = emit_end_g - self.base_T
|
| 887 |
+
emit_start = max(0, min(emit_start, T_full))
|
| 888 |
+
emit_end = max(emit_start, min(emit_end, T_full))
|
| 889 |
+
|
| 890 |
+
mel_output = mel_full[:, :, emit_start:emit_end]
|
| 891 |
+
self.last_emitted_T = core_end_g # only advance the core frame pointer (global)
|
| 892 |
+
else:
|
| 893 |
+
mel_output = mel_full[:, :, 0:0]
|
| 894 |
+
|
| 895 |
+
self.total_samples_processed += len(audio_chunk)
|
| 896 |
+
self.is_first = False
|
| 897 |
+
|
| 898 |
+
info = {
|
| 899 |
+
"type": "exact_chunk",
|
| 900 |
+
"chunk_number": self.chunk_count,
|
| 901 |
+
"emitted_frames": mel_output.shape[-1],
|
| 902 |
+
"stable_T": stable_T,
|
| 903 |
+
"T_full": T_full,
|
| 904 |
+
"base_T": self.base_T,
|
| 905 |
+
"stable_T_global": stable_T_global,
|
| 906 |
+
"buffer_len_samples": int(self.buffer.shape[0]),
|
| 907 |
+
"left_samples_dropped": self.left_samples_dropped,
|
| 908 |
+
"core_start": core_start_g, # if keep the original field name, use the global value here
|
| 909 |
+
"core_end": core_end_g, # same as above
|
| 910 |
+
}
|
| 911 |
+
return mel_output, info
|
| 912 |
+
|
| 913 |
+
def flush(self) -> torch.Tensor:
|
| 914 |
+
"""Called when the stream ends, output the remaining unemitted frames, ensuring consistency with offline (calculated by global coordinates)."""
|
| 915 |
+
if len(self.buffer) == 0:
|
| 916 |
+
return torch.zeros(1, 80, 0)
|
| 917 |
+
|
| 918 |
+
mel_full = self._extract_full()
|
| 919 |
+
T_local = mel_full.shape[-1]
|
| 920 |
+
T_global = self.base_T + T_local
|
| 921 |
+
|
| 922 |
+
if self.last_emitted_T < T_global:
|
| 923 |
+
start_l = max(0, self.last_emitted_T - self.base_T)
|
| 924 |
+
tail = mel_full[:, :, start_l:]
|
| 925 |
+
self.last_emitted_T = T_global
|
| 926 |
+
return tail
|
| 927 |
+
return mel_full[:, :, 0:0]
|
| 928 |
+
|
| 929 |
+
def get_config(self) -> Dict:
|
| 930 |
+
return {
|
| 931 |
+
"chunk_ms": self.chunk_ms,
|
| 932 |
+
"first_chunk_ms": self.first_chunk_ms,
|
| 933 |
+
"effective_first_chunk_ms": self.first_chunk_samples / self.sample_rate * 1000.0,
|
| 934 |
+
"sample_rate": self.sample_rate,
|
| 935 |
+
"n_fft": self.n_fft,
|
| 936 |
+
"hop_length": self.hop_length,
|
| 937 |
+
"cnn_redundancy_ms": self.cnn_redundancy_ms,
|
| 938 |
+
"cnn_redundancy_frames": self.cnn_redundancy_frames,
|
| 939 |
+
"enable_sliding_window": self.enable_sliding_window,
|
| 940 |
+
"trigger_seconds": self.trigger_seconds,
|
| 941 |
+
"slide_seconds": self.slide_seconds,
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
def get_state(self) -> Dict:
|
| 945 |
+
return {
|
| 946 |
+
"chunk_count": self.chunk_count,
|
| 947 |
+
"last_emitted_T": self.last_emitted_T,
|
| 948 |
+
"total_samples_processed": self.total_samples_processed,
|
| 949 |
+
"buffer_len": int(self.buffer.shape[0]),
|
| 950 |
+
"base_T": self.base_T,
|
| 951 |
+
"left_samples_dropped": self.left_samples_dropped,
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
def get_snapshot(self) -> Dict:
|
| 955 |
+
"""Get a complete state snapshot (including buffer), used for recovery from a fast start.
|
| 956 |
+
|
| 957 |
+
Returns:
|
| 958 |
+
A dictionary containing the complete state, which can be used to restore the snapshot
|
| 959 |
+
"""
|
| 960 |
+
buffer_copy = self.buffer.copy()
|
| 961 |
+
snapshot = {
|
| 962 |
+
"chunk_count": self.chunk_count,
|
| 963 |
+
"last_emitted_T": self.last_emitted_T,
|
| 964 |
+
"total_samples_processed": self.total_samples_processed,
|
| 965 |
+
"buffer": buffer_copy,
|
| 966 |
+
"base_T": self.base_T,
|
| 967 |
+
"left_samples_dropped": self.left_samples_dropped,
|
| 968 |
+
"is_first": self.is_first,
|
| 969 |
+
# save the state of the feature_extractor (key: ensure determinism of mel feature extraction)
|
| 970 |
+
"fe_dynamic_log_norm": getattr(self.feature_extractor, "dynamic_log_norm", None),
|
| 971 |
+
"fe_dynamic_range_db": getattr(self.feature_extractor, "dynamic_range_db", None),
|
| 972 |
+
"fe_log_floor_db": getattr(self.feature_extractor, "log_floor_db", None),
|
| 973 |
+
}
|
| 974 |
+
|
| 975 |
+
return snapshot
|
| 976 |
+
|
| 977 |
+
def restore_snapshot(self, snapshot: Dict) -> None:
|
| 978 |
+
"""Restore state from a snapshot
|
| 979 |
+
|
| 980 |
+
Args:
|
| 981 |
+
snapshot: the snapshot dictionary returned by get_snapshot
|
| 982 |
+
"""
|
| 983 |
+
# record the state before restoration
|
| 984 |
+
prev_state = {
|
| 985 |
+
"chunk_count": self.chunk_count,
|
| 986 |
+
"last_emitted_T": self.last_emitted_T,
|
| 987 |
+
"buffer_len": len(self.buffer),
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
# restore state
|
| 991 |
+
self.chunk_count = snapshot["chunk_count"]
|
| 992 |
+
self.last_emitted_T = snapshot["last_emitted_T"]
|
| 993 |
+
self.total_samples_processed = snapshot["total_samples_processed"]
|
| 994 |
+
self.buffer = snapshot["buffer"].copy() # copy buffer
|
| 995 |
+
self.base_T = snapshot["base_T"]
|
| 996 |
+
self.left_samples_dropped = snapshot["left_samples_dropped"]
|
| 997 |
+
self.is_first = snapshot["is_first"]
|
| 998 |
+
|
| 999 |
+
# restore the state of the feature_extractor (key: ensure determinism of mel feature extraction)
|
| 1000 |
+
if snapshot.get("fe_dynamic_log_norm") is not None:
|
| 1001 |
+
self.feature_extractor.dynamic_log_norm = snapshot["fe_dynamic_log_norm"]
|
| 1002 |
+
if snapshot.get("fe_dynamic_range_db") is not None:
|
| 1003 |
+
self.feature_extractor.dynamic_range_db = snapshot["fe_dynamic_range_db"]
|
| 1004 |
+
if snapshot.get("fe_log_floor_db") is not None:
|
| 1005 |
+
self.feature_extractor.log_floor_db = snapshot["fe_log_floor_db"]
|
| 1006 |
+
|
| 1007 |
+
|
| 1008 |
+
class MiniCPMOProcessor(ProcessorMixin):
|
| 1009 |
+
attributes = ["image_processor", "audio_processor", "tokenizer"]
|
| 1010 |
+
audio_processor_class = "AutoFeatureExtractor"
|
| 1011 |
+
image_processor_class = "AutoImageProcessor"
|
| 1012 |
+
tokenizer_class = "AutoTokenizer"
|
| 1013 |
+
|
| 1014 |
+
def __init__(self, image_processor=None, audio_processor=None, tokenizer=None, **kwargs):
|
| 1015 |
+
super().__init__(image_processor, audio_processor, tokenizer)
|
| 1016 |
+
|
| 1017 |
+
self.version = image_processor.version if image_processor else None
|
| 1018 |
+
# audio feature pooling step, needs to be consistent with config.audio_pool_step
|
| 1019 |
+
self.pool_step = kwargs.get("audio_pool_step", 5)
|
| 1020 |
+
|
| 1021 |
+
# initialize the streaming audio processor
|
| 1022 |
+
self._streaming_mel_processor = None
|
| 1023 |
+
if audio_processor is not None:
|
| 1024 |
+
self._init_streaming_processor()
|
| 1025 |
+
|
| 1026 |
+
def get_audio_placeholder(
|
| 1027 |
+
self,
|
| 1028 |
+
audio_lens: int,
|
| 1029 |
+
chunk_input: bool = True,
|
| 1030 |
+
chunk_length: int = 1,
|
| 1031 |
+
) -> str:
|
| 1032 |
+
"""
|
| 1033 |
+
Public method to get audio placeholder string for vLLM integration.
|
| 1034 |
+
|
| 1035 |
+
Args:
|
| 1036 |
+
audio_lens: Length of audio in samples
|
| 1037 |
+
chunk_input: Whether to use chunked processing
|
| 1038 |
+
chunk_length: Chunk length in seconds
|
| 1039 |
+
|
| 1040 |
+
Returns:
|
| 1041 |
+
Audio placeholder string
|
| 1042 |
+
"""
|
| 1043 |
+
pool_step = self.pool_step
|
| 1044 |
+
feature_lens = math.ceil(audio_lens / self.audio_processor.hop_length)
|
| 1045 |
+
|
| 1046 |
+
feature_lens = (feature_lens - 1) // 2 + 1
|
| 1047 |
+
output_lens = (feature_lens - pool_step) // pool_step + 1
|
| 1048 |
+
|
| 1049 |
+
if chunk_input:
|
| 1050 |
+
fbank_feat_in_chunk = int(chunk_length * 100)
|
| 1051 |
+
cnn_feat_in_chunk = (fbank_feat_in_chunk - 1) // 2 + 1
|
| 1052 |
+
audio_embeds_in_chunk = (cnn_feat_in_chunk - pool_step) // pool_step + 1
|
| 1053 |
+
num_audio_chunks = (output_lens + audio_embeds_in_chunk - 1) // audio_embeds_in_chunk
|
| 1054 |
+
|
| 1055 |
+
place_holders = ""
|
| 1056 |
+
total_unk_len = 0
|
| 1057 |
+
for _ in range(num_audio_chunks):
|
| 1058 |
+
unk_len = min(audio_embeds_in_chunk, output_lens - total_unk_len)
|
| 1059 |
+
place_holders += self.tokenizer.audio_start + "<unk>" * unk_len + self.tokenizer.audio_end
|
| 1060 |
+
total_unk_len += unk_len
|
| 1061 |
+
audio_placeholder = place_holders
|
| 1062 |
+
else:
|
| 1063 |
+
audio_placeholder = self.tokenizer.audio_start + "<unk>" * output_lens + self.tokenizer.audio_end
|
| 1064 |
+
|
| 1065 |
+
return audio_placeholder
|
| 1066 |
+
|
| 1067 |
+
def _init_streaming_processor(
|
| 1068 |
+
self,
|
| 1069 |
+
chunk_ms: int = 100,
|
| 1070 |
+
cnn_redundancy_ms: int = 0,
|
| 1071 |
+
*,
|
| 1072 |
+
mode: str = "exact",
|
| 1073 |
+
first_chunk_ms: Optional[int] = None,
|
| 1074 |
+
enable_sliding_window: bool = False,
|
| 1075 |
+
slide_trigger_seconds: float = 30.0,
|
| 1076 |
+
slide_stride_seconds: float = 10.0,
|
| 1077 |
+
):
|
| 1078 |
+
"""Initialize the streaming processor
|
| 1079 |
+
|
| 1080 |
+
Args:
|
| 1081 |
+
chunk_ms: Chunk size in milliseconds, also the sliding step.
|
| 1082 |
+
cnn_redundancy_ms: CNN boundary redundancy in milliseconds (before and after), 0 means standard mode.
|
| 1083 |
+
mode: streaming processing mode, currently only supports "exact"
|
| 1084 |
+
first_chunk_ms: the size of the first chunk (milliseconds), if not specified, it is the same as chunk_ms
|
| 1085 |
+
enable_sliding_window: whether to enable sliding window (trigger mode)
|
| 1086 |
+
slide_trigger_seconds: trigger threshold for sliding window in seconds
|
| 1087 |
+
slide_stride_seconds: stride for sliding window in seconds
|
| 1088 |
+
"""
|
| 1089 |
+
if mode == "exact":
|
| 1090 |
+
self._streaming_mel_processor = StreamingMelProcessorExact(
|
| 1091 |
+
feature_extractor=self.audio_processor,
|
| 1092 |
+
chunk_ms=chunk_ms,
|
| 1093 |
+
first_chunk_ms=first_chunk_ms,
|
| 1094 |
+
sample_rate=16000,
|
| 1095 |
+
cnn_redundancy_ms=cnn_redundancy_ms,
|
| 1096 |
+
enable_sliding_window=enable_sliding_window,
|
| 1097 |
+
slide_trigger_seconds=slide_trigger_seconds,
|
| 1098 |
+
slide_stride_seconds=slide_stride_seconds,
|
| 1099 |
+
)
|
| 1100 |
+
else:
|
| 1101 |
+
raise ValueError(f"Unsupported mode: {mode}, only 'exact' is supported")
|
| 1102 |
+
self._streaming_mode = mode if mode in ["exact"] else ("exact")
|
| 1103 |
+
|
| 1104 |
+
def set_streaming_mode(
|
| 1105 |
+
self,
|
| 1106 |
+
mode: str = "exact",
|
| 1107 |
+
chunk_ms: int = 100,
|
| 1108 |
+
cnn_redundancy_ms: int = 0,
|
| 1109 |
+
*,
|
| 1110 |
+
first_chunk_ms: Optional[int] = None,
|
| 1111 |
+
enable_sliding_window: bool = False,
|
| 1112 |
+
slide_trigger_seconds: float = 30.0,
|
| 1113 |
+
slide_stride_seconds: float = 10.0,
|
| 1114 |
+
):
|
| 1115 |
+
"""Set streaming processing mode
|
| 1116 |
+
|
| 1117 |
+
Args:
|
| 1118 |
+
mode: streaming processing mode, currently only supports "exact"
|
| 1119 |
+
chunk_ms: chunk size in milliseconds, also the sliding step.
|
| 1120 |
+
cnn_redundancy_ms: CNN boundary redundancy in milliseconds (before and after), 0 means standard mode.
|
| 1121 |
+
first_chunk_ms: the size of the first chunk (milliseconds), if not specified, it is the same as chunk_ms
|
| 1122 |
+
enable_sliding_window: whether to enable sliding window (trigger mode)
|
| 1123 |
+
slide_trigger_seconds: trigger threshold for sliding window in seconds
|
| 1124 |
+
slide_stride_seconds: stride for sliding window in seconds
|
| 1125 |
+
"""
|
| 1126 |
+
if self.audio_processor is None:
|
| 1127 |
+
raise ValueError("audio_processor is not set, cannot initialize the streaming processor")
|
| 1128 |
+
self._init_streaming_processor(
|
| 1129 |
+
chunk_ms=chunk_ms,
|
| 1130 |
+
cnn_redundancy_ms=cnn_redundancy_ms,
|
| 1131 |
+
mode=mode,
|
| 1132 |
+
first_chunk_ms=first_chunk_ms,
|
| 1133 |
+
enable_sliding_window=enable_sliding_window,
|
| 1134 |
+
slide_trigger_seconds=slide_trigger_seconds,
|
| 1135 |
+
slide_stride_seconds=slide_stride_seconds,
|
| 1136 |
+
)
|
| 1137 |
+
|
| 1138 |
+
def process_image(
|
| 1139 |
+
self,
|
| 1140 |
+
images: Optional[ImageInput] = None,
|
| 1141 |
+
do_pad: bool = True,
|
| 1142 |
+
max_slice_nums: int = 1,
|
| 1143 |
+
return_tensors: str = "pt",
|
| 1144 |
+
) -> MiniCPMOBatchFeature:
|
| 1145 |
+
"""Process image data
|
| 1146 |
+
|
| 1147 |
+
Args:
|
| 1148 |
+
images: input images
|
| 1149 |
+
do_pad: whether to pad
|
| 1150 |
+
max_slice_nums: maximum number of slices
|
| 1151 |
+
return_tensors: return tensor type
|
| 1152 |
+
Returns:
|
| 1153 |
+
MiniCPMOBatchFeature object
|
| 1154 |
+
"""
|
| 1155 |
+
if images is None:
|
| 1156 |
+
return MiniCPMOBatchFeature(data={"pixel_values": [[]], "image_sizes": [[]], "tgt_sizes": [[]]})
|
| 1157 |
+
|
| 1158 |
+
result = self.image_processor(
|
| 1159 |
+
images, do_pad=do_pad, max_slice_nums=max_slice_nums, return_tensors=return_tensors
|
| 1160 |
+
)
|
| 1161 |
+
|
| 1162 |
+
model_inputs = {
|
| 1163 |
+
"pixel_values": result.get("pixel_values", [[]]),
|
| 1164 |
+
"image_sizes": result.get("image_sizes", [[]]),
|
| 1165 |
+
"tgt_sizes": result.get("tgt_sizes", [[]]),
|
| 1166 |
+
}
|
| 1167 |
+
|
| 1168 |
+
return MiniCPMOBatchFeature(data=model_inputs)
|
| 1169 |
+
|
| 1170 |
+
def process_audio(
|
| 1171 |
+
self,
|
| 1172 |
+
audios: Optional[Union[np.ndarray, List[np.ndarray]]] = None,
|
| 1173 |
+
sampling_rate: int = 16000,
|
| 1174 |
+
regroup_to_seconds: Optional[int] = None,
|
| 1175 |
+
fps: int = 100,
|
| 1176 |
+
) -> MiniCPMOBatchFeature:
|
| 1177 |
+
"""Process audio data in batch
|
| 1178 |
+
|
| 1179 |
+
Args:
|
| 1180 |
+
audios: audio data
|
| 1181 |
+
sampling_rate: sampling rate
|
| 1182 |
+
regroup_to_seconds: regroup duration in seconds
|
| 1183 |
+
fps: frames per second
|
| 1184 |
+
Returns:
|
| 1185 |
+
MiniCPMOBatchFeature object
|
| 1186 |
+
"""
|
| 1187 |
+
if audios is None:
|
| 1188 |
+
return MiniCPMOBatchFeature(data={"audio_features": [], "audio_feature_lens": []})
|
| 1189 |
+
|
| 1190 |
+
audio_features, audio_feature_lens = process_audio_batch(
|
| 1191 |
+
audios=audios,
|
| 1192 |
+
feature_extractor=self.audio_processor,
|
| 1193 |
+
sampling_rate=sampling_rate,
|
| 1194 |
+
max_duration_seconds=30,
|
| 1195 |
+
return_attention_mask=True,
|
| 1196 |
+
)
|
| 1197 |
+
|
| 1198 |
+
if regroup_to_seconds is not None and len(audio_features) > 0:
|
| 1199 |
+
audio_features, audio_feature_lens = regroup_audio_features(
|
| 1200 |
+
audio_features=audio_features,
|
| 1201 |
+
audio_feature_lens=audio_feature_lens,
|
| 1202 |
+
regroup_seconds=regroup_to_seconds,
|
| 1203 |
+
fps=fps,
|
| 1204 |
+
)
|
| 1205 |
+
|
| 1206 |
+
model_inputs = {"audio_features": audio_features, "audio_feature_lens": audio_feature_lens}
|
| 1207 |
+
|
| 1208 |
+
return MiniCPMOBatchFeature(data=model_inputs)
|
| 1209 |
+
|
| 1210 |
+
def process_audio_streaming(
|
| 1211 |
+
self,
|
| 1212 |
+
audio_chunk: np.ndarray,
|
| 1213 |
+
reset: bool = False,
|
| 1214 |
+
return_batch_feature: bool = False,
|
| 1215 |
+
is_last_chunk: bool = False,
|
| 1216 |
+
) -> Union[Tuple[torch.Tensor, dict], MiniCPMOBatchFeature]:
|
| 1217 |
+
"""Process audio chunk in streaming
|
| 1218 |
+
|
| 1219 |
+
Args:
|
| 1220 |
+
audio_chunk: audio data chunk (any audio, e.g. first process 125ms, then process 100ms)
|
| 1221 |
+
reset: whether to reset the processor state
|
| 1222 |
+
return_batch_feature: whether to return MiniCPMOBatchFeature format (consistent with process_audio)
|
| 1223 |
+
Returns:
|
| 1224 |
+
If return_batch_feature=False:
|
| 1225 |
+
(audio_features, info)
|
| 1226 |
+
- audio_features: [1, 80, n_frames] mel features
|
| 1227 |
+
- info: processing information dictionary
|
| 1228 |
+
If return_batch_feature=True:
|
| 1229 |
+
MiniCPMOBatchFeature object, containing:
|
| 1230 |
+
- audio_features: [1, 80, n_frames] mel features
|
| 1231 |
+
- audio_feature_lens: [tensor([n_frames])]
|
| 1232 |
+
- info: processing information (as an extra attribute)
|
| 1233 |
+
"""
|
| 1234 |
+
if self._streaming_mel_processor is None:
|
| 1235 |
+
raise ValueError("Streaming processor not initialized, please ensure audio_processor is set")
|
| 1236 |
+
|
| 1237 |
+
if reset:
|
| 1238 |
+
self._streaming_mel_processor.reset()
|
| 1239 |
+
|
| 1240 |
+
# process chunk
|
| 1241 |
+
mel_features, info = self._streaming_mel_processor.process(audio_chunk, is_last_chunk=is_last_chunk)
|
| 1242 |
+
|
| 1243 |
+
# determine the return format based on the parameters
|
| 1244 |
+
if return_batch_feature:
|
| 1245 |
+
# return the format consistent with process_audio
|
| 1246 |
+
# note: info returns emitted_frames, which represents the actual output frames
|
| 1247 |
+
n_frames = info.get("emitted_frames", mel_features.shape[-1])
|
| 1248 |
+
model_inputs = {
|
| 1249 |
+
"audio_features": mel_features,
|
| 1250 |
+
"audio_feature_lens": [torch.tensor([n_frames])],
|
| 1251 |
+
"streaming_info": info, # add streaming processing information
|
| 1252 |
+
}
|
| 1253 |
+
return MiniCPMOBatchFeature(data=model_inputs)
|
| 1254 |
+
else:
|
| 1255 |
+
return mel_features, info
|
| 1256 |
+
|
| 1257 |
+
def reset_streaming(self):
|
| 1258 |
+
if self._streaming_mel_processor is not None:
|
| 1259 |
+
self._streaming_mel_processor.reset()
|
| 1260 |
+
|
| 1261 |
+
def get_streaming_chunk_size(self) -> int:
|
| 1262 |
+
if self._streaming_mel_processor is None:
|
| 1263 |
+
raise ValueError("Streaming processor not initialized")
|
| 1264 |
+
return self._streaming_mel_processor.get_chunk_size()
|
| 1265 |
+
|
| 1266 |
+
def configure_streaming(
|
| 1267 |
+
self,
|
| 1268 |
+
chunk_ms: int = 100,
|
| 1269 |
+
enable_sliding_window: bool = False,
|
| 1270 |
+
slide_trigger_seconds: float = 30.0,
|
| 1271 |
+
slide_stride_seconds: float = 10.0,
|
| 1272 |
+
):
|
| 1273 |
+
"""Configure streaming processor parameters
|
| 1274 |
+
|
| 1275 |
+
Args:
|
| 1276 |
+
chunk_ms: chunk size in milliseconds
|
| 1277 |
+
enable_sliding_window: whether to enable sliding window (trigger mode)
|
| 1278 |
+
slide_trigger_seconds: trigger threshold for sliding window in seconds
|
| 1279 |
+
slide_stride_seconds: stride for sliding window in seconds
|
| 1280 |
+
"""
|
| 1281 |
+
if self.audio_processor is None:
|
| 1282 |
+
raise ValueError("audio_processor is not set")
|
| 1283 |
+
|
| 1284 |
+
self._init_streaming_processor(
|
| 1285 |
+
chunk_ms=chunk_ms,
|
| 1286 |
+
enable_sliding_window=enable_sliding_window,
|
| 1287 |
+
slide_trigger_seconds=slide_trigger_seconds,
|
| 1288 |
+
slide_stride_seconds=slide_stride_seconds,
|
| 1289 |
+
)
|
| 1290 |
+
|
| 1291 |
+
def get_streaming_config(self) -> dict:
|
| 1292 |
+
if self._streaming_mel_processor is None:
|
| 1293 |
+
return {}
|
| 1294 |
+
return self._streaming_mel_processor.get_config()
|
| 1295 |
+
|
| 1296 |
+
def get_streaming_state(self) -> dict:
|
| 1297 |
+
if self._streaming_mel_processor is None:
|
| 1298 |
+
return {}
|
| 1299 |
+
return self._streaming_mel_processor.get_state()
|
| 1300 |
+
|
| 1301 |
+
def get_streaming_snapshot(self) -> dict:
|
| 1302 |
+
if self._streaming_mel_processor is None:
|
| 1303 |
+
return {}
|
| 1304 |
+
return self._streaming_mel_processor.get_snapshot()
|
| 1305 |
+
|
| 1306 |
+
def restore_streaming_snapshot(self, snapshot: dict) -> None:
|
| 1307 |
+
if self._streaming_mel_processor is None:
|
| 1308 |
+
return
|
| 1309 |
+
if not snapshot:
|
| 1310 |
+
return
|
| 1311 |
+
self._streaming_mel_processor.restore_snapshot(snapshot)
|
| 1312 |
+
|
| 1313 |
+
def __call__(
|
| 1314 |
+
self,
|
| 1315 |
+
text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]],
|
| 1316 |
+
images: ImageInput = None,
|
| 1317 |
+
audios: Union[np.ndarray, List[np.ndarray], List[List[np.ndarray]]] = None,
|
| 1318 |
+
audio_parts: Optional[list] = None,
|
| 1319 |
+
max_length: Optional[int] = None,
|
| 1320 |
+
do_pad: Optional[bool] = True,
|
| 1321 |
+
max_slice_nums: int = None,
|
| 1322 |
+
use_image_id: bool = True,
|
| 1323 |
+
stream_input: bool = False,
|
| 1324 |
+
return_tensors: Optional[Union[str, TensorType]] = TensorType.PYTORCH,
|
| 1325 |
+
sampling_rate: Optional[int] = 16000,
|
| 1326 |
+
online_streaming: bool = False,
|
| 1327 |
+
audio_chunk_idx: int = 0,
|
| 1328 |
+
is_last_chunk: bool = False,
|
| 1329 |
+
**kwargs,
|
| 1330 |
+
) -> MiniCPMOBatchFeature:
|
| 1331 |
+
if images is not None:
|
| 1332 |
+
image_inputs = self.process_image(
|
| 1333 |
+
images=images, do_pad=do_pad, max_slice_nums=max_slice_nums, return_tensors=return_tensors
|
| 1334 |
+
)
|
| 1335 |
+
else:
|
| 1336 |
+
image_inputs = None
|
| 1337 |
+
|
| 1338 |
+
audio_features, audio_feature_lens, audio_phs = self.audio_feature_extract(
|
| 1339 |
+
audios,
|
| 1340 |
+
audio_parts,
|
| 1341 |
+
stream_input,
|
| 1342 |
+
sampling_rate,
|
| 1343 |
+
online_streaming=online_streaming,
|
| 1344 |
+
is_last_chunk=is_last_chunk,
|
| 1345 |
+
)
|
| 1346 |
+
|
| 1347 |
+
model_inputs = self._convert_omni_to_inputs(
|
| 1348 |
+
image_inputs,
|
| 1349 |
+
audio_phs,
|
| 1350 |
+
text,
|
| 1351 |
+
max_slice_nums=max_slice_nums,
|
| 1352 |
+
use_image_id=use_image_id,
|
| 1353 |
+
max_length=max_length,
|
| 1354 |
+
**kwargs,
|
| 1355 |
+
)
|
| 1356 |
+
|
| 1357 |
+
model_inputs["audio_features"] = audio_features
|
| 1358 |
+
model_inputs["audio_feature_lens"] = audio_feature_lens
|
| 1359 |
+
|
| 1360 |
+
result = MiniCPMOBatchFeature(data={**model_inputs})
|
| 1361 |
+
|
| 1362 |
+
if online_streaming:
|
| 1363 |
+
result.use_extra_context = True
|
| 1364 |
+
result.prefix_extra_frames = 0 if audio_chunk_idx == 0 else 2
|
| 1365 |
+
result.suffix_extra_frames = 2
|
| 1366 |
+
result.chunk_idx = audio_chunk_idx
|
| 1367 |
+
|
| 1368 |
+
return result
|
| 1369 |
+
|
| 1370 |
+
def audio_feature_extract(
|
| 1371 |
+
self,
|
| 1372 |
+
audios: Union[np.ndarray, List[np.ndarray], List[List[np.ndarray]], None] = None,
|
| 1373 |
+
audio_parts: Optional[list] = None,
|
| 1374 |
+
stream_input: Optional[bool] = False,
|
| 1375 |
+
sampling_rate: Optional[int] = None,
|
| 1376 |
+
chunk_length: Optional[int] = 1,
|
| 1377 |
+
online_streaming: bool = False,
|
| 1378 |
+
is_last_chunk: bool = False,
|
| 1379 |
+
**kwargs,
|
| 1380 |
+
):
|
| 1381 |
+
if audios is None:
|
| 1382 |
+
return [], [], []
|
| 1383 |
+
|
| 1384 |
+
if isinstance(audios, np.ndarray):
|
| 1385 |
+
audios_list = [[audios]]
|
| 1386 |
+
elif isinstance(audios[0], np.ndarray):
|
| 1387 |
+
audios_list = [audios]
|
| 1388 |
+
else:
|
| 1389 |
+
audios_list = audios
|
| 1390 |
+
|
| 1391 |
+
if audio_parts is not None:
|
| 1392 |
+
assert len(audio_parts) == len(audios_list)
|
| 1393 |
+
for parts, audios in zip(audio_parts, audios_list):
|
| 1394 |
+
assert len(parts) == len(audios)
|
| 1395 |
+
|
| 1396 |
+
audio_feature_lens_list = []
|
| 1397 |
+
audio_ph_list = []
|
| 1398 |
+
audio_features_all = []
|
| 1399 |
+
|
| 1400 |
+
# audio placeholder not dependent on audio_parts
|
| 1401 |
+
for audios in audios_list:
|
| 1402 |
+
if audios:
|
| 1403 |
+
audio_ph_list.append(
|
| 1404 |
+
[
|
| 1405 |
+
self.get_audio_placeholder(len(a), chunk_input=stream_input, chunk_length=chunk_length)
|
| 1406 |
+
for a in audios
|
| 1407 |
+
]
|
| 1408 |
+
)
|
| 1409 |
+
else:
|
| 1410 |
+
audio_ph_list.append([])
|
| 1411 |
+
|
| 1412 |
+
for idx, audios in enumerate(audios_list):
|
| 1413 |
+
if audio_parts is not None:
|
| 1414 |
+
# same audio part merge
|
| 1415 |
+
audio_part = audio_parts[idx]
|
| 1416 |
+
merge_audio = []
|
| 1417 |
+
cur_audio = []
|
| 1418 |
+
for aid, (part, audio) in enumerate(zip(audio_part, audios)):
|
| 1419 |
+
if aid == 0 or audio_part[aid] == audio_part[aid - 1]:
|
| 1420 |
+
cur_audio.append(audio)
|
| 1421 |
+
else:
|
| 1422 |
+
merge_audio.append(np.hstack(cur_audio))
|
| 1423 |
+
cur_audio = [audio]
|
| 1424 |
+
if cur_audio:
|
| 1425 |
+
merge_audio.append(np.hstack(cur_audio))
|
| 1426 |
+
else:
|
| 1427 |
+
merge_audio = audios
|
| 1428 |
+
|
| 1429 |
+
# If the audio exceeds 30 seconds, split it into chunks every 30 seconds.
|
| 1430 |
+
final_merge_audio = []
|
| 1431 |
+
max_audio_inp_len = 30 * sampling_rate
|
| 1432 |
+
for audio in merge_audio:
|
| 1433 |
+
if len(audio) <= max_audio_inp_len:
|
| 1434 |
+
final_merge_audio.append(audio)
|
| 1435 |
+
else:
|
| 1436 |
+
for i in range(math.ceil(len(audio) / max_audio_inp_len)):
|
| 1437 |
+
final_merge_audio.append(audio[i * max_audio_inp_len : (i + 1) * max_audio_inp_len])
|
| 1438 |
+
|
| 1439 |
+
audio_feature_lens = []
|
| 1440 |
+
|
| 1441 |
+
if audios:
|
| 1442 |
+
if online_streaming:
|
| 1443 |
+
# online streaming: only support single audio, directly use process_audio_streaming return format
|
| 1444 |
+
assert (
|
| 1445 |
+
len(final_merge_audio) == 1
|
| 1446 |
+
), f"online streaming mode only supports single audio, currently there are {len(final_merge_audio)}"
|
| 1447 |
+
audio = final_merge_audio[0]
|
| 1448 |
+
result = self.process_audio_streaming(
|
| 1449 |
+
audio, reset=False, return_batch_feature=True, is_last_chunk=is_last_chunk
|
| 1450 |
+
)
|
| 1451 |
+
audio_features_all.append(
|
| 1452 |
+
result["audio_features"].squeeze(0)
|
| 1453 |
+
) # [1, 80, T] -> [80, T], keep consistent with batch processing
|
| 1454 |
+
audio_feature_lens_list.append(result["audio_feature_lens"][0])
|
| 1455 |
+
else:
|
| 1456 |
+
# batch processing
|
| 1457 |
+
audio_inputs = self.audio_processor(
|
| 1458 |
+
final_merge_audio,
|
| 1459 |
+
sampling_rate=sampling_rate,
|
| 1460 |
+
return_attention_mask=True,
|
| 1461 |
+
padding="max_length",
|
| 1462 |
+
return_tensors="pt",
|
| 1463 |
+
**kwargs,
|
| 1464 |
+
)
|
| 1465 |
+
audio_feature = audio_inputs["input_features"]
|
| 1466 |
+
actual_lens = audio_inputs["attention_mask"].sum(dim=1)
|
| 1467 |
+
|
| 1468 |
+
for feat, lens in zip(audio_feature, actual_lens):
|
| 1469 |
+
audio_features_all.append(feat[:, :lens])
|
| 1470 |
+
audio_feature_lens.append(lens)
|
| 1471 |
+
|
| 1472 |
+
audio_feature_lens = torch.hstack(audio_feature_lens)
|
| 1473 |
+
audio_feature_lens_list.append(audio_feature_lens)
|
| 1474 |
+
else:
|
| 1475 |
+
audio_feature_lens_list.append([])
|
| 1476 |
+
|
| 1477 |
+
if audio_features_all:
|
| 1478 |
+
audio_features = [i.permute(1, 0) for i in audio_features_all]
|
| 1479 |
+
audio_features = torch.nn.utils.rnn.pad_sequence(
|
| 1480 |
+
audio_features, batch_first=True, padding_value=0.0
|
| 1481 |
+
).permute(0, 2, 1)
|
| 1482 |
+
else:
|
| 1483 |
+
audio_features = []
|
| 1484 |
+
|
| 1485 |
+
return audio_features, audio_feature_lens_list, audio_ph_list
|
| 1486 |
+
|
| 1487 |
+
def _convert(self, input_str, max_inp_length: Optional[int] = None):
|
| 1488 |
+
old_input_ids = self.tokenizer.encode(input_str)
|
| 1489 |
+
|
| 1490 |
+
listen_token_id = self.tokenizer.convert_tokens_to_ids("<|listen|>")
|
| 1491 |
+
input_ids = []
|
| 1492 |
+
for token in old_input_ids:
|
| 1493 |
+
if token != listen_token_id:
|
| 1494 |
+
input_ids.append(token)
|
| 1495 |
+
|
| 1496 |
+
if max_inp_length is not None:
|
| 1497 |
+
input_ids = input_ids[:max_inp_length]
|
| 1498 |
+
input_ids = torch.tensor(input_ids, dtype=torch.int32)
|
| 1499 |
+
|
| 1500 |
+
## image bound
|
| 1501 |
+
start_cond = (input_ids == self.tokenizer.im_start_id) | (input_ids == self.tokenizer.slice_start_id)
|
| 1502 |
+
end_cond = (input_ids == self.tokenizer.im_end_id) | (input_ids == self.tokenizer.slice_end_id)
|
| 1503 |
+
|
| 1504 |
+
image_start_idx = torch.where(start_cond)[0]
|
| 1505 |
+
image_start_idx += 1
|
| 1506 |
+
image_end_idx = torch.where(end_cond)[0]
|
| 1507 |
+
|
| 1508 |
+
valid_image_nums = max(len(image_start_idx), len(image_end_idx))
|
| 1509 |
+
|
| 1510 |
+
image_bounds = torch.hstack(
|
| 1511 |
+
[
|
| 1512 |
+
image_start_idx[:valid_image_nums].unsqueeze(-1),
|
| 1513 |
+
image_end_idx[:valid_image_nums].unsqueeze(-1),
|
| 1514 |
+
]
|
| 1515 |
+
)
|
| 1516 |
+
|
| 1517 |
+
## audio bound
|
| 1518 |
+
audio_start_idx = torch.where(input_ids == self.tokenizer.audio_start_id)[0]
|
| 1519 |
+
audio_end_idx = torch.where(input_ids == self.tokenizer.audio_end_id)[0]
|
| 1520 |
+
assert len(audio_start_idx) == len(audio_end_idx)
|
| 1521 |
+
audio_bounds = torch.hstack([(audio_start_idx + 1).unsqueeze(-1), audio_end_idx.unsqueeze(-1)])
|
| 1522 |
+
|
| 1523 |
+
spk_start_idx = torch.where(input_ids == self.tokenizer.spk_start_id)[0]
|
| 1524 |
+
spk_end_idx = torch.where(input_ids == self.tokenizer.spk_end_id)[0]
|
| 1525 |
+
assert len(spk_start_idx) == len(spk_end_idx)
|
| 1526 |
+
spk_bounds = torch.hstack([(spk_start_idx + 1).unsqueeze(-1), spk_end_idx.unsqueeze(-1)])
|
| 1527 |
+
|
| 1528 |
+
return input_ids, image_bounds, audio_bounds, spk_bounds
|
| 1529 |
+
|
| 1530 |
+
def _convert_omni_to_inputs(
|
| 1531 |
+
self,
|
| 1532 |
+
images,
|
| 1533 |
+
audio_phs,
|
| 1534 |
+
texts: Union[str, List[str]],
|
| 1535 |
+
truncation=None,
|
| 1536 |
+
max_length=None,
|
| 1537 |
+
max_slice_nums=None,
|
| 1538 |
+
use_image_id=None,
|
| 1539 |
+
return_tensors=None,
|
| 1540 |
+
**kwargs,
|
| 1541 |
+
):
|
| 1542 |
+
if images is None and audio_phs is None:
|
| 1543 |
+
model_inputs = self.tokenizer(
|
| 1544 |
+
texts, return_tensors=return_tensors, truncation=truncation, max_length=max_length, **kwargs
|
| 1545 |
+
)
|
| 1546 |
+
return MiniCPMOBatchFeature(data={**model_inputs})
|
| 1547 |
+
|
| 1548 |
+
image_pattern = "<image>./</image>"
|
| 1549 |
+
audio_pattern = "<audio>./</audio>"
|
| 1550 |
+
split_pattern = f"({image_pattern}|{audio_pattern})"
|
| 1551 |
+
|
| 1552 |
+
if isinstance(texts, str):
|
| 1553 |
+
texts = [texts]
|
| 1554 |
+
|
| 1555 |
+
bs = len(texts)
|
| 1556 |
+
if images is not None:
|
| 1557 |
+
images, image_sizes, tgt_sizes = images["pixel_values"], images["image_sizes"], images["tgt_sizes"]
|
| 1558 |
+
else:
|
| 1559 |
+
images, image_sizes, tgt_sizes = [[]] * bs, [[]] * bs, [[]] * bs
|
| 1560 |
+
|
| 1561 |
+
input_ids_list = []
|
| 1562 |
+
image_bounds_list = []
|
| 1563 |
+
audio_bounds_list = []
|
| 1564 |
+
spk_bounds_list = []
|
| 1565 |
+
|
| 1566 |
+
for index, text in enumerate(texts):
|
| 1567 |
+
text_chunks = re.split(split_pattern, text)
|
| 1568 |
+
|
| 1569 |
+
image_tags = re.findall(image_pattern, text)
|
| 1570 |
+
audio_tags = re.findall(audio_pattern, text)
|
| 1571 |
+
|
| 1572 |
+
if image_tags:
|
| 1573 |
+
assert images is not None
|
| 1574 |
+
assert len(image_tags) == len(image_sizes[index])
|
| 1575 |
+
if audio_tags:
|
| 1576 |
+
assert audio_phs is not None
|
| 1577 |
+
assert len(audio_tags) == len(audio_phs[index])
|
| 1578 |
+
|
| 1579 |
+
image_id = 0
|
| 1580 |
+
audio_id = 0
|
| 1581 |
+
for i, chunk in enumerate(text_chunks):
|
| 1582 |
+
if chunk == image_pattern:
|
| 1583 |
+
image_placeholder = self.image_processor.get_slice_image_placeholder(
|
| 1584 |
+
image_sizes[index][image_id], image_id, max_slice_nums, use_image_id
|
| 1585 |
+
)
|
| 1586 |
+
image_id += 1
|
| 1587 |
+
text_chunks[i] = image_placeholder
|
| 1588 |
+
elif chunk == audio_pattern:
|
| 1589 |
+
audio_placeholder = audio_phs[index][audio_id]
|
| 1590 |
+
audio_id += 1
|
| 1591 |
+
text_chunks[i] = audio_placeholder
|
| 1592 |
+
|
| 1593 |
+
final_text = "".join(text_chunks)
|
| 1594 |
+
input_ids, image_bounds, audio_bounds, spk_bounds = self._convert(final_text, max_length)
|
| 1595 |
+
|
| 1596 |
+
input_ids_list.append(input_ids)
|
| 1597 |
+
image_bounds_list.append(image_bounds)
|
| 1598 |
+
audio_bounds_list.append(audio_bounds)
|
| 1599 |
+
spk_bounds_list.append(spk_bounds)
|
| 1600 |
+
|
| 1601 |
+
padded_input_ids, padding_lengths = self.pad(input_ids_list, padding_side="left")
|
| 1602 |
+
attention_mask = torch.ones_like(padded_input_ids, dtype=torch.bool)
|
| 1603 |
+
for i, length in enumerate(padding_lengths):
|
| 1604 |
+
image_bounds_list[i] = image_bounds_list[i] + length
|
| 1605 |
+
audio_bounds_list[i] = audio_bounds_list[i] + length
|
| 1606 |
+
spk_bounds_list[i] = spk_bounds_list[i] + length
|
| 1607 |
+
attention_mask[i, :length] = False
|
| 1608 |
+
|
| 1609 |
+
data = {
|
| 1610 |
+
"input_ids": padded_input_ids,
|
| 1611 |
+
"attention_mask": attention_mask,
|
| 1612 |
+
"pixel_values": images,
|
| 1613 |
+
"image_sizes": image_sizes,
|
| 1614 |
+
"image_bound": image_bounds_list,
|
| 1615 |
+
"tgt_sizes": tgt_sizes,
|
| 1616 |
+
"audio_bounds": audio_bounds_list,
|
| 1617 |
+
"spk_bounds": spk_bounds_list,
|
| 1618 |
+
}
|
| 1619 |
+
|
| 1620 |
+
return data
|
| 1621 |
+
|
| 1622 |
+
def pad(self, inputs, max_length=None, padding_value=0, padding_side="left"):
|
| 1623 |
+
items = []
|
| 1624 |
+
if isinstance(inputs[0], list):
|
| 1625 |
+
assert isinstance(inputs[0][0], torch.Tensor)
|
| 1626 |
+
for it in inputs:
|
| 1627 |
+
for tr in it:
|
| 1628 |
+
items.append(tr)
|
| 1629 |
+
else:
|
| 1630 |
+
assert isinstance(inputs[0], torch.Tensor)
|
| 1631 |
+
items = inputs
|
| 1632 |
+
|
| 1633 |
+
batch_size = len(items)
|
| 1634 |
+
shape = items[0].shape
|
| 1635 |
+
dim = len(shape)
|
| 1636 |
+
assert dim <= 2
|
| 1637 |
+
if max_length is None:
|
| 1638 |
+
max_length = 0
|
| 1639 |
+
max_length = max(max_length, max(item.shape[-1] for item in items))
|
| 1640 |
+
min_length = min(item.shape[-1] for item in items)
|
| 1641 |
+
dtype = items[0].dtype
|
| 1642 |
+
|
| 1643 |
+
if dim == 0:
|
| 1644 |
+
return torch.stack([item for item in items], dim=0), [0]
|
| 1645 |
+
elif dim == 1:
|
| 1646 |
+
if max_length == min_length:
|
| 1647 |
+
return torch.stack([item for item in items], dim=0), [0] * batch_size
|
| 1648 |
+
tensor = torch.zeros((batch_size, max_length), dtype=dtype) + padding_value
|
| 1649 |
+
else:
|
| 1650 |
+
tensor = torch.zeros((batch_size, max_length, shape[-1]), dtype=dtype) + padding_value
|
| 1651 |
+
|
| 1652 |
+
padding_length = []
|
| 1653 |
+
for i, item in enumerate(items):
|
| 1654 |
+
if dim == 1:
|
| 1655 |
+
if padding_side == "left":
|
| 1656 |
+
tensor[i, -len(item) :] = item.clone()
|
| 1657 |
+
else:
|
| 1658 |
+
tensor[i, : len(item)] = item.clone()
|
| 1659 |
+
elif dim == 2:
|
| 1660 |
+
if padding_side == "left":
|
| 1661 |
+
tensor[i, -len(item) :, :] = item.clone()
|
| 1662 |
+
else:
|
| 1663 |
+
tensor[i, : len(item), :] = item.clone()
|
| 1664 |
+
padding_length.append(tensor.shape[-1] - len(item))
|
| 1665 |
+
|
| 1666 |
+
return tensor, padding_length
|
Realtime-Venus-Audio/processor_config.json
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"auto_map": {
|
| 3 |
+
"AutoProcessor": "processing_minicpmo.MiniCPMOProcessor"
|
| 4 |
+
},
|
| 5 |
+
"processor_class": "MiniCPMOProcessor"
|
| 6 |
+
}
|
Realtime-Venus-Audio/special_tokens_map.json
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<unk>",
|
| 4 |
+
"<image>",
|
| 5 |
+
"</image>",
|
| 6 |
+
"<ref>",
|
| 7 |
+
"</ref>",
|
| 8 |
+
"<box>",
|
| 9 |
+
"</box>",
|
| 10 |
+
"<quad>",
|
| 11 |
+
"</quad>",
|
| 12 |
+
"<point>",
|
| 13 |
+
"</point>",
|
| 14 |
+
"<slice>",
|
| 15 |
+
"</slice>",
|
| 16 |
+
"<image_id>",
|
| 17 |
+
"</image_id>",
|
| 18 |
+
"<unit>",
|
| 19 |
+
"</unit>",
|
| 20 |
+
"<answer>",
|
| 21 |
+
"</answer>",
|
| 22 |
+
"<focus>",
|
| 23 |
+
"</focus>",
|
| 24 |
+
"<line>",
|
| 25 |
+
"</line>",
|
| 26 |
+
"<perception>",
|
| 27 |
+
"</perception>",
|
| 28 |
+
"<source_image>",
|
| 29 |
+
"</source_image>",
|
| 30 |
+
"<image_save_to>",
|
| 31 |
+
"</image_save_to>",
|
| 32 |
+
"<|audio_start|>",
|
| 33 |
+
"<|audio|>",
|
| 34 |
+
"<|audio_end|>",
|
| 35 |
+
"<|spk_bos|>",
|
| 36 |
+
"<|spk|>",
|
| 37 |
+
"<|spk_eos|>",
|
| 38 |
+
"<|tts_bos|>",
|
| 39 |
+
"<|tts_eos|>",
|
| 40 |
+
"<|listen|>",
|
| 41 |
+
"<|speak|>",
|
| 42 |
+
"<|interrupt|>",
|
| 43 |
+
"<|vad_start|>",
|
| 44 |
+
"<|vad_end|>",
|
| 45 |
+
"<|emotion_start|>",
|
| 46 |
+
"<|emotion_end|>",
|
| 47 |
+
"<|speed_start|>",
|
| 48 |
+
"<|speed_end|>",
|
| 49 |
+
"<|pitch_start|>",
|
| 50 |
+
"<|pitch_end|>",
|
| 51 |
+
"<|chunk_eos|>",
|
| 52 |
+
"<|chunk_bos|>",
|
| 53 |
+
"<|chunk_tts_bos|>",
|
| 54 |
+
"<|chunk_tts_eos|>",
|
| 55 |
+
"<|tts_pad|>",
|
| 56 |
+
"<|timbre_7|>",
|
| 57 |
+
"<|timbre_8|>",
|
| 58 |
+
"<|timbre_9|>",
|
| 59 |
+
"<|timbre_10|>",
|
| 60 |
+
"<|timbre_11|>",
|
| 61 |
+
"<|timbre_12|>",
|
| 62 |
+
"<|timbre_13|>",
|
| 63 |
+
"<|timbre_14|>",
|
| 64 |
+
"<|timbre_15|>",
|
| 65 |
+
"<|timbre_16|>",
|
| 66 |
+
"<|timbre_17|>",
|
| 67 |
+
"<|timbre_18|>",
|
| 68 |
+
"<|timbre_19|>",
|
| 69 |
+
"<|timbre_20|>",
|
| 70 |
+
"<|timbre_21|>",
|
| 71 |
+
"<|timbre_22|>",
|
| 72 |
+
"<|timbre_23|>",
|
| 73 |
+
"<|timbre_24|>",
|
| 74 |
+
"<|timbre_25|>",
|
| 75 |
+
"<|timbre_26|>",
|
| 76 |
+
"<|timbre_27|>",
|
| 77 |
+
"<|timbre_28|>",
|
| 78 |
+
"<|timbre_29|>",
|
| 79 |
+
"<|timbre_30|>",
|
| 80 |
+
"<|timbre_31|>",
|
| 81 |
+
"<delegate>",
|
| 82 |
+
"</delegate>",
|
| 83 |
+
"<backend>",
|
| 84 |
+
"</backend>",
|
| 85 |
+
{
|
| 86 |
+
"content": "<|im_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false
|
| 91 |
+
}
|
| 92 |
+
],
|
| 93 |
+
"bos_token": {
|
| 94 |
+
"content": "<|im_start|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false
|
| 99 |
+
},
|
| 100 |
+
"eos_token": {
|
| 101 |
+
"content": "<|im_end|>",
|
| 102 |
+
"lstrip": false,
|
| 103 |
+
"normalized": false,
|
| 104 |
+
"rstrip": false,
|
| 105 |
+
"single_word": false
|
| 106 |
+
},
|
| 107 |
+
"pad_token": {
|
| 108 |
+
"content": "<|endoftext|>",
|
| 109 |
+
"lstrip": false,
|
| 110 |
+
"normalized": false,
|
| 111 |
+
"rstrip": false,
|
| 112 |
+
"single_word": false
|
| 113 |
+
},
|
| 114 |
+
"unk_token": {
|
| 115 |
+
"content": "<unk>",
|
| 116 |
+
"lstrip": false,
|
| 117 |
+
"normalized": false,
|
| 118 |
+
"rstrip": false,
|
| 119 |
+
"single_word": false
|
| 120 |
+
}
|
| 121 |
+
}
|
Realtime-Venus-Audio/tokenization_minicpmo_fast.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# Copyright 2026 The OpenBMB Team. All rights reserved.
|
| 4 |
+
# Modifications Copyright 2026 The Realtime-Venus Team.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
from typing import List
|
| 19 |
+
|
| 20 |
+
from transformers import Qwen2TokenizerFast
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class MiniCPMOTokenizerFast(Qwen2TokenizerFast):
|
| 24 |
+
def __init__(self, **kwargs):
|
| 25 |
+
self._bad_token_ids = kwargs.pop("bad_token_ids", [])
|
| 26 |
+
|
| 27 |
+
super().__init__(**kwargs)
|
| 28 |
+
|
| 29 |
+
# image
|
| 30 |
+
self.im_start = "<image>"
|
| 31 |
+
self.im_end = "</image>"
|
| 32 |
+
self.ref_start = "<ref>"
|
| 33 |
+
self.ref_end = "</ref>"
|
| 34 |
+
self.box_start = "<box>"
|
| 35 |
+
self.box_end = "</box>"
|
| 36 |
+
self.quad_start = "<quad>"
|
| 37 |
+
self.quad_end = "</quad>"
|
| 38 |
+
self.slice_start = "<slice>"
|
| 39 |
+
self.slice_end = "</slice>"
|
| 40 |
+
self.im_id_start = "<image_id>"
|
| 41 |
+
self.im_id_end = "</image_id>"
|
| 42 |
+
|
| 43 |
+
# audio
|
| 44 |
+
self.audio_start = "<|audio_start|>"
|
| 45 |
+
self.audio_end = "<|audio_end|>"
|
| 46 |
+
self.spk_start = "<|spk_bos|>"
|
| 47 |
+
self.spk_end = "<|spk_eos|>"
|
| 48 |
+
self.tts_start = "<|tts_bos|>"
|
| 49 |
+
self.tts_end = "<|tts_eos|>"
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def eos_id(self):
|
| 53 |
+
return self.eos_token_id
|
| 54 |
+
|
| 55 |
+
@property
|
| 56 |
+
def bos_id(self):
|
| 57 |
+
return self.bos_token_id
|
| 58 |
+
|
| 59 |
+
@property
|
| 60 |
+
def unk_id(self):
|
| 61 |
+
return self.unk_token_id
|
| 62 |
+
|
| 63 |
+
@property
|
| 64 |
+
def im_start_id(self):
|
| 65 |
+
return self.convert_tokens_to_ids(self.im_start)
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
def im_end_id(self):
|
| 69 |
+
return self.convert_tokens_to_ids(self.im_end)
|
| 70 |
+
|
| 71 |
+
@property
|
| 72 |
+
def slice_start_id(self):
|
| 73 |
+
return self.convert_tokens_to_ids(self.slice_start)
|
| 74 |
+
|
| 75 |
+
@property
|
| 76 |
+
def slice_end_id(self):
|
| 77 |
+
return self.convert_tokens_to_ids(self.slice_end)
|
| 78 |
+
|
| 79 |
+
@property
|
| 80 |
+
def im_id_start_id(self):
|
| 81 |
+
return self.convert_tokens_to_ids(self.im_id_start)
|
| 82 |
+
|
| 83 |
+
@property
|
| 84 |
+
def im_id_end_id(self):
|
| 85 |
+
return self.convert_tokens_to_ids(self.im_id_end)
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def audio_start_id(self):
|
| 89 |
+
return self.convert_tokens_to_ids(self.audio_start)
|
| 90 |
+
|
| 91 |
+
@property
|
| 92 |
+
def audio_end_id(self):
|
| 93 |
+
return self.convert_tokens_to_ids(self.audio_end)
|
| 94 |
+
|
| 95 |
+
@property
|
| 96 |
+
def spk_start_id(self):
|
| 97 |
+
return self.convert_tokens_to_ids(self.spk_start)
|
| 98 |
+
|
| 99 |
+
@property
|
| 100 |
+
def spk_end_id(self):
|
| 101 |
+
return self.convert_tokens_to_ids(self.spk_end)
|
| 102 |
+
|
| 103 |
+
@property
|
| 104 |
+
def tts_start_id(self):
|
| 105 |
+
return self.convert_tokens_to_ids(self.tts_start)
|
| 106 |
+
|
| 107 |
+
@property
|
| 108 |
+
def tts_end_id(self):
|
| 109 |
+
return self.convert_tokens_to_ids(self.tts_end)
|
| 110 |
+
|
| 111 |
+
@staticmethod
|
| 112 |
+
def escape(text: str) -> str:
|
| 113 |
+
return text
|
| 114 |
+
|
| 115 |
+
@staticmethod
|
| 116 |
+
def unescape(text: str) -> str:
|
| 117 |
+
return text
|
| 118 |
+
|
| 119 |
+
@property
|
| 120 |
+
def bad_token_ids(self) -> List[int]:
|
| 121 |
+
return self._bad_token_ids
|
Realtime-Venus-Audio/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6d694bbb227bd59984fcad14c7d807ad74507019c9a1a8013d325c9d87977432
|
| 3 |
+
size 11438460
|
Realtime-Venus-Audio/tokenizer_config.json
ADDED
|
@@ -0,0 +1,990 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"128244": {
|
| 6 |
+
"content": "<unk>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151643": {
|
| 14 |
+
"content": "<|endoftext|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151644": {
|
| 22 |
+
"content": "<|im_start|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151645": {
|
| 30 |
+
"content": "<|im_end|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151646": {
|
| 38 |
+
"content": "<|object_ref_start|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151647": {
|
| 46 |
+
"content": "<|object_ref_end|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151648": {
|
| 54 |
+
"content": "<|box_start|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151649": {
|
| 62 |
+
"content": "<|box_end|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151650": {
|
| 70 |
+
"content": "<|quad_start|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151651": {
|
| 78 |
+
"content": "<|quad_end|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151652": {
|
| 86 |
+
"content": "<|vision_start|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151653": {
|
| 94 |
+
"content": "<|vision_end|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151654": {
|
| 102 |
+
"content": "<|vision_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151655": {
|
| 110 |
+
"content": "<|image_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151656": {
|
| 118 |
+
"content": "<|video_pad|>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": true
|
| 124 |
+
},
|
| 125 |
+
"151657": {
|
| 126 |
+
"content": "<tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151658": {
|
| 134 |
+
"content": "</tool_call>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151659": {
|
| 142 |
+
"content": "<|fim_prefix|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151660": {
|
| 150 |
+
"content": "<|fim_middle|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151661": {
|
| 158 |
+
"content": "<|fim_suffix|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151662": {
|
| 166 |
+
"content": "<|fim_pad|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151663": {
|
| 174 |
+
"content": "<|repo_name|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
},
|
| 181 |
+
"151664": {
|
| 182 |
+
"content": "<|file_sep|>",
|
| 183 |
+
"lstrip": false,
|
| 184 |
+
"normalized": false,
|
| 185 |
+
"rstrip": false,
|
| 186 |
+
"single_word": false,
|
| 187 |
+
"special": false
|
| 188 |
+
},
|
| 189 |
+
"151665": {
|
| 190 |
+
"content": "<tool_response>",
|
| 191 |
+
"lstrip": false,
|
| 192 |
+
"normalized": false,
|
| 193 |
+
"rstrip": false,
|
| 194 |
+
"single_word": false,
|
| 195 |
+
"special": false
|
| 196 |
+
},
|
| 197 |
+
"151666": {
|
| 198 |
+
"content": "</tool_response>",
|
| 199 |
+
"lstrip": false,
|
| 200 |
+
"normalized": false,
|
| 201 |
+
"rstrip": false,
|
| 202 |
+
"single_word": false,
|
| 203 |
+
"special": false
|
| 204 |
+
},
|
| 205 |
+
"151667": {
|
| 206 |
+
"content": "<think>",
|
| 207 |
+
"lstrip": false,
|
| 208 |
+
"normalized": false,
|
| 209 |
+
"rstrip": false,
|
| 210 |
+
"single_word": false,
|
| 211 |
+
"special": false
|
| 212 |
+
},
|
| 213 |
+
"151668": {
|
| 214 |
+
"content": "</think>",
|
| 215 |
+
"lstrip": false,
|
| 216 |
+
"normalized": false,
|
| 217 |
+
"rstrip": false,
|
| 218 |
+
"single_word": false,
|
| 219 |
+
"special": false
|
| 220 |
+
},
|
| 221 |
+
"151669": {
|
| 222 |
+
"content": "<image>",
|
| 223 |
+
"lstrip": false,
|
| 224 |
+
"normalized": false,
|
| 225 |
+
"rstrip": false,
|
| 226 |
+
"single_word": false,
|
| 227 |
+
"special": true
|
| 228 |
+
},
|
| 229 |
+
"151670": {
|
| 230 |
+
"content": "</image>",
|
| 231 |
+
"lstrip": false,
|
| 232 |
+
"normalized": false,
|
| 233 |
+
"rstrip": false,
|
| 234 |
+
"single_word": false,
|
| 235 |
+
"special": true
|
| 236 |
+
},
|
| 237 |
+
"151671": {
|
| 238 |
+
"content": "<ref>",
|
| 239 |
+
"lstrip": false,
|
| 240 |
+
"normalized": false,
|
| 241 |
+
"rstrip": false,
|
| 242 |
+
"single_word": false,
|
| 243 |
+
"special": true
|
| 244 |
+
},
|
| 245 |
+
"151672": {
|
| 246 |
+
"content": "</ref>",
|
| 247 |
+
"lstrip": false,
|
| 248 |
+
"normalized": false,
|
| 249 |
+
"rstrip": false,
|
| 250 |
+
"single_word": false,
|
| 251 |
+
"special": true
|
| 252 |
+
},
|
| 253 |
+
"151673": {
|
| 254 |
+
"content": "<box>",
|
| 255 |
+
"lstrip": false,
|
| 256 |
+
"normalized": false,
|
| 257 |
+
"rstrip": false,
|
| 258 |
+
"single_word": false,
|
| 259 |
+
"special": true
|
| 260 |
+
},
|
| 261 |
+
"151674": {
|
| 262 |
+
"content": "</box>",
|
| 263 |
+
"lstrip": false,
|
| 264 |
+
"normalized": false,
|
| 265 |
+
"rstrip": false,
|
| 266 |
+
"single_word": false,
|
| 267 |
+
"special": true
|
| 268 |
+
},
|
| 269 |
+
"151675": {
|
| 270 |
+
"content": "<quad>",
|
| 271 |
+
"lstrip": false,
|
| 272 |
+
"normalized": false,
|
| 273 |
+
"rstrip": false,
|
| 274 |
+
"single_word": false,
|
| 275 |
+
"special": true
|
| 276 |
+
},
|
| 277 |
+
"151676": {
|
| 278 |
+
"content": "</quad>",
|
| 279 |
+
"lstrip": false,
|
| 280 |
+
"normalized": false,
|
| 281 |
+
"rstrip": false,
|
| 282 |
+
"single_word": false,
|
| 283 |
+
"special": true
|
| 284 |
+
},
|
| 285 |
+
"151677": {
|
| 286 |
+
"content": "<point>",
|
| 287 |
+
"lstrip": false,
|
| 288 |
+
"normalized": false,
|
| 289 |
+
"rstrip": false,
|
| 290 |
+
"single_word": false,
|
| 291 |
+
"special": true
|
| 292 |
+
},
|
| 293 |
+
"151678": {
|
| 294 |
+
"content": "</point>",
|
| 295 |
+
"lstrip": false,
|
| 296 |
+
"normalized": false,
|
| 297 |
+
"rstrip": false,
|
| 298 |
+
"single_word": false,
|
| 299 |
+
"special": true
|
| 300 |
+
},
|
| 301 |
+
"151679": {
|
| 302 |
+
"content": "<slice>",
|
| 303 |
+
"lstrip": false,
|
| 304 |
+
"normalized": false,
|
| 305 |
+
"rstrip": false,
|
| 306 |
+
"single_word": false,
|
| 307 |
+
"special": true
|
| 308 |
+
},
|
| 309 |
+
"151680": {
|
| 310 |
+
"content": "</slice>",
|
| 311 |
+
"lstrip": false,
|
| 312 |
+
"normalized": false,
|
| 313 |
+
"rstrip": false,
|
| 314 |
+
"single_word": false,
|
| 315 |
+
"special": true
|
| 316 |
+
},
|
| 317 |
+
"151681": {
|
| 318 |
+
"content": "<image_id>",
|
| 319 |
+
"lstrip": false,
|
| 320 |
+
"normalized": false,
|
| 321 |
+
"rstrip": false,
|
| 322 |
+
"single_word": false,
|
| 323 |
+
"special": true
|
| 324 |
+
},
|
| 325 |
+
"151682": {
|
| 326 |
+
"content": "</image_id>",
|
| 327 |
+
"lstrip": false,
|
| 328 |
+
"normalized": false,
|
| 329 |
+
"rstrip": false,
|
| 330 |
+
"single_word": false,
|
| 331 |
+
"special": true
|
| 332 |
+
},
|
| 333 |
+
"151683": {
|
| 334 |
+
"content": "<unit>",
|
| 335 |
+
"lstrip": false,
|
| 336 |
+
"normalized": false,
|
| 337 |
+
"rstrip": false,
|
| 338 |
+
"single_word": false,
|
| 339 |
+
"special": true
|
| 340 |
+
},
|
| 341 |
+
"151684": {
|
| 342 |
+
"content": "</unit>",
|
| 343 |
+
"lstrip": false,
|
| 344 |
+
"normalized": false,
|
| 345 |
+
"rstrip": false,
|
| 346 |
+
"single_word": false,
|
| 347 |
+
"special": true
|
| 348 |
+
},
|
| 349 |
+
"151685": {
|
| 350 |
+
"content": "<answer>",
|
| 351 |
+
"lstrip": false,
|
| 352 |
+
"normalized": false,
|
| 353 |
+
"rstrip": false,
|
| 354 |
+
"single_word": false,
|
| 355 |
+
"special": true
|
| 356 |
+
},
|
| 357 |
+
"151686": {
|
| 358 |
+
"content": "</answer>",
|
| 359 |
+
"lstrip": false,
|
| 360 |
+
"normalized": false,
|
| 361 |
+
"rstrip": false,
|
| 362 |
+
"single_word": false,
|
| 363 |
+
"special": true
|
| 364 |
+
},
|
| 365 |
+
"151687": {
|
| 366 |
+
"content": "<focus>",
|
| 367 |
+
"lstrip": false,
|
| 368 |
+
"normalized": false,
|
| 369 |
+
"rstrip": false,
|
| 370 |
+
"single_word": false,
|
| 371 |
+
"special": true
|
| 372 |
+
},
|
| 373 |
+
"151688": {
|
| 374 |
+
"content": "</focus>",
|
| 375 |
+
"lstrip": false,
|
| 376 |
+
"normalized": false,
|
| 377 |
+
"rstrip": false,
|
| 378 |
+
"single_word": false,
|
| 379 |
+
"special": true
|
| 380 |
+
},
|
| 381 |
+
"151689": {
|
| 382 |
+
"content": "<line>",
|
| 383 |
+
"lstrip": false,
|
| 384 |
+
"normalized": false,
|
| 385 |
+
"rstrip": false,
|
| 386 |
+
"single_word": false,
|
| 387 |
+
"special": true
|
| 388 |
+
},
|
| 389 |
+
"151690": {
|
| 390 |
+
"content": "</line>",
|
| 391 |
+
"lstrip": false,
|
| 392 |
+
"normalized": false,
|
| 393 |
+
"rstrip": false,
|
| 394 |
+
"single_word": false,
|
| 395 |
+
"special": true
|
| 396 |
+
},
|
| 397 |
+
"151691": {
|
| 398 |
+
"content": "<perception>",
|
| 399 |
+
"lstrip": false,
|
| 400 |
+
"normalized": false,
|
| 401 |
+
"rstrip": false,
|
| 402 |
+
"single_word": false,
|
| 403 |
+
"special": true
|
| 404 |
+
},
|
| 405 |
+
"151692": {
|
| 406 |
+
"content": "</perception>",
|
| 407 |
+
"lstrip": false,
|
| 408 |
+
"normalized": false,
|
| 409 |
+
"rstrip": false,
|
| 410 |
+
"single_word": false,
|
| 411 |
+
"special": true
|
| 412 |
+
},
|
| 413 |
+
"151693": {
|
| 414 |
+
"content": "<source_image>",
|
| 415 |
+
"lstrip": false,
|
| 416 |
+
"normalized": false,
|
| 417 |
+
"rstrip": false,
|
| 418 |
+
"single_word": false,
|
| 419 |
+
"special": true
|
| 420 |
+
},
|
| 421 |
+
"151694": {
|
| 422 |
+
"content": "</source_image>",
|
| 423 |
+
"lstrip": false,
|
| 424 |
+
"normalized": false,
|
| 425 |
+
"rstrip": false,
|
| 426 |
+
"single_word": false,
|
| 427 |
+
"special": true
|
| 428 |
+
},
|
| 429 |
+
"151695": {
|
| 430 |
+
"content": "<image_save_to>",
|
| 431 |
+
"lstrip": false,
|
| 432 |
+
"normalized": false,
|
| 433 |
+
"rstrip": false,
|
| 434 |
+
"single_word": false,
|
| 435 |
+
"special": true
|
| 436 |
+
},
|
| 437 |
+
"151696": {
|
| 438 |
+
"content": "</image_save_to>",
|
| 439 |
+
"lstrip": false,
|
| 440 |
+
"normalized": false,
|
| 441 |
+
"rstrip": false,
|
| 442 |
+
"single_word": false,
|
| 443 |
+
"special": true
|
| 444 |
+
},
|
| 445 |
+
"151697": {
|
| 446 |
+
"content": "<|audio_start|>",
|
| 447 |
+
"lstrip": false,
|
| 448 |
+
"normalized": false,
|
| 449 |
+
"rstrip": false,
|
| 450 |
+
"single_word": false,
|
| 451 |
+
"special": true
|
| 452 |
+
},
|
| 453 |
+
"151698": {
|
| 454 |
+
"content": "<|audio|>",
|
| 455 |
+
"lstrip": false,
|
| 456 |
+
"normalized": false,
|
| 457 |
+
"rstrip": false,
|
| 458 |
+
"single_word": false,
|
| 459 |
+
"special": true
|
| 460 |
+
},
|
| 461 |
+
"151699": {
|
| 462 |
+
"content": "<|audio_end|>",
|
| 463 |
+
"lstrip": false,
|
| 464 |
+
"normalized": false,
|
| 465 |
+
"rstrip": false,
|
| 466 |
+
"single_word": false,
|
| 467 |
+
"special": true
|
| 468 |
+
},
|
| 469 |
+
"151700": {
|
| 470 |
+
"content": "<|spk_bos|>",
|
| 471 |
+
"lstrip": false,
|
| 472 |
+
"normalized": false,
|
| 473 |
+
"rstrip": false,
|
| 474 |
+
"single_word": false,
|
| 475 |
+
"special": true
|
| 476 |
+
},
|
| 477 |
+
"151701": {
|
| 478 |
+
"content": "<|spk|>",
|
| 479 |
+
"lstrip": false,
|
| 480 |
+
"normalized": false,
|
| 481 |
+
"rstrip": false,
|
| 482 |
+
"single_word": false,
|
| 483 |
+
"special": true
|
| 484 |
+
},
|
| 485 |
+
"151702": {
|
| 486 |
+
"content": "<|spk_eos|>",
|
| 487 |
+
"lstrip": false,
|
| 488 |
+
"normalized": false,
|
| 489 |
+
"rstrip": false,
|
| 490 |
+
"single_word": false,
|
| 491 |
+
"special": true
|
| 492 |
+
},
|
| 493 |
+
"151703": {
|
| 494 |
+
"content": "<|tts_bos|>",
|
| 495 |
+
"lstrip": false,
|
| 496 |
+
"normalized": false,
|
| 497 |
+
"rstrip": false,
|
| 498 |
+
"single_word": false,
|
| 499 |
+
"special": true
|
| 500 |
+
},
|
| 501 |
+
"151704": {
|
| 502 |
+
"content": "<|tts_eos|>",
|
| 503 |
+
"lstrip": false,
|
| 504 |
+
"normalized": false,
|
| 505 |
+
"rstrip": false,
|
| 506 |
+
"single_word": false,
|
| 507 |
+
"special": true
|
| 508 |
+
},
|
| 509 |
+
"151705": {
|
| 510 |
+
"content": "<|listen|>",
|
| 511 |
+
"lstrip": false,
|
| 512 |
+
"normalized": false,
|
| 513 |
+
"rstrip": false,
|
| 514 |
+
"single_word": false,
|
| 515 |
+
"special": true
|
| 516 |
+
},
|
| 517 |
+
"151706": {
|
| 518 |
+
"content": "<|speak|>",
|
| 519 |
+
"lstrip": false,
|
| 520 |
+
"normalized": false,
|
| 521 |
+
"rstrip": false,
|
| 522 |
+
"single_word": false,
|
| 523 |
+
"special": true
|
| 524 |
+
},
|
| 525 |
+
"151707": {
|
| 526 |
+
"content": "<|interrupt|>",
|
| 527 |
+
"lstrip": false,
|
| 528 |
+
"normalized": false,
|
| 529 |
+
"rstrip": false,
|
| 530 |
+
"single_word": false,
|
| 531 |
+
"special": true
|
| 532 |
+
},
|
| 533 |
+
"151708": {
|
| 534 |
+
"content": "<|vad_start|>",
|
| 535 |
+
"lstrip": false,
|
| 536 |
+
"normalized": false,
|
| 537 |
+
"rstrip": false,
|
| 538 |
+
"single_word": false,
|
| 539 |
+
"special": true
|
| 540 |
+
},
|
| 541 |
+
"151709": {
|
| 542 |
+
"content": "<|vad_end|>",
|
| 543 |
+
"lstrip": false,
|
| 544 |
+
"normalized": false,
|
| 545 |
+
"rstrip": false,
|
| 546 |
+
"single_word": false,
|
| 547 |
+
"special": true
|
| 548 |
+
},
|
| 549 |
+
"151710": {
|
| 550 |
+
"content": "<|emotion_start|>",
|
| 551 |
+
"lstrip": false,
|
| 552 |
+
"normalized": false,
|
| 553 |
+
"rstrip": false,
|
| 554 |
+
"single_word": false,
|
| 555 |
+
"special": true
|
| 556 |
+
},
|
| 557 |
+
"151711": {
|
| 558 |
+
"content": "<|emotion_end|>",
|
| 559 |
+
"lstrip": false,
|
| 560 |
+
"normalized": false,
|
| 561 |
+
"rstrip": false,
|
| 562 |
+
"single_word": false,
|
| 563 |
+
"special": true
|
| 564 |
+
},
|
| 565 |
+
"151712": {
|
| 566 |
+
"content": "<|speed_start|>",
|
| 567 |
+
"lstrip": false,
|
| 568 |
+
"normalized": false,
|
| 569 |
+
"rstrip": false,
|
| 570 |
+
"single_word": false,
|
| 571 |
+
"special": true
|
| 572 |
+
},
|
| 573 |
+
"151713": {
|
| 574 |
+
"content": "<|speed_end|>",
|
| 575 |
+
"lstrip": false,
|
| 576 |
+
"normalized": false,
|
| 577 |
+
"rstrip": false,
|
| 578 |
+
"single_word": false,
|
| 579 |
+
"special": true
|
| 580 |
+
},
|
| 581 |
+
"151714": {
|
| 582 |
+
"content": "<|pitch_start|>",
|
| 583 |
+
"lstrip": false,
|
| 584 |
+
"normalized": false,
|
| 585 |
+
"rstrip": false,
|
| 586 |
+
"single_word": false,
|
| 587 |
+
"special": true
|
| 588 |
+
},
|
| 589 |
+
"151715": {
|
| 590 |
+
"content": "<|pitch_end|>",
|
| 591 |
+
"lstrip": false,
|
| 592 |
+
"normalized": false,
|
| 593 |
+
"rstrip": false,
|
| 594 |
+
"single_word": false,
|
| 595 |
+
"special": true
|
| 596 |
+
},
|
| 597 |
+
"151716": {
|
| 598 |
+
"content": "<|turn_bos|>",
|
| 599 |
+
"lstrip": false,
|
| 600 |
+
"normalized": false,
|
| 601 |
+
"rstrip": false,
|
| 602 |
+
"single_word": false,
|
| 603 |
+
"special": true
|
| 604 |
+
},
|
| 605 |
+
"151717": {
|
| 606 |
+
"content": "<|turn_eos|>",
|
| 607 |
+
"lstrip": false,
|
| 608 |
+
"normalized": false,
|
| 609 |
+
"rstrip": false,
|
| 610 |
+
"single_word": false,
|
| 611 |
+
"special": true
|
| 612 |
+
},
|
| 613 |
+
"151718": {
|
| 614 |
+
"content": "<|chunk_eos|>",
|
| 615 |
+
"lstrip": false,
|
| 616 |
+
"normalized": false,
|
| 617 |
+
"rstrip": false,
|
| 618 |
+
"single_word": false,
|
| 619 |
+
"special": true
|
| 620 |
+
},
|
| 621 |
+
"151719": {
|
| 622 |
+
"content": "<|chunk_bos|>",
|
| 623 |
+
"lstrip": false,
|
| 624 |
+
"normalized": false,
|
| 625 |
+
"rstrip": false,
|
| 626 |
+
"single_word": false,
|
| 627 |
+
"special": true
|
| 628 |
+
},
|
| 629 |
+
"151720": {
|
| 630 |
+
"content": "<|chunk_tts_bos|>",
|
| 631 |
+
"lstrip": false,
|
| 632 |
+
"normalized": false,
|
| 633 |
+
"rstrip": false,
|
| 634 |
+
"single_word": false,
|
| 635 |
+
"special": true
|
| 636 |
+
},
|
| 637 |
+
"151721": {
|
| 638 |
+
"content": "<|chunk_tts_eos|>",
|
| 639 |
+
"lstrip": false,
|
| 640 |
+
"normalized": false,
|
| 641 |
+
"rstrip": false,
|
| 642 |
+
"single_word": false,
|
| 643 |
+
"special": true
|
| 644 |
+
},
|
| 645 |
+
"151722": {
|
| 646 |
+
"content": "<|tts_pad|>",
|
| 647 |
+
"lstrip": false,
|
| 648 |
+
"normalized": false,
|
| 649 |
+
"rstrip": false,
|
| 650 |
+
"single_word": false,
|
| 651 |
+
"special": true
|
| 652 |
+
},
|
| 653 |
+
"151723": {
|
| 654 |
+
"content": "<|timbre_7|>",
|
| 655 |
+
"lstrip": false,
|
| 656 |
+
"normalized": false,
|
| 657 |
+
"rstrip": false,
|
| 658 |
+
"single_word": false,
|
| 659 |
+
"special": true
|
| 660 |
+
},
|
| 661 |
+
"151724": {
|
| 662 |
+
"content": "<|timbre_8|>",
|
| 663 |
+
"lstrip": false,
|
| 664 |
+
"normalized": false,
|
| 665 |
+
"rstrip": false,
|
| 666 |
+
"single_word": false,
|
| 667 |
+
"special": true
|
| 668 |
+
},
|
| 669 |
+
"151725": {
|
| 670 |
+
"content": "<|timbre_9|>",
|
| 671 |
+
"lstrip": false,
|
| 672 |
+
"normalized": false,
|
| 673 |
+
"rstrip": false,
|
| 674 |
+
"single_word": false,
|
| 675 |
+
"special": true
|
| 676 |
+
},
|
| 677 |
+
"151726": {
|
| 678 |
+
"content": "<|timbre_10|>",
|
| 679 |
+
"lstrip": false,
|
| 680 |
+
"normalized": false,
|
| 681 |
+
"rstrip": false,
|
| 682 |
+
"single_word": false,
|
| 683 |
+
"special": true
|
| 684 |
+
},
|
| 685 |
+
"151727": {
|
| 686 |
+
"content": "<|timbre_11|>",
|
| 687 |
+
"lstrip": false,
|
| 688 |
+
"normalized": false,
|
| 689 |
+
"rstrip": false,
|
| 690 |
+
"single_word": false,
|
| 691 |
+
"special": true
|
| 692 |
+
},
|
| 693 |
+
"151728": {
|
| 694 |
+
"content": "<|timbre_12|>",
|
| 695 |
+
"lstrip": false,
|
| 696 |
+
"normalized": false,
|
| 697 |
+
"rstrip": false,
|
| 698 |
+
"single_word": false,
|
| 699 |
+
"special": true
|
| 700 |
+
},
|
| 701 |
+
"151729": {
|
| 702 |
+
"content": "<|timbre_13|>",
|
| 703 |
+
"lstrip": false,
|
| 704 |
+
"normalized": false,
|
| 705 |
+
"rstrip": false,
|
| 706 |
+
"single_word": false,
|
| 707 |
+
"special": true
|
| 708 |
+
},
|
| 709 |
+
"151730": {
|
| 710 |
+
"content": "<|timbre_14|>",
|
| 711 |
+
"lstrip": false,
|
| 712 |
+
"normalized": false,
|
| 713 |
+
"rstrip": false,
|
| 714 |
+
"single_word": false,
|
| 715 |
+
"special": true
|
| 716 |
+
},
|
| 717 |
+
"151731": {
|
| 718 |
+
"content": "<|timbre_15|>",
|
| 719 |
+
"lstrip": false,
|
| 720 |
+
"normalized": false,
|
| 721 |
+
"rstrip": false,
|
| 722 |
+
"single_word": false,
|
| 723 |
+
"special": true
|
| 724 |
+
},
|
| 725 |
+
"151732": {
|
| 726 |
+
"content": "<|timbre_16|>",
|
| 727 |
+
"lstrip": false,
|
| 728 |
+
"normalized": false,
|
| 729 |
+
"rstrip": false,
|
| 730 |
+
"single_word": false,
|
| 731 |
+
"special": true
|
| 732 |
+
},
|
| 733 |
+
"151733": {
|
| 734 |
+
"content": "<|timbre_17|>",
|
| 735 |
+
"lstrip": false,
|
| 736 |
+
"normalized": false,
|
| 737 |
+
"rstrip": false,
|
| 738 |
+
"single_word": false,
|
| 739 |
+
"special": true
|
| 740 |
+
},
|
| 741 |
+
"151734": {
|
| 742 |
+
"content": "<|timbre_18|>",
|
| 743 |
+
"lstrip": false,
|
| 744 |
+
"normalized": false,
|
| 745 |
+
"rstrip": false,
|
| 746 |
+
"single_word": false,
|
| 747 |
+
"special": true
|
| 748 |
+
},
|
| 749 |
+
"151735": {
|
| 750 |
+
"content": "<|timbre_19|>",
|
| 751 |
+
"lstrip": false,
|
| 752 |
+
"normalized": false,
|
| 753 |
+
"rstrip": false,
|
| 754 |
+
"single_word": false,
|
| 755 |
+
"special": true
|
| 756 |
+
},
|
| 757 |
+
"151736": {
|
| 758 |
+
"content": "<|timbre_20|>",
|
| 759 |
+
"lstrip": false,
|
| 760 |
+
"normalized": false,
|
| 761 |
+
"rstrip": false,
|
| 762 |
+
"single_word": false,
|
| 763 |
+
"special": true
|
| 764 |
+
},
|
| 765 |
+
"151737": {
|
| 766 |
+
"content": "<|timbre_21|>",
|
| 767 |
+
"lstrip": false,
|
| 768 |
+
"normalized": false,
|
| 769 |
+
"rstrip": false,
|
| 770 |
+
"single_word": false,
|
| 771 |
+
"special": true
|
| 772 |
+
},
|
| 773 |
+
"151738": {
|
| 774 |
+
"content": "<|timbre_22|>",
|
| 775 |
+
"lstrip": false,
|
| 776 |
+
"normalized": false,
|
| 777 |
+
"rstrip": false,
|
| 778 |
+
"single_word": false,
|
| 779 |
+
"special": true
|
| 780 |
+
},
|
| 781 |
+
"151739": {
|
| 782 |
+
"content": "<|timbre_23|>",
|
| 783 |
+
"lstrip": false,
|
| 784 |
+
"normalized": false,
|
| 785 |
+
"rstrip": false,
|
| 786 |
+
"single_word": false,
|
| 787 |
+
"special": true
|
| 788 |
+
},
|
| 789 |
+
"151740": {
|
| 790 |
+
"content": "<|timbre_24|>",
|
| 791 |
+
"lstrip": false,
|
| 792 |
+
"normalized": false,
|
| 793 |
+
"rstrip": false,
|
| 794 |
+
"single_word": false,
|
| 795 |
+
"special": true
|
| 796 |
+
},
|
| 797 |
+
"151741": {
|
| 798 |
+
"content": "<|timbre_25|>",
|
| 799 |
+
"lstrip": false,
|
| 800 |
+
"normalized": false,
|
| 801 |
+
"rstrip": false,
|
| 802 |
+
"single_word": false,
|
| 803 |
+
"special": true
|
| 804 |
+
},
|
| 805 |
+
"151742": {
|
| 806 |
+
"content": "<|timbre_26|>",
|
| 807 |
+
"lstrip": false,
|
| 808 |
+
"normalized": false,
|
| 809 |
+
"rstrip": false,
|
| 810 |
+
"single_word": false,
|
| 811 |
+
"special": true
|
| 812 |
+
},
|
| 813 |
+
"151743": {
|
| 814 |
+
"content": "<|timbre_27|>",
|
| 815 |
+
"lstrip": false,
|
| 816 |
+
"normalized": false,
|
| 817 |
+
"rstrip": false,
|
| 818 |
+
"single_word": false,
|
| 819 |
+
"special": true
|
| 820 |
+
},
|
| 821 |
+
"151744": {
|
| 822 |
+
"content": "<|timbre_28|>",
|
| 823 |
+
"lstrip": false,
|
| 824 |
+
"normalized": false,
|
| 825 |
+
"rstrip": false,
|
| 826 |
+
"single_word": false,
|
| 827 |
+
"special": true
|
| 828 |
+
},
|
| 829 |
+
"151745": {
|
| 830 |
+
"content": "<|timbre_29|>",
|
| 831 |
+
"lstrip": false,
|
| 832 |
+
"normalized": false,
|
| 833 |
+
"rstrip": false,
|
| 834 |
+
"single_word": false,
|
| 835 |
+
"special": true
|
| 836 |
+
},
|
| 837 |
+
"151746": {
|
| 838 |
+
"content": "<|timbre_30|>",
|
| 839 |
+
"lstrip": false,
|
| 840 |
+
"normalized": false,
|
| 841 |
+
"rstrip": false,
|
| 842 |
+
"single_word": false,
|
| 843 |
+
"special": true
|
| 844 |
+
},
|
| 845 |
+
"151747": {
|
| 846 |
+
"content": "<|timbre_31|>",
|
| 847 |
+
"lstrip": false,
|
| 848 |
+
"normalized": false,
|
| 849 |
+
"rstrip": false,
|
| 850 |
+
"single_word": false,
|
| 851 |
+
"special": true
|
| 852 |
+
},
|
| 853 |
+
"151748": {
|
| 854 |
+
"content": "<delegate>",
|
| 855 |
+
"lstrip": false,
|
| 856 |
+
"normalized": false,
|
| 857 |
+
"rstrip": false,
|
| 858 |
+
"single_word": false,
|
| 859 |
+
"special": true
|
| 860 |
+
},
|
| 861 |
+
"151749": {
|
| 862 |
+
"content": "</delegate>",
|
| 863 |
+
"lstrip": false,
|
| 864 |
+
"normalized": false,
|
| 865 |
+
"rstrip": false,
|
| 866 |
+
"single_word": false,
|
| 867 |
+
"special": true
|
| 868 |
+
},
|
| 869 |
+
"151750": {
|
| 870 |
+
"content": "<backend>",
|
| 871 |
+
"lstrip": false,
|
| 872 |
+
"normalized": false,
|
| 873 |
+
"rstrip": false,
|
| 874 |
+
"single_word": false,
|
| 875 |
+
"special": true
|
| 876 |
+
},
|
| 877 |
+
"151751": {
|
| 878 |
+
"content": "</backend>",
|
| 879 |
+
"lstrip": false,
|
| 880 |
+
"normalized": false,
|
| 881 |
+
"rstrip": false,
|
| 882 |
+
"single_word": false,
|
| 883 |
+
"special": true
|
| 884 |
+
}
|
| 885 |
+
},
|
| 886 |
+
"additional_special_tokens": [
|
| 887 |
+
"<unk>",
|
| 888 |
+
"<image>",
|
| 889 |
+
"</image>",
|
| 890 |
+
"<ref>",
|
| 891 |
+
"</ref>",
|
| 892 |
+
"<box>",
|
| 893 |
+
"</box>",
|
| 894 |
+
"<quad>",
|
| 895 |
+
"</quad>",
|
| 896 |
+
"<point>",
|
| 897 |
+
"</point>",
|
| 898 |
+
"<slice>",
|
| 899 |
+
"</slice>",
|
| 900 |
+
"<image_id>",
|
| 901 |
+
"</image_id>",
|
| 902 |
+
"<unit>",
|
| 903 |
+
"</unit>",
|
| 904 |
+
"<answer>",
|
| 905 |
+
"</answer>",
|
| 906 |
+
"<focus>",
|
| 907 |
+
"</focus>",
|
| 908 |
+
"<line>",
|
| 909 |
+
"</line>",
|
| 910 |
+
"<perception>",
|
| 911 |
+
"</perception>",
|
| 912 |
+
"<source_image>",
|
| 913 |
+
"</source_image>",
|
| 914 |
+
"<image_save_to>",
|
| 915 |
+
"</image_save_to>",
|
| 916 |
+
"<|audio_start|>",
|
| 917 |
+
"<|audio|>",
|
| 918 |
+
"<|audio_end|>",
|
| 919 |
+
"<|spk_bos|>",
|
| 920 |
+
"<|spk|>",
|
| 921 |
+
"<|spk_eos|>",
|
| 922 |
+
"<|tts_bos|>",
|
| 923 |
+
"<|tts_eos|>",
|
| 924 |
+
"<|listen|>",
|
| 925 |
+
"<|speak|>",
|
| 926 |
+
"<|interrupt|>",
|
| 927 |
+
"<|vad_start|>",
|
| 928 |
+
"<|vad_end|>",
|
| 929 |
+
"<|emotion_start|>",
|
| 930 |
+
"<|emotion_end|>",
|
| 931 |
+
"<|speed_start|>",
|
| 932 |
+
"<|speed_end|>",
|
| 933 |
+
"<|pitch_start|>",
|
| 934 |
+
"<|pitch_end|>",
|
| 935 |
+
"<|chunk_eos|>",
|
| 936 |
+
"<|chunk_bos|>",
|
| 937 |
+
"<|chunk_tts_bos|>",
|
| 938 |
+
"<|chunk_tts_eos|>",
|
| 939 |
+
"<|tts_pad|>",
|
| 940 |
+
"<|timbre_7|>",
|
| 941 |
+
"<|timbre_8|>",
|
| 942 |
+
"<|timbre_9|>",
|
| 943 |
+
"<|timbre_10|>",
|
| 944 |
+
"<|timbre_11|>",
|
| 945 |
+
"<|timbre_12|>",
|
| 946 |
+
"<|timbre_13|>",
|
| 947 |
+
"<|timbre_14|>",
|
| 948 |
+
"<|timbre_15|>",
|
| 949 |
+
"<|timbre_16|>",
|
| 950 |
+
"<|timbre_17|>",
|
| 951 |
+
"<|timbre_18|>",
|
| 952 |
+
"<|timbre_19|>",
|
| 953 |
+
"<|timbre_20|>",
|
| 954 |
+
"<|timbre_21|>",
|
| 955 |
+
"<|timbre_22|>",
|
| 956 |
+
"<|timbre_23|>",
|
| 957 |
+
"<|timbre_24|>",
|
| 958 |
+
"<|timbre_25|>",
|
| 959 |
+
"<|timbre_26|>",
|
| 960 |
+
"<|timbre_27|>",
|
| 961 |
+
"<|timbre_28|>",
|
| 962 |
+
"<|timbre_29|>",
|
| 963 |
+
"<|timbre_30|>",
|
| 964 |
+
"<|timbre_31|>",
|
| 965 |
+
"<delegate>",
|
| 966 |
+
"</delegate>",
|
| 967 |
+
"<backend>",
|
| 968 |
+
"</backend>",
|
| 969 |
+
"<|im_end|>"
|
| 970 |
+
],
|
| 971 |
+
"auto_map": {
|
| 972 |
+
"AutoProcessor": "processing_minicpmo.MiniCPMOProcessor",
|
| 973 |
+
"AutoTokenizer": [
|
| 974 |
+
"tokenization_qwen2.Qwen2Tokenizer",
|
| 975 |
+
"tokenization_minicpmo_fast.MiniCPMOTokenizerFast"
|
| 976 |
+
]
|
| 977 |
+
},
|
| 978 |
+
"bos_token": "<|im_start|>",
|
| 979 |
+
"clean_up_tokenization_spaces": false,
|
| 980 |
+
"eos_token": "<|im_end|>",
|
| 981 |
+
"errors": "replace",
|
| 982 |
+
"extra_special_tokens": {},
|
| 983 |
+
"model_max_length": 131072,
|
| 984 |
+
"pad_token": "<|endoftext|>",
|
| 985 |
+
"padding_side": "left",
|
| 986 |
+
"processor_class": "MiniCPMOProcessor",
|
| 987 |
+
"split_special_tokens": false,
|
| 988 |
+
"tokenizer_class": "MiniCPMOTokenizer",
|
| 989 |
+
"unk_token": "<unk>"
|
| 990 |
+
}
|
Realtime-Venus-Audio/utils.py
ADDED
|
@@ -0,0 +1,2418 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# Copyright 2026 The OpenBMB Team. All rights reserved.
|
| 4 |
+
# Modifications Copyright 2026 The Realtime-Venus Team.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
import logging
|
| 19 |
+
from dataclasses import dataclass
|
| 20 |
+
from typing import Any
|
| 21 |
+
from typing import Dict
|
| 22 |
+
from typing import List
|
| 23 |
+
from typing import Literal
|
| 24 |
+
from typing import Optional
|
| 25 |
+
from typing import Tuple
|
| 26 |
+
from typing import Union
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
import torch.nn.functional as F
|
| 30 |
+
import torch.nn.utils.parametrize as P
|
| 31 |
+
from transformers.cache_utils import DynamicCache
|
| 32 |
+
|
| 33 |
+
logger = logging.getLogger(__name__)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# text
|
| 37 |
+
@dataclass
|
| 38 |
+
class GenerateChunkOutput:
|
| 39 |
+
chunk_token_ids: torch.Tensor
|
| 40 |
+
current_inputs_embeds: torch.Tensor
|
| 41 |
+
input_last_hidden_states: Optional[torch.Tensor] # for tts use_speaker_embedding
|
| 42 |
+
last_hidden_states: Optional[torch.Tensor] # for tts input feature (projector_semantic)
|
| 43 |
+
past_key_values: Optional[torch.Tensor]
|
| 44 |
+
finished: bool
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ChunkPrefillChunkGenerate:
|
| 48 |
+
def __init__(self, model, tokenizer, terminators):
|
| 49 |
+
self.tokenizer = tokenizer
|
| 50 |
+
self.model = model
|
| 51 |
+
self.terminators = terminators
|
| 52 |
+
self.terminators_ids = [tokenizer.convert_tokens_to_ids(i) for i in self.terminators]
|
| 53 |
+
self.embedding_layer = self.model.get_input_embeddings()
|
| 54 |
+
|
| 55 |
+
self.forbidden_tokens = [
|
| 56 |
+
":",
|
| 57 |
+
":",
|
| 58 |
+
";",
|
| 59 |
+
"#",
|
| 60 |
+
"“",
|
| 61 |
+
"”",
|
| 62 |
+
"‘",
|
| 63 |
+
"’",
|
| 64 |
+
"@",
|
| 65 |
+
"*",
|
| 66 |
+
"【",
|
| 67 |
+
"】",
|
| 68 |
+
"「",
|
| 69 |
+
"」",
|
| 70 |
+
"(",
|
| 71 |
+
")",
|
| 72 |
+
"(",
|
| 73 |
+
")",
|
| 74 |
+
"[",
|
| 75 |
+
"]",
|
| 76 |
+
"&",
|
| 77 |
+
"/",
|
| 78 |
+
"$",
|
| 79 |
+
]
|
| 80 |
+
|
| 81 |
+
self.forbidden_token_ids = [tokenizer.convert_tokens_to_ids(i) for i in self.forbidden_tokens]
|
| 82 |
+
bad_token_ids = getattr(tokenizer, "bad_token_ids", [])
|
| 83 |
+
if bad_token_ids:
|
| 84 |
+
self.forbidden_token_ids.extend(bad_token_ids)
|
| 85 |
+
|
| 86 |
+
@staticmethod
|
| 87 |
+
def prepare_generation_config(do_sample, max_new_tokens=50, min_new_tokens=0, **kwargs):
|
| 88 |
+
num_beams = kwargs.get("num_beams", 3)
|
| 89 |
+
generation_config = {
|
| 90 |
+
"num_beams": num_beams,
|
| 91 |
+
"top_p": 0.8,
|
| 92 |
+
"top_k": 100,
|
| 93 |
+
"temperature": 0.7,
|
| 94 |
+
"do_sample": True,
|
| 95 |
+
"repetition_penalty": 1.05,
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
if do_sample:
|
| 99 |
+
generation_config.update(
|
| 100 |
+
{
|
| 101 |
+
"top_p": 0.8,
|
| 102 |
+
"top_k": 100,
|
| 103 |
+
"temperature": 0.7,
|
| 104 |
+
"do_sample": True,
|
| 105 |
+
"repetition_penalty": 1.05,
|
| 106 |
+
}
|
| 107 |
+
)
|
| 108 |
+
elif num_beams > 1:
|
| 109 |
+
generation_config.update({"num_beams": num_beams, "repetition_penalty": 1.2, "do_sample": False})
|
| 110 |
+
else:
|
| 111 |
+
generation_config.update({"do_sample": False, "repetition_penalty": 1.05})
|
| 112 |
+
|
| 113 |
+
generation_config.update((k, kwargs[k]) for k in generation_config.keys() & kwargs.keys())
|
| 114 |
+
generation_config["min_new_tokens"] = min_new_tokens
|
| 115 |
+
generation_config["max_new_tokens"] = max_new_tokens
|
| 116 |
+
|
| 117 |
+
return generation_config
|
| 118 |
+
|
| 119 |
+
def chunk_generate(
|
| 120 |
+
self,
|
| 121 |
+
inputs_embeds: torch.Tensor,
|
| 122 |
+
past_key_values,
|
| 123 |
+
is_first_generate_chunk: bool,
|
| 124 |
+
chunk_size: int,
|
| 125 |
+
return_hidden_states: bool,
|
| 126 |
+
do_sample: bool,
|
| 127 |
+
temperature: float,
|
| 128 |
+
top_p: float,
|
| 129 |
+
top_k: int,
|
| 130 |
+
repetition_penalty: float = 1.05,
|
| 131 |
+
length_penalty: float = 1.0,
|
| 132 |
+
all_input_ids: Optional[torch.Tensor] = None,
|
| 133 |
+
) -> GenerateChunkOutput:
|
| 134 |
+
"""
|
| 135 |
+
Args:
|
| 136 |
+
inputs_embeds: [1, seq_len, hidden_dim], Input embeddings of current chunk.
|
| 137 |
+
past_key_values: [num_layers, 2, batch_size, num_heads, seq_len, head_dim], Past key values for llm.
|
| 138 |
+
is_first_generate_chunk: bool, Whether this is the first generate chunk.
|
| 139 |
+
chunk_size: int, The size of the current chunk, default is 10, and it is fixed during training.
|
| 140 |
+
return_hidden_states: bool Whether to return the hidden states, default is True.
|
| 141 |
+
do_sample: bool Whether to sample from the model, default is True.
|
| 142 |
+
temperature: float The temperature for the model, default is 0.7.
|
| 143 |
+
top_p: float The top-p for the model, default is 0.8.
|
| 144 |
+
top_k: int The top-k for the model, default is 100.
|
| 145 |
+
repetition_penalty: float, The repetition penalty for the model, default is 1.05.
|
| 146 |
+
length_penalty: float, The length penalty for the model, default is 1.0. Higher value means more detailed generation.
|
| 147 |
+
all_input_ids: Optional[torch.Tensor], The input ids for the current chunk.
|
| 148 |
+
"""
|
| 149 |
+
|
| 150 |
+
finished = False
|
| 151 |
+
current_inputs_embeds = inputs_embeds.clone()
|
| 152 |
+
input_last_hidden_states = []
|
| 153 |
+
last_hidden_states = []
|
| 154 |
+
generated_tokens = []
|
| 155 |
+
|
| 156 |
+
for token_idx in range(chunk_size):
|
| 157 |
+
if is_first_generate_chunk and token_idx == 0:
|
| 158 |
+
# first generate chunk, prefill inputs_embeds
|
| 159 |
+
model_inputs = {
|
| 160 |
+
"inputs_embeds": current_inputs_embeds,
|
| 161 |
+
"past_key_values": past_key_values,
|
| 162 |
+
"use_cache": True,
|
| 163 |
+
"output_hidden_states": return_hidden_states,
|
| 164 |
+
}
|
| 165 |
+
else: # for all other cases: prefill the latest generated token
|
| 166 |
+
model_inputs = {
|
| 167 |
+
"inputs_embeds": current_inputs_embeds[:, -1:, :],
|
| 168 |
+
"past_key_values": past_key_values,
|
| 169 |
+
"use_cache": True,
|
| 170 |
+
"output_hidden_states": return_hidden_states,
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
with torch.no_grad():
|
| 174 |
+
outputs = self.model(**model_inputs)
|
| 175 |
+
|
| 176 |
+
# last token's logits
|
| 177 |
+
logits = outputs.logits[:, -1, :].to(copy=True, dtype=torch.float32, device=inputs_embeds.device)
|
| 178 |
+
|
| 179 |
+
# forbid specific tokens decoding = model.generate@suppress_tokens
|
| 180 |
+
if self.forbidden_token_ids:
|
| 181 |
+
logits[:, self.forbidden_token_ids] = float("-inf")
|
| 182 |
+
|
| 183 |
+
past_key_values = outputs.past_key_values
|
| 184 |
+
|
| 185 |
+
PENALTY_WINDOW_SIZE = 128
|
| 186 |
+
|
| 187 |
+
# apply repetition penalty
|
| 188 |
+
if repetition_penalty != 1.0:
|
| 189 |
+
# get token ids for repetition penalty
|
| 190 |
+
if all_input_ids is not None:
|
| 191 |
+
# use global input ids (including original input and generated part)
|
| 192 |
+
if len(generated_tokens) > 0:
|
| 193 |
+
generated_token_ids = torch.cat(generated_tokens, dim=1)
|
| 194 |
+
current_sequence = torch.cat(
|
| 195 |
+
[
|
| 196 |
+
all_input_ids[:, -PENALTY_WINDOW_SIZE:],
|
| 197 |
+
generated_token_ids,
|
| 198 |
+
],
|
| 199 |
+
dim=1,
|
| 200 |
+
)
|
| 201 |
+
else:
|
| 202 |
+
current_sequence = all_input_ids[:, -PENALTY_WINDOW_SIZE:]
|
| 203 |
+
unique_token_ids = torch.unique(current_sequence.squeeze(0))
|
| 204 |
+
elif len(generated_tokens) > 0:
|
| 205 |
+
# revert to original logic: only use generated tokens
|
| 206 |
+
generated_token_ids = torch.cat(generated_tokens, dim=1).squeeze(0)
|
| 207 |
+
unique_token_ids = torch.unique(generated_token_ids)
|
| 208 |
+
else:
|
| 209 |
+
unique_token_ids = torch.tensor([], dtype=torch.long, device=logits.device)
|
| 210 |
+
|
| 211 |
+
# apply repetition penalty
|
| 212 |
+
for token_id in unique_token_ids:
|
| 213 |
+
if logits[0, token_id] > 0:
|
| 214 |
+
logits[0, token_id] = logits[0, token_id] / repetition_penalty
|
| 215 |
+
else:
|
| 216 |
+
logits[0, token_id] = logits[0, token_id] * repetition_penalty
|
| 217 |
+
|
| 218 |
+
# apply length penalty, higher value means more detailed generation
|
| 219 |
+
if length_penalty != 1.0:
|
| 220 |
+
for eos_token_id in self.terminators_ids:
|
| 221 |
+
if logits[0, eos_token_id] > 0:
|
| 222 |
+
logits[0, eos_token_id] = logits[0, eos_token_id] / length_penalty
|
| 223 |
+
else:
|
| 224 |
+
logits[0, eos_token_id] = logits[0, eos_token_id] * length_penalty
|
| 225 |
+
|
| 226 |
+
# apply temperature
|
| 227 |
+
if temperature != 1.0:
|
| 228 |
+
logits = logits / temperature
|
| 229 |
+
|
| 230 |
+
if do_sample:
|
| 231 |
+
# Top-k filtering
|
| 232 |
+
if top_k > 0:
|
| 233 |
+
top_k_logits, top_k_indices = torch.topk(logits, min(top_k, logits.size(-1)))
|
| 234 |
+
logits_filtered = torch.full_like(logits, float("-inf"))
|
| 235 |
+
logits_filtered.scatter_(1, top_k_indices, top_k_logits)
|
| 236 |
+
logits = logits_filtered
|
| 237 |
+
|
| 238 |
+
# Top-p filtering
|
| 239 |
+
if top_p < 1.0:
|
| 240 |
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
| 241 |
+
cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
|
| 242 |
+
|
| 243 |
+
# remove tokens with cumulative probability greater than top_p
|
| 244 |
+
sorted_indices_to_remove = cumulative_probs > top_p
|
| 245 |
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
| 246 |
+
sorted_indices_to_remove[..., 0] = 0
|
| 247 |
+
|
| 248 |
+
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
|
| 249 |
+
logits[indices_to_remove] = float("-inf")
|
| 250 |
+
|
| 251 |
+
# sampling
|
| 252 |
+
probs = F.softmax(logits, dim=-1)
|
| 253 |
+
next_token = torch.multinomial(probs, num_samples=1)
|
| 254 |
+
else:
|
| 255 |
+
next_token = torch.argmax(logits, dim=-1, keepdim=True)
|
| 256 |
+
|
| 257 |
+
if return_hidden_states:
|
| 258 |
+
if is_first_generate_chunk and token_idx == 0:
|
| 259 |
+
input_last_hidden_states.append(outputs.hidden_states[-1])
|
| 260 |
+
else:
|
| 261 |
+
last_hidden_states.append(outputs.hidden_states[-1])
|
| 262 |
+
|
| 263 |
+
# if terminator token, stop generating
|
| 264 |
+
if next_token.item() in self.terminators_ids:
|
| 265 |
+
finished = True
|
| 266 |
+
break
|
| 267 |
+
|
| 268 |
+
generated_tokens.append(next_token)
|
| 269 |
+
|
| 270 |
+
# convert new token to embeddings and concatenate
|
| 271 |
+
next_token_embed = self.embedding_layer(next_token)
|
| 272 |
+
|
| 273 |
+
# update inputs_embeds, add one
|
| 274 |
+
current_inputs_embeds = torch.cat([current_inputs_embeds, next_token_embed], dim=1)
|
| 275 |
+
|
| 276 |
+
if len(generated_tokens) > 0:
|
| 277 |
+
chunk_token_ids = torch.cat(generated_tokens, dim=1)
|
| 278 |
+
else:
|
| 279 |
+
# special case: if last chunk and first predict is eos token, return last token of previous chunk. return a tensor with shape (1, 0)
|
| 280 |
+
if finished:
|
| 281 |
+
chunk_token_ids = torch.zeros((1, 0), dtype=torch.long, device=current_inputs_embeds.device)
|
| 282 |
+
else:
|
| 283 |
+
raise Exception("this should not happen")
|
| 284 |
+
|
| 285 |
+
if len(last_hidden_states) > 0:
|
| 286 |
+
last_hidden_states = torch.cat(last_hidden_states, dim=1)
|
| 287 |
+
else:
|
| 288 |
+
# special case: if last chunk, return last token of previous chunk.
|
| 289 |
+
if finished:
|
| 290 |
+
last_hidden_states = torch.cat(last_hidden_states, dim=1)
|
| 291 |
+
else:
|
| 292 |
+
raise Exception("this should not happen")
|
| 293 |
+
|
| 294 |
+
if len(input_last_hidden_states) > 0:
|
| 295 |
+
input_last_hidden_states = torch.cat(input_last_hidden_states, dim=1)
|
| 296 |
+
else:
|
| 297 |
+
input_last_hidden_states = None
|
| 298 |
+
|
| 299 |
+
return GenerateChunkOutput(
|
| 300 |
+
chunk_token_ids=chunk_token_ids,
|
| 301 |
+
current_inputs_embeds=current_inputs_embeds,
|
| 302 |
+
input_last_hidden_states=input_last_hidden_states,
|
| 303 |
+
last_hidden_states=last_hidden_states,
|
| 304 |
+
past_key_values=past_key_values,
|
| 305 |
+
finished=finished,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
def streaming_token_decoder(token_iterator, tokenizer, skip_special_tokens=False):
|
| 310 |
+
"""
|
| 311 |
+
Incrementally decode tokens from an iterator, handling partial multi-byte characters.
|
| 312 |
+
|
| 313 |
+
When streaming tokens, multi-byte characters (like Chinese) may be split across multiple
|
| 314 |
+
tokens. Decoding partial tokens results in replacement characters (U+FFFD). This function
|
| 315 |
+
buffers tokens and only yields complete characters.
|
| 316 |
+
|
| 317 |
+
Args:
|
| 318 |
+
token_iterator: An iterator yielding (token_ids, is_finished) tuples.
|
| 319 |
+
token_ids can be torch.Tensor or any iterable of integers.
|
| 320 |
+
tokenizer: The tokenizer to use for decoding.
|
| 321 |
+
skip_special_tokens: Whether to skip special tokens during decoding.
|
| 322 |
+
|
| 323 |
+
Yields:
|
| 324 |
+
(decoded_text, is_finished) tuples where decoded_text is the new text since last yield.
|
| 325 |
+
"""
|
| 326 |
+
accumulated_token_ids = []
|
| 327 |
+
yielded_text_len = 0
|
| 328 |
+
|
| 329 |
+
for token_ids, is_finished in token_iterator:
|
| 330 |
+
# Accumulate token IDs
|
| 331 |
+
if torch.is_tensor(token_ids):
|
| 332 |
+
accumulated_token_ids.extend(token_ids.reshape(-1).tolist())
|
| 333 |
+
else:
|
| 334 |
+
accumulated_token_ids.extend(list(token_ids) if hasattr(token_ids, "__iter__") else [token_ids])
|
| 335 |
+
|
| 336 |
+
# Decode all accumulated tokens
|
| 337 |
+
full_decoded = tokenizer.decode(accumulated_token_ids, skip_special_tokens=skip_special_tokens)
|
| 338 |
+
|
| 339 |
+
if is_finished:
|
| 340 |
+
# Final chunk - yield all remaining text
|
| 341 |
+
new_text = full_decoded[yielded_text_len:]
|
| 342 |
+
yield new_text, is_finished
|
| 343 |
+
else:
|
| 344 |
+
# Find safe prefix without incomplete multi-byte characters
|
| 345 |
+
# The replacement character '�' (U+FFFD) indicates incomplete decoding
|
| 346 |
+
new_text = full_decoded[yielded_text_len:]
|
| 347 |
+
|
| 348 |
+
# Hold back text ending with replacement character (incomplete UTF-8 sequence)
|
| 349 |
+
safe_end = len(new_text)
|
| 350 |
+
while safe_end > 0 and new_text[safe_end - 1] == "\ufffd":
|
| 351 |
+
safe_end -= 1
|
| 352 |
+
|
| 353 |
+
safe_text = new_text[:safe_end] if safe_end > 0 else ""
|
| 354 |
+
yielded_text_len += len(safe_text)
|
| 355 |
+
yield safe_text, is_finished
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def torch_clone_recursive(obj):
|
| 359 |
+
"""Recursively clone nested containers of torch.Tensors.
|
| 360 |
+
|
| 361 |
+
Supported container types: dict, list, tuple. Non-container non-Tensor
|
| 362 |
+
objects are returned as-is.
|
| 363 |
+
"""
|
| 364 |
+
if torch.is_tensor(obj):
|
| 365 |
+
return obj.clone()
|
| 366 |
+
elif isinstance(obj, dict):
|
| 367 |
+
return {k: torch_clone_recursive(v) for k, v in obj.items()}
|
| 368 |
+
elif isinstance(obj, list):
|
| 369 |
+
return [torch_clone_recursive(v) for v in obj]
|
| 370 |
+
elif isinstance(obj, tuple):
|
| 371 |
+
return tuple(torch_clone_recursive(v) for v in obj)
|
| 372 |
+
else:
|
| 373 |
+
raise ValueError(f"Unsupported type: {type(obj)}")
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
def rotate_half(x: torch.Tensor) -> torch.Tensor:
|
| 377 |
+
"""Rotate half the hidden dims of the input for RoPE."""
|
| 378 |
+
dim = x.shape[-1]
|
| 379 |
+
x1 = x[..., : dim // 2]
|
| 380 |
+
x2 = x[..., dim // 2 :]
|
| 381 |
+
return torch.cat((-x2, x1), dim=-1)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
@dataclass
|
| 385 |
+
class SpeculativeSnapshot:
|
| 386 |
+
"""Speculative snapshot for VAD speculative rollback.
|
| 387 |
+
|
| 388 |
+
Used in VAD speculative execution: creates a snapshot after streaming_prefill
|
| 389 |
+
and before streaming_generate. If speculation fails (user continues speaking),
|
| 390 |
+
the state can be restored to continue streaming_prefill.
|
| 391 |
+
|
| 392 |
+
Implementation:
|
| 393 |
+
- LLM KV Cache: only record length, restore by truncation (zero extra VRAM)
|
| 394 |
+
- Audio KV Cache: requires cloning, as generate sets it to None
|
| 395 |
+
- Mel processor: save full state snapshot (including buffer)
|
| 396 |
+
"""
|
| 397 |
+
|
| 398 |
+
# KV Cache length (for truncation recovery)
|
| 399 |
+
llm_cache_length: int
|
| 400 |
+
audio_cache_length: int
|
| 401 |
+
|
| 402 |
+
# session state
|
| 403 |
+
new_user_msg: bool
|
| 404 |
+
llm_generated: bool
|
| 405 |
+
llm_generate_completed: bool
|
| 406 |
+
|
| 407 |
+
# Round management
|
| 408 |
+
next_round_id: int
|
| 409 |
+
pending_round_id: Optional[int]
|
| 410 |
+
omni_chunk_history_length: int
|
| 411 |
+
|
| 412 |
+
# TTS state (requires cloning, but usually small)
|
| 413 |
+
tts_last_turn_tokens: Optional[torch.Tensor]
|
| 414 |
+
|
| 415 |
+
# Streaming processor state
|
| 416 |
+
audio_chunk_idx: int
|
| 417 |
+
|
| 418 |
+
# Mel processor state snapshot (including buffer)
|
| 419 |
+
mel_processor_snapshot: Optional[dict] = None
|
| 420 |
+
|
| 421 |
+
# Audio encoder KV cache (requires cloning to ensure determinism after recovery)
|
| 422 |
+
audio_past_key_values: Optional[tuple] = None
|
| 423 |
+
|
| 424 |
+
# timestamp (for debugging)
|
| 425 |
+
timestamp: float = 0.0
|
| 426 |
+
|
| 427 |
+
# debug field: for verifying correctness of recovery
|
| 428 |
+
llm_cache_checksum: Optional[float] = None # LLM KV Cache first layer K sum
|
| 429 |
+
audio_cache_checksum: Optional[float] = None # Audio KV Cache first layer K sum
|
| 430 |
+
mel_buffer_checksum: Optional[float] = None # Mel buffer sum
|
| 431 |
+
|
| 432 |
+
# RNG state (key: for ensuring determinism of dithering etc. after recovery)
|
| 433 |
+
rng_state_cpu: Optional[torch.Tensor] = None # torch CPU RNG state
|
| 434 |
+
rng_state_cuda: Optional[torch.Tensor] = None # torch CUDA RNG state (if on GPU)
|
| 435 |
+
|
| 436 |
+
def summary(self) -> str:
|
| 437 |
+
mel_buf_len = 0
|
| 438 |
+
if self.mel_processor_snapshot:
|
| 439 |
+
buf = self.mel_processor_snapshot.get("buffer")
|
| 440 |
+
if buf is not None:
|
| 441 |
+
mel_buf_len = len(buf)
|
| 442 |
+
return (
|
| 443 |
+
f"llm_cache={self.llm_cache_length}, "
|
| 444 |
+
f"audio_cache={self.audio_cache_length}, "
|
| 445 |
+
f"audio_chunk_idx={self.audio_chunk_idx}, "
|
| 446 |
+
f"mel_buffer={mel_buf_len}, "
|
| 447 |
+
f"history_len={self.omni_chunk_history_length}, "
|
| 448 |
+
f"new_user_msg={self.new_user_msg}, "
|
| 449 |
+
f"llm_generated={self.llm_generated}"
|
| 450 |
+
)
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
# tts
|
| 454 |
+
@dataclass
|
| 455 |
+
class TTSSamplingParams:
|
| 456 |
+
top_p: float = 0.85
|
| 457 |
+
min_p: float = 0.01
|
| 458 |
+
top_k: int = 25
|
| 459 |
+
repetition_penalty: float = 1.05
|
| 460 |
+
temperature: float = 0.8
|
| 461 |
+
win_size: int = 16
|
| 462 |
+
tau_r: float = 0.1
|
| 463 |
+
|
| 464 |
+
|
| 465 |
+
class TTSStreamingGenerator:
|
| 466 |
+
"""
|
| 467 |
+
Streaming generator for TTS that processes chunks and yields audio tokens in real-time.
|
| 468 |
+
|
| 469 |
+
Supported attention types:
|
| 470 |
+
- full_attention: Full attention, all tokens can attend to each other
|
| 471 |
+
- sliding_window: Sliding window attention, KV cache is truncated to fixed size (token_window_size)
|
| 472 |
+
- sliding_recompute: Sliding recompute, only keep previous chunk and recompute with current chunk
|
| 473 |
+
- reindex: Keep first chunk as sink, reindex sliding window positions via RoPE rotation
|
| 474 |
+
"""
|
| 475 |
+
|
| 476 |
+
def __init__(
|
| 477 |
+
self,
|
| 478 |
+
model,
|
| 479 |
+
temperature: float,
|
| 480 |
+
eos_token: Union[int, torch.Tensor],
|
| 481 |
+
chunk_size: int = 25, # s3tokenizer 1s = 25token
|
| 482 |
+
tts_last_turn_tokens: torch.Tensor = None,
|
| 483 |
+
logits_processors=None,
|
| 484 |
+
logits_warpers=None,
|
| 485 |
+
):
|
| 486 |
+
self.tts = model
|
| 487 |
+
self.device = model.device
|
| 488 |
+
self.temperature = torch.tensor([temperature], dtype=torch.float, device=self.device)
|
| 489 |
+
self.eos_token = (
|
| 490 |
+
torch.tensor(eos_token, device=self.device) if isinstance(eos_token, int) else eos_token.to(self.device)
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
self.num_vq = model.num_vq
|
| 494 |
+
self.num_audio_tokens = model.num_audio_tokens
|
| 495 |
+
self.recomputed_chunks = model.recomputed_chunks
|
| 496 |
+
self.emb_code = model.emb_code
|
| 497 |
+
self.head_code = model.head_code
|
| 498 |
+
|
| 499 |
+
# Attention type and window sizes
|
| 500 |
+
self.attention_type = model.attention_type # "full_attention", "sliding_window", "sliding_recompute", "reindex"
|
| 501 |
+
self.chunk_window_size = model.chunk_window_size # chunk-level window for sliding_recompute (default 2)
|
| 502 |
+
self.token_window_size = model.token_window_size # token-level window for sliding_window/reindex (default 300)
|
| 503 |
+
|
| 504 |
+
# RoPE config (for reindex mode)
|
| 505 |
+
self.rope_theta = model.model.config.rope_theta
|
| 506 |
+
self.head_dim = model.model.config.hidden_size // model.model.config.num_attention_heads
|
| 507 |
+
|
| 508 |
+
# Logits processors
|
| 509 |
+
self.logits_processors = logits_processors if logits_processors is not None else []
|
| 510 |
+
# Logits warpers (like TopP/TopK), separate from processors
|
| 511 |
+
self.logits_warpers = logits_warpers if logits_warpers is not None else []
|
| 512 |
+
|
| 513 |
+
# initialize state
|
| 514 |
+
self.past_key_values = None
|
| 515 |
+
self.text_start_pos = 0
|
| 516 |
+
self.idx = -1 # start from -1, become 0 when first called
|
| 517 |
+
self.all_conditions = []
|
| 518 |
+
self.all_generated_tokens = []
|
| 519 |
+
self.tts_last_turn_tokens = tts_last_turn_tokens
|
| 520 |
+
self.spk_emb = None
|
| 521 |
+
|
| 522 |
+
audio_bos = [self.tts.audio_bos_token_id]
|
| 523 |
+
audio_bos = torch.Tensor(audio_bos).to(self.tts.emb_text.weight.device, dtype=torch.long)
|
| 524 |
+
|
| 525 |
+
self.audio_bos_embeds = self.tts.emb_text(audio_bos).unsqueeze(0)
|
| 526 |
+
self.text_eos_embed = self.tts.emb_text(
|
| 527 |
+
torch.tensor(
|
| 528 |
+
[self.tts.config.text_eos_token_id],
|
| 529 |
+
device=self.tts.emb_text.weight.device,
|
| 530 |
+
dtype=torch.long,
|
| 531 |
+
)
|
| 532 |
+
).unsqueeze(0)
|
| 533 |
+
|
| 534 |
+
# buffer related, used to fill up chunk_size and yield to outside
|
| 535 |
+
self.chunk_size = chunk_size
|
| 536 |
+
self._token_buffer: List[torch.Tensor] = []
|
| 537 |
+
|
| 538 |
+
# Chunk info tracking for sliding_recompute and reindex
|
| 539 |
+
self._chunk_info: List[dict] = []
|
| 540 |
+
self._total_seq_len = 0
|
| 541 |
+
|
| 542 |
+
# Reindex mode: track sink (first chunk) length
|
| 543 |
+
self._sink_kv_len = 0
|
| 544 |
+
|
| 545 |
+
def _build_recompute_inputs(self, current_condition: torch.Tensor) -> torch.Tensor:
|
| 546 |
+
"""Build recompute inputs for sliding_recompute mode."""
|
| 547 |
+
if len(self._chunk_info) == 0:
|
| 548 |
+
return current_condition
|
| 549 |
+
|
| 550 |
+
prev_chunk = self._chunk_info[-1]
|
| 551 |
+
prev_condition = prev_chunk["condition"]
|
| 552 |
+
prev_audio_tokens = prev_chunk["audio_tokens"]
|
| 553 |
+
|
| 554 |
+
recompute_list = [prev_condition]
|
| 555 |
+
if len(prev_audio_tokens) > 0:
|
| 556 |
+
prev_audio_embeds = torch.cat([self.emb_code[0](tok) for tok in prev_audio_tokens], dim=1)
|
| 557 |
+
recompute_list.append(prev_audio_embeds)
|
| 558 |
+
|
| 559 |
+
recompute_list.append(current_condition)
|
| 560 |
+
return torch.cat(recompute_list, dim=1)
|
| 561 |
+
|
| 562 |
+
def _truncate_kv_cache_sliding_window(self):
|
| 563 |
+
"""Truncate KV cache for sliding_window mode."""
|
| 564 |
+
if self.past_key_values is None:
|
| 565 |
+
return
|
| 566 |
+
|
| 567 |
+
if hasattr(self.past_key_values, "get_seq_length"):
|
| 568 |
+
current_kv_len = self.past_key_values.get_seq_length()
|
| 569 |
+
else:
|
| 570 |
+
current_kv_len = self.past_key_values[0][0].shape[2]
|
| 571 |
+
|
| 572 |
+
if current_kv_len <= self.token_window_size:
|
| 573 |
+
return
|
| 574 |
+
|
| 575 |
+
new_cache = DynamicCache()
|
| 576 |
+
num_layers = (
|
| 577 |
+
len(self.past_key_values.key_cache)
|
| 578 |
+
if hasattr(self.past_key_values, "key_cache")
|
| 579 |
+
else len(self.past_key_values)
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
for layer_idx in range(num_layers):
|
| 583 |
+
if hasattr(self.past_key_values, "key_cache"):
|
| 584 |
+
key = self.past_key_values.key_cache[layer_idx][:, :, -self.token_window_size :, :]
|
| 585 |
+
value = self.past_key_values.value_cache[layer_idx][:, :, -self.token_window_size :, :]
|
| 586 |
+
else:
|
| 587 |
+
key = self.past_key_values[layer_idx][0][:, :, -self.token_window_size :, :]
|
| 588 |
+
value = self.past_key_values[layer_idx][1][:, :, -self.token_window_size :, :]
|
| 589 |
+
new_cache.update(key, value, layer_idx)
|
| 590 |
+
|
| 591 |
+
self.past_key_values = new_cache
|
| 592 |
+
|
| 593 |
+
@staticmethod
|
| 594 |
+
def _apply_rope_rotation(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
|
| 595 |
+
"""Apply RoPE rotation to tensor."""
|
| 596 |
+
return x * cos + rotate_half(x) * sin
|
| 597 |
+
|
| 598 |
+
def _compute_rope_cos_sin(self, positions: torch.Tensor, device: torch.device, dtype: torch.dtype):
|
| 599 |
+
"""Compute RoPE cos and sin for given positions."""
|
| 600 |
+
dim_half = self.head_dim // 2
|
| 601 |
+
freq_seq = torch.arange(0, dim_half, dtype=torch.float32, device=device)
|
| 602 |
+
inv_freq = 1.0 / (self.rope_theta ** (freq_seq / dim_half))
|
| 603 |
+
|
| 604 |
+
# positions: [seq_len]
|
| 605 |
+
angles = positions.float().unsqueeze(-1) * inv_freq.unsqueeze(0) # [seq_len, dim_half]
|
| 606 |
+
angles = torch.cat([angles, angles], dim=-1) # [seq_len, head_dim]
|
| 607 |
+
|
| 608 |
+
cos = angles.cos().to(dtype)
|
| 609 |
+
sin = angles.sin().to(dtype)
|
| 610 |
+
return cos, sin
|
| 611 |
+
|
| 612 |
+
def _reindex_kv_cache(self):
|
| 613 |
+
"""
|
| 614 |
+
Reindex KV cache for reindex mode:
|
| 615 |
+
1. Keep first chunk as attention sink
|
| 616 |
+
2. Keep last chunk
|
| 617 |
+
3. Discard middle chunks
|
| 618 |
+
4. Reindex the last chunk's key positions to be right after sink via RoPE rotation
|
| 619 |
+
"""
|
| 620 |
+
if self.past_key_values is None or len(self._chunk_info) < 2:
|
| 621 |
+
return
|
| 622 |
+
|
| 623 |
+
# Get current KV cache length
|
| 624 |
+
if hasattr(self.past_key_values, "get_seq_length"):
|
| 625 |
+
current_kv_len = self.past_key_values.get_seq_length()
|
| 626 |
+
else:
|
| 627 |
+
current_kv_len = self.past_key_values[0][0].shape[2]
|
| 628 |
+
|
| 629 |
+
# Calculate sink length (first chunk)
|
| 630 |
+
sink_len = self._chunk_info[0]["condition_len"] + self._chunk_info[0]["audio_token_count"]
|
| 631 |
+
|
| 632 |
+
# Last chunk length
|
| 633 |
+
last_chunk = self._chunk_info[-1]
|
| 634 |
+
last_chunk_len = last_chunk["condition_len"] + last_chunk["audio_token_count"]
|
| 635 |
+
|
| 636 |
+
keep_len = sink_len + last_chunk_len
|
| 637 |
+
|
| 638 |
+
# Get device and dtype
|
| 639 |
+
device = self.past_key_values.key_cache[0].device
|
| 640 |
+
dtype = self.past_key_values.key_cache[0].dtype
|
| 641 |
+
|
| 642 |
+
if current_kv_len <= keep_len:
|
| 643 |
+
last_chunk_kv_len = current_kv_len - sink_len
|
| 644 |
+
if last_chunk_kv_len <= 0:
|
| 645 |
+
return
|
| 646 |
+
self.text_start_pos = current_kv_len
|
| 647 |
+
return
|
| 648 |
+
|
| 649 |
+
# Step 1: Truncate KV cache - keep sink and last chunk
|
| 650 |
+
new_cache = DynamicCache()
|
| 651 |
+
num_layers = len(self.past_key_values.key_cache)
|
| 652 |
+
|
| 653 |
+
original_start_pos = current_kv_len - last_chunk_len
|
| 654 |
+
new_start_pos = sink_len
|
| 655 |
+
delta = new_start_pos - original_start_pos # This is a scalar constant
|
| 656 |
+
delta_positions = torch.full((last_chunk_len,), delta, dtype=torch.float32, device=device)
|
| 657 |
+
|
| 658 |
+
# Compute rotation cos/sin
|
| 659 |
+
cos, sin = self._compute_rope_cos_sin(delta_positions, device, dtype)
|
| 660 |
+
cos = cos.unsqueeze(0).unsqueeze(0) # [1, 1, seq_len, head_dim]
|
| 661 |
+
sin = sin.unsqueeze(0).unsqueeze(0)
|
| 662 |
+
|
| 663 |
+
for layer_idx in range(num_layers):
|
| 664 |
+
key_full = self.past_key_values.key_cache[layer_idx]
|
| 665 |
+
value_full = self.past_key_values.value_cache[layer_idx]
|
| 666 |
+
|
| 667 |
+
# Extract sink and last chunk
|
| 668 |
+
key_sink = key_full[:, :, :sink_len, :]
|
| 669 |
+
value_sink = value_full[:, :, :sink_len, :]
|
| 670 |
+
key_last = key_full[:, :, -last_chunk_len:, :]
|
| 671 |
+
value_last = value_full[:, :, -last_chunk_len:, :]
|
| 672 |
+
|
| 673 |
+
# Apply RoPE rotation to reindex key positions
|
| 674 |
+
key_last_reindexed = self._apply_rope_rotation(key_last, cos, sin)
|
| 675 |
+
|
| 676 |
+
# Concatenate sink and reindexed last chunk
|
| 677 |
+
key = torch.cat([key_sink, key_last_reindexed], dim=2)
|
| 678 |
+
value = torch.cat([value_sink, value_last], dim=2)
|
| 679 |
+
|
| 680 |
+
new_cache.update(key, value, layer_idx)
|
| 681 |
+
|
| 682 |
+
self.past_key_values = new_cache
|
| 683 |
+
|
| 684 |
+
# Update text_start_pos to reflect new positions
|
| 685 |
+
self.text_start_pos = sink_len + last_chunk_len
|
| 686 |
+
|
| 687 |
+
@torch.inference_mode()
|
| 688 |
+
def generate_with_buffer(
|
| 689 |
+
self,
|
| 690 |
+
condition: torch.Tensor,
|
| 691 |
+
text_finished: bool = False,
|
| 692 |
+
max_new_token: int = 500,
|
| 693 |
+
):
|
| 694 |
+
"""input a condition embedding chunk, generate audio token each time,
|
| 695 |
+
and accumulate to buffer, only yield when buffer satisfies chunk_size.
|
| 696 |
+
|
| 697 |
+
Yields:
|
| 698 |
+
torch.Tensor of shape [chunk_size] (2D: [1, chunk_size])
|
| 699 |
+
"""
|
| 700 |
+
self.idx += 1
|
| 701 |
+
self.device = self.tts.device
|
| 702 |
+
|
| 703 |
+
# if text finished, first concatenate Text EOS
|
| 704 |
+
if text_finished:
|
| 705 |
+
condition = torch.cat([condition, self.text_eos_embed], dim=1)
|
| 706 |
+
|
| 707 |
+
# always concatenate Audio BOS
|
| 708 |
+
condition = torch.cat([condition, self.audio_bos_embeds], dim=1).to(self.device)
|
| 709 |
+
|
| 710 |
+
self.all_conditions.append(condition)
|
| 711 |
+
|
| 712 |
+
# Initialize current chunk info
|
| 713 |
+
current_chunk_info = {
|
| 714 |
+
"condition_len": condition.shape[1],
|
| 715 |
+
"audio_token_count": 0,
|
| 716 |
+
"condition": condition.clone(),
|
| 717 |
+
"audio_tokens": [],
|
| 718 |
+
}
|
| 719 |
+
|
| 720 |
+
# Handle different attention types
|
| 721 |
+
if self.attention_type == "sliding_recompute" and self.idx >= 1:
|
| 722 |
+
# sliding_recompute: discard KV cache, recompute with previous + current chunk
|
| 723 |
+
self.past_key_values = None
|
| 724 |
+
current_condition = self._build_recompute_inputs(condition)
|
| 725 |
+
self.text_start_pos = 0
|
| 726 |
+
elif self.attention_type == "reindex" and self.idx >= 1:
|
| 727 |
+
# reindex: truncate KV cache keeping sink + last chunk, reindex positions via RoPE
|
| 728 |
+
self._reindex_kv_cache()
|
| 729 |
+
current_condition = condition
|
| 730 |
+
# Always update text_start_pos based on actual KV cache length (like reference code)
|
| 731 |
+
if self.past_key_values is not None:
|
| 732 |
+
if hasattr(self.past_key_values, "get_seq_length"):
|
| 733 |
+
kv_len = self.past_key_values.get_seq_length()
|
| 734 |
+
else:
|
| 735 |
+
kv_len = self.past_key_values[0][0].shape[2]
|
| 736 |
+
self.text_start_pos = kv_len
|
| 737 |
+
else:
|
| 738 |
+
current_condition = condition
|
| 739 |
+
|
| 740 |
+
condition_length = current_condition.shape[1]
|
| 741 |
+
prefill_len = condition_length
|
| 742 |
+
finished = torch.zeros(1, dtype=torch.bool, device=self.device)
|
| 743 |
+
chunk_generated_tokens = []
|
| 744 |
+
|
| 745 |
+
for t in range(max_new_token):
|
| 746 |
+
if t == 0:
|
| 747 |
+
inputs_embeds = current_condition
|
| 748 |
+
pos_ids = torch.arange(
|
| 749 |
+
self.text_start_pos,
|
| 750 |
+
self.text_start_pos + condition_length,
|
| 751 |
+
dtype=torch.long,
|
| 752 |
+
device=self.device,
|
| 753 |
+
).unsqueeze(0)
|
| 754 |
+
else:
|
| 755 |
+
last = self.all_generated_tokens[-1]
|
| 756 |
+
# last: [1,1], directly as code id
|
| 757 |
+
inputs_embeds = self.emb_code[0](last)
|
| 758 |
+
pos_ids = torch.tensor(
|
| 759 |
+
[self.text_start_pos + prefill_len + t - 1],
|
| 760 |
+
dtype=torch.long,
|
| 761 |
+
device=self.device,
|
| 762 |
+
).unsqueeze(0)
|
| 763 |
+
|
| 764 |
+
outputs = self.tts.model(
|
| 765 |
+
position_ids=pos_ids,
|
| 766 |
+
past_key_values=self.past_key_values,
|
| 767 |
+
inputs_embeds=inputs_embeds,
|
| 768 |
+
use_cache=True,
|
| 769 |
+
)
|
| 770 |
+
hidden_states = outputs.last_hidden_state
|
| 771 |
+
|
| 772 |
+
# Handle KV cache based on attention type
|
| 773 |
+
if self.attention_type == "sliding_window":
|
| 774 |
+
self.past_key_values = outputs.past_key_values
|
| 775 |
+
self._truncate_kv_cache_sliding_window()
|
| 776 |
+
else:
|
| 777 |
+
self.past_key_values = outputs.past_key_values
|
| 778 |
+
|
| 779 |
+
with P.cached():
|
| 780 |
+
logits = torch.empty(
|
| 781 |
+
hidden_states.size(0),
|
| 782 |
+
hidden_states.size(1),
|
| 783 |
+
self.num_audio_tokens,
|
| 784 |
+
self.num_vq,
|
| 785 |
+
dtype=torch.float,
|
| 786 |
+
device=self.device,
|
| 787 |
+
)
|
| 788 |
+
for num_vq_iter in range(self.num_vq):
|
| 789 |
+
x: torch.Tensor = self.head_code[num_vq_iter](hidden_states)
|
| 790 |
+
logits[..., num_vq_iter] = x
|
| 791 |
+
del x
|
| 792 |
+
|
| 793 |
+
del hidden_states
|
| 794 |
+
|
| 795 |
+
logits = logits[:, -1].float()
|
| 796 |
+
|
| 797 |
+
logits = logits.permute(0, 2, 1)
|
| 798 |
+
logits = logits.reshape(-1, logits.size(2))
|
| 799 |
+
|
| 800 |
+
logits /= self.temperature
|
| 801 |
+
|
| 802 |
+
audio_bos = len(self.all_generated_tokens) == 0 and t == 0
|
| 803 |
+
|
| 804 |
+
if not audio_bos:
|
| 805 |
+
# use generated tokens (current chunk) as input for processor/warper (align with modeling_minicpmo)
|
| 806 |
+
all_generated_tokens = torch.cat(self.all_generated_tokens, dim=1).to(self.device) # [1, T]
|
| 807 |
+
for processor in self.logits_processors:
|
| 808 |
+
logits = processor(all_generated_tokens, logits)
|
| 809 |
+
|
| 810 |
+
for warper in self.logits_warpers:
|
| 811 |
+
logits = warper(all_generated_tokens, logits)
|
| 812 |
+
del all_generated_tokens
|
| 813 |
+
|
| 814 |
+
# sample next token (only use first codebook, same as generate)
|
| 815 |
+
scores = F.softmax(logits, dim=-1)
|
| 816 |
+
idx_next = torch.multinomial(scores, num_samples=1) # [(B*num_vq), 1]
|
| 817 |
+
next_id = idx_next.view(-1, self.num_vq)[:, 0:1] # only take first codebook → [B, 1]
|
| 818 |
+
del scores
|
| 819 |
+
|
| 820 |
+
if next_id.eq(
|
| 821 |
+
self.eos_token
|
| 822 |
+
).any(): # generated audio eos token, means this chunk is finished, no longer generate new tokens
|
| 823 |
+
finished[:] = True
|
| 824 |
+
else: # eos token cannot be added to buffer, he does not speak.
|
| 825 |
+
# convert next_id to correct shape [1, 1], no num_vq dimension
|
| 826 |
+
if next_id.dim() == 0: # if scalar
|
| 827 |
+
next_tok = next_id.unsqueeze(0).unsqueeze(0) # [1, 1]
|
| 828 |
+
elif next_id.dim() == 1: # if 1D [1]
|
| 829 |
+
next_tok = next_id.unsqueeze(0) # [1, 1]
|
| 830 |
+
else:
|
| 831 |
+
next_tok = next_id
|
| 832 |
+
|
| 833 |
+
self.all_generated_tokens.append(next_tok)
|
| 834 |
+
chunk_generated_tokens.append(next_tok)
|
| 835 |
+
|
| 836 |
+
# Update chunk info for sliding_recompute
|
| 837 |
+
current_chunk_info["audio_tokens"].append(next_tok.clone())
|
| 838 |
+
current_chunk_info["audio_token_count"] += 1
|
| 839 |
+
|
| 840 |
+
self._token_buffer.append(next_tok)
|
| 841 |
+
|
| 842 |
+
if len(self._token_buffer) == 0:
|
| 843 |
+
# case 1: if last text chunk, yield None
|
| 844 |
+
if text_finished:
|
| 845 |
+
yield torch.empty(1, 0, dtype=torch.long, device=self.device), True
|
| 846 |
+
break
|
| 847 |
+
# case 2: if not last text chunk, break directly
|
| 848 |
+
else:
|
| 849 |
+
break
|
| 850 |
+
else: # buffer has something
|
| 851 |
+
# case 1: if buffer is larger/equal to chunk_size, yield out
|
| 852 |
+
if len(self._token_buffer) >= self.chunk_size:
|
| 853 |
+
batch = torch.cat(self._token_buffer[: self.chunk_size], dim=1) # [1, chunk_size]
|
| 854 |
+
yield batch, False # → [1, chunk_size]
|
| 855 |
+
# discard yielded part
|
| 856 |
+
self._token_buffer = self._token_buffer[self.chunk_size :]
|
| 857 |
+
|
| 858 |
+
# case 2: if buffer is smaller than chunk_size
|
| 859 |
+
else:
|
| 860 |
+
# if generation finished, and is the last text chunk, yield all remaining tokens, then break
|
| 861 |
+
if finished.all():
|
| 862 |
+
if text_finished:
|
| 863 |
+
batch = torch.cat(self._token_buffer, dim=1) # [1, chunk_size]
|
| 864 |
+
yield batch, True # → [1, chunk_size]
|
| 865 |
+
self._token_buffer = []
|
| 866 |
+
break
|
| 867 |
+
else:
|
| 868 |
+
# not the last text chunk, need to wait for next text chunk to fill up buffer, then this call ends
|
| 869 |
+
break
|
| 870 |
+
else: # generation of this audio chunk is not finished, continue generating
|
| 871 |
+
continue
|
| 872 |
+
|
| 873 |
+
# Save current chunk info for sliding_recompute and reindex
|
| 874 |
+
self._chunk_info.append(current_chunk_info)
|
| 875 |
+
self._total_seq_len += condition.shape[1] + len(chunk_generated_tokens)
|
| 876 |
+
|
| 877 |
+
# Update text_start_pos based on attention type
|
| 878 |
+
if self.attention_type == "sliding_recompute":
|
| 879 |
+
# sliding_recompute: will be reset at next chunk start, update normally here
|
| 880 |
+
self.text_start_pos += prefill_len + len(chunk_generated_tokens)
|
| 881 |
+
elif self.attention_type == "reindex":
|
| 882 |
+
# reindex: position based on actual KV cache length (positions have been reindexed to be continuous)
|
| 883 |
+
if self.past_key_values is not None:
|
| 884 |
+
if hasattr(self.past_key_values, "get_seq_length"):
|
| 885 |
+
self.text_start_pos = self.past_key_values.get_seq_length()
|
| 886 |
+
else:
|
| 887 |
+
self.text_start_pos = self.past_key_values[0][0].shape[2]
|
| 888 |
+
else:
|
| 889 |
+
self.text_start_pos += condition.shape[1] + len(chunk_generated_tokens)
|
| 890 |
+
else:
|
| 891 |
+
self.text_start_pos += condition.shape[1] + len(chunk_generated_tokens)
|
| 892 |
+
# note: remaining tokens in buffer will be kept, and accumulated next time
|
| 893 |
+
|
| 894 |
+
|
| 895 |
+
# sliding window
|
| 896 |
+
@dataclass
|
| 897 |
+
class StreamingWindowConfig:
|
| 898 |
+
text_window_high_tokens: int = 8000
|
| 899 |
+
text_window_low_tokens: int = 6000
|
| 900 |
+
|
| 901 |
+
|
| 902 |
+
@dataclass
|
| 903 |
+
class DuplexWindowConfig:
|
| 904 |
+
"""duplex sliding window configuration
|
| 905 |
+
|
| 906 |
+
sliding window mode:
|
| 907 |
+
- "off": disable sliding window
|
| 908 |
+
- "basic": basic sliding window (trigger by cache length)
|
| 909 |
+
- "context": sliding window with context (trigger by unit number, preserve generated text to previous)
|
| 910 |
+
"""
|
| 911 |
+
|
| 912 |
+
# sliding window mode
|
| 913 |
+
sliding_window_mode: str = "off" # "off" / "basic" / "context"
|
| 914 |
+
|
| 915 |
+
# basic sliding window parameters
|
| 916 |
+
basic_window_high_tokens: int = 8000 # high watermark: trigger sliding window when exceeded
|
| 917 |
+
basic_window_low_tokens: int = 6000 # low watermark: keep to this value after sliding window
|
| 918 |
+
|
| 919 |
+
# context sliding window parameters
|
| 920 |
+
context_previous_max_tokens: int = 500 # previous maximum token number
|
| 921 |
+
context_max_units: int = 24 # maximum unit number (trigger sliding window when exceeded)
|
| 922 |
+
|
| 923 |
+
# verification mode (for comparison test)
|
| 924 |
+
verify_mode: bool = False # whether to enable verification log
|
| 925 |
+
|
| 926 |
+
|
| 927 |
+
def as_dynamic_cache(past_key_values):
|
| 928 |
+
"""Convert legacy tuple cache to DynamicCache if needed."""
|
| 929 |
+
if isinstance(past_key_values, DynamicCache):
|
| 930 |
+
return past_key_values
|
| 931 |
+
|
| 932 |
+
if isinstance(past_key_values, tuple):
|
| 933 |
+
return DynamicCache.from_legacy_cache(past_key_values)
|
| 934 |
+
|
| 935 |
+
return past_key_values
|
| 936 |
+
|
| 937 |
+
|
| 938 |
+
def get_kv_cache_length(cache) -> int:
|
| 939 |
+
"""Get the sequence length of a KV cache.
|
| 940 |
+
|
| 941 |
+
Args:
|
| 942 |
+
cache: DynamicCache or tuple-based cache
|
| 943 |
+
|
| 944 |
+
Returns:
|
| 945 |
+
The number of tokens in the cache
|
| 946 |
+
"""
|
| 947 |
+
if cache is None:
|
| 948 |
+
return 0
|
| 949 |
+
|
| 950 |
+
if isinstance(cache, DynamicCache):
|
| 951 |
+
if not cache.key_cache or not cache.key_cache[0].numel():
|
| 952 |
+
return 0
|
| 953 |
+
return cache.key_cache[0].shape[-2]
|
| 954 |
+
|
| 955 |
+
if isinstance(cache, tuple):
|
| 956 |
+
return cache[0][0].shape[2]
|
| 957 |
+
|
| 958 |
+
return 0
|
| 959 |
+
|
| 960 |
+
|
| 961 |
+
def get_rotary_cos_sin(
|
| 962 |
+
head_dim: int,
|
| 963 |
+
positions: torch.Tensor,
|
| 964 |
+
device: torch.device,
|
| 965 |
+
dtype: torch.dtype,
|
| 966 |
+
rope_theta: float = 10000.0,
|
| 967 |
+
inv_freq_cache: Optional[Dict[Tuple, torch.Tensor]] = None,
|
| 968 |
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
| 969 |
+
"""Compute RoPE cos and sin components for given positions.
|
| 970 |
+
|
| 971 |
+
Args:
|
| 972 |
+
head_dim: Dimension of each attention head
|
| 973 |
+
positions: Position indices tensor
|
| 974 |
+
device: Target device
|
| 975 |
+
dtype: Target dtype
|
| 976 |
+
rope_theta: RoPE base frequency (default 10000.0)
|
| 977 |
+
inv_freq_cache: Optional cache dict for inverse frequencies
|
| 978 |
+
|
| 979 |
+
Returns:
|
| 980 |
+
Tuple of (cos, sin) tensors with shape [1, 1, seq_len, head_dim]
|
| 981 |
+
"""
|
| 982 |
+
cache_key = (head_dim, device)
|
| 983 |
+
|
| 984 |
+
inv_freq = inv_freq_cache.get(cache_key) if inv_freq_cache is not None else None
|
| 985 |
+
if inv_freq is None or inv_freq.device != device or inv_freq.shape[0] != head_dim // 2:
|
| 986 |
+
exponent = torch.arange(0, head_dim, 2, device=device, dtype=torch.float32) / head_dim
|
| 987 |
+
inv_freq = 1.0 / (rope_theta**exponent)
|
| 988 |
+
if inv_freq_cache is not None:
|
| 989 |
+
inv_freq_cache[cache_key] = inv_freq
|
| 990 |
+
|
| 991 |
+
positions = positions.to(device=device, dtype=torch.float32)
|
| 992 |
+
angles = torch.einsum("i,j->ij", positions, inv_freq)
|
| 993 |
+
cos = torch.cos(angles)
|
| 994 |
+
sin = torch.sin(angles)
|
| 995 |
+
|
| 996 |
+
# Use cat instead of repeat_interleave, consistent with model's original RotaryEmbedding
|
| 997 |
+
# Original: emb = torch.cat((freqs, freqs), dim=-1) -> [f0, f1, ..., f_{d/2}, f0, f1, ..., f_{d/2}]
|
| 998 |
+
cos_full = torch.cat([cos, cos], dim=-1).to(dtype=dtype)
|
| 999 |
+
sin_full = torch.cat([sin, sin], dim=-1).to(dtype=dtype)
|
| 1000 |
+
cos_full = cos_full.unsqueeze(0).unsqueeze(0)
|
| 1001 |
+
sin_full = sin_full.unsqueeze(0).unsqueeze(0)
|
| 1002 |
+
return cos_full, sin_full
|
| 1003 |
+
|
| 1004 |
+
|
| 1005 |
+
def realign_rotary_suffix(
|
| 1006 |
+
suffix_keys: torch.Tensor,
|
| 1007 |
+
old_positions: torch.Tensor,
|
| 1008 |
+
new_positions: torch.Tensor,
|
| 1009 |
+
rope_theta: float = 10000.0,
|
| 1010 |
+
inv_freq_cache: Optional[Dict[Tuple, torch.Tensor]] = None,
|
| 1011 |
+
) -> torch.Tensor:
|
| 1012 |
+
"""Realign RoPE position encoding after cache eviction.
|
| 1013 |
+
|
| 1014 |
+
When tokens are dropped from the middle of a cache, the suffix tokens
|
| 1015 |
+
need their RoPE embeddings recalculated with new position indices.
|
| 1016 |
+
|
| 1017 |
+
Args:
|
| 1018 |
+
suffix_keys: Key tensor to realign, shape [batch, heads, seq_len, head_dim]
|
| 1019 |
+
old_positions: Original position indices
|
| 1020 |
+
new_positions: New position indices after eviction
|
| 1021 |
+
rope_theta: RoPE base frequency
|
| 1022 |
+
inv_freq_cache: Optional cache dict for inverse frequencies
|
| 1023 |
+
|
| 1024 |
+
Returns:
|
| 1025 |
+
Realigned key tensor with same shape as input
|
| 1026 |
+
"""
|
| 1027 |
+
if suffix_keys.numel() == 0:
|
| 1028 |
+
return suffix_keys
|
| 1029 |
+
|
| 1030 |
+
head_dim = suffix_keys.shape[-1]
|
| 1031 |
+
device = suffix_keys.device
|
| 1032 |
+
dtype = suffix_keys.dtype
|
| 1033 |
+
|
| 1034 |
+
# Compute old position cos/sin
|
| 1035 |
+
cos_old, sin_old = get_rotary_cos_sin(head_dim, old_positions, device, dtype, rope_theta, inv_freq_cache)
|
| 1036 |
+
|
| 1037 |
+
# Inverse transform: recover original key
|
| 1038 |
+
base = cos_old * suffix_keys - sin_old * rotate_half(suffix_keys)
|
| 1039 |
+
|
| 1040 |
+
# Compute new position cos/sin
|
| 1041 |
+
cos_new, sin_new = get_rotary_cos_sin(head_dim, new_positions, device, dtype, rope_theta, inv_freq_cache)
|
| 1042 |
+
|
| 1043 |
+
# Forward transform: re-encode with new positions
|
| 1044 |
+
return cos_new * base + sin_new * rotate_half(base)
|
| 1045 |
+
|
| 1046 |
+
|
| 1047 |
+
def drop_tokens_from_cache(
|
| 1048 |
+
cache: Optional[DynamicCache | Tuple],
|
| 1049 |
+
length: int,
|
| 1050 |
+
preserve: int,
|
| 1051 |
+
position_offset: int,
|
| 1052 |
+
rope_theta: float = 10000.0,
|
| 1053 |
+
inv_freq_cache: Optional[Dict[Tuple, torch.Tensor]] = None,
|
| 1054 |
+
) -> Tuple[Optional[DynamicCache], int, bool]:
|
| 1055 |
+
"""Drop tokens from a KV cache while preserving system prompt.
|
| 1056 |
+
|
| 1057 |
+
Removes tokens in the range [preserve, preserve + length) from the cache,
|
| 1058 |
+
realigning RoPE embeddings for the suffix.
|
| 1059 |
+
|
| 1060 |
+
Args:
|
| 1061 |
+
cache: DynamicCache or tuple-based cache (will be converted to DynamicCache)
|
| 1062 |
+
length: Number of tokens to drop
|
| 1063 |
+
preserve: Number of tokens to preserve at the start (system prompt)
|
| 1064 |
+
position_offset: Current position offset for RoPE calculation
|
| 1065 |
+
rope_theta: RoPE base frequency
|
| 1066 |
+
inv_freq_cache: Optional cache dict for inverse frequencies
|
| 1067 |
+
|
| 1068 |
+
Returns:
|
| 1069 |
+
Tuple of (cache, new_position_offset, success)
|
| 1070 |
+
Note: Tuple cache will be converted to DynamicCache. Modification is in-place.
|
| 1071 |
+
"""
|
| 1072 |
+
if cache is None or length <= 0:
|
| 1073 |
+
return cache, position_offset, False
|
| 1074 |
+
|
| 1075 |
+
cache = as_dynamic_cache(cache)
|
| 1076 |
+
|
| 1077 |
+
total_len = get_kv_cache_length(cache)
|
| 1078 |
+
if total_len <= 0:
|
| 1079 |
+
return cache, position_offset, False
|
| 1080 |
+
|
| 1081 |
+
preserve = min(preserve, total_len)
|
| 1082 |
+
available = total_len - preserve
|
| 1083 |
+
|
| 1084 |
+
if available < length:
|
| 1085 |
+
logger.warning(
|
| 1086 |
+
"Cannot drop %d tokens: only %d available (total=%d, preserve=%d)",
|
| 1087 |
+
length,
|
| 1088 |
+
available,
|
| 1089 |
+
total_len,
|
| 1090 |
+
preserve,
|
| 1091 |
+
)
|
| 1092 |
+
return cache, position_offset, False
|
| 1093 |
+
|
| 1094 |
+
suffix_len = total_len - preserve - length
|
| 1095 |
+
# note: after RoPE reindex, the position of cache has been compressed (from preserve start)
|
| 1096 |
+
# so here should not add position_offset, but use the actual layout of current cache
|
| 1097 |
+
suffix_offset = preserve + length # suffix current position in cache
|
| 1098 |
+
prefix_offset = preserve # suffix new position (follow preserve)
|
| 1099 |
+
|
| 1100 |
+
# Prepare position tensors for RoPE realignment
|
| 1101 |
+
old_positions = None
|
| 1102 |
+
new_positions = None
|
| 1103 |
+
if suffix_len > 0:
|
| 1104 |
+
device = cache.key_cache[0].device
|
| 1105 |
+
old_positions = torch.arange(
|
| 1106 |
+
suffix_offset,
|
| 1107 |
+
suffix_offset + suffix_len,
|
| 1108 |
+
device=device,
|
| 1109 |
+
dtype=torch.long,
|
| 1110 |
+
)
|
| 1111 |
+
new_positions = torch.arange(
|
| 1112 |
+
prefix_offset,
|
| 1113 |
+
prefix_offset + suffix_len,
|
| 1114 |
+
device=device,
|
| 1115 |
+
dtype=torch.long,
|
| 1116 |
+
)
|
| 1117 |
+
|
| 1118 |
+
keep_len = total_len - length
|
| 1119 |
+
|
| 1120 |
+
# Process each layer (in-place modification)
|
| 1121 |
+
for layer_idx in range(len(cache.key_cache)):
|
| 1122 |
+
key_tensor = cache.key_cache[layer_idx]
|
| 1123 |
+
value_tensor = cache.value_cache[layer_idx]
|
| 1124 |
+
|
| 1125 |
+
if not key_tensor.numel():
|
| 1126 |
+
continue
|
| 1127 |
+
|
| 1128 |
+
# Preserve prefix (system prompt)
|
| 1129 |
+
prefix_keys = key_tensor[:, :, :preserve, :]
|
| 1130 |
+
prefix_values = value_tensor[:, :, :preserve, :]
|
| 1131 |
+
|
| 1132 |
+
if suffix_len > 0:
|
| 1133 |
+
# Keep and realign suffix
|
| 1134 |
+
suffix_keys = key_tensor[:, :, preserve + length :, :]
|
| 1135 |
+
suffix_values = value_tensor[:, :, preserve + length :, :]
|
| 1136 |
+
|
| 1137 |
+
if old_positions is not None and new_positions is not None and suffix_keys.numel():
|
| 1138 |
+
suffix_keys = realign_rotary_suffix(
|
| 1139 |
+
suffix_keys,
|
| 1140 |
+
old_positions,
|
| 1141 |
+
new_positions,
|
| 1142 |
+
rope_theta,
|
| 1143 |
+
inv_freq_cache,
|
| 1144 |
+
)
|
| 1145 |
+
|
| 1146 |
+
cache.key_cache[layer_idx] = torch.cat([prefix_keys, suffix_keys], dim=-2).contiguous()
|
| 1147 |
+
cache.value_cache[layer_idx] = torch.cat([prefix_values, suffix_values], dim=-2).contiguous()
|
| 1148 |
+
else:
|
| 1149 |
+
cache.key_cache[layer_idx] = prefix_keys.contiguous()
|
| 1150 |
+
cache.value_cache[layer_idx] = prefix_values.contiguous()
|
| 1151 |
+
|
| 1152 |
+
cache.crop(keep_len)
|
| 1153 |
+
cache._seen_tokens = max(keep_len, 0)
|
| 1154 |
+
|
| 1155 |
+
new_offset = position_offset + length
|
| 1156 |
+
logger.debug("Dropped %d tokens from cache, new length=%d", length, keep_len)
|
| 1157 |
+
|
| 1158 |
+
return cache, new_offset, True
|
| 1159 |
+
|
| 1160 |
+
|
| 1161 |
+
# stream decoder
|
| 1162 |
+
def top_k_top_p_filtering(logits, top_k=0, top_p=0.0, filter_value=-float("inf")):
|
| 1163 |
+
logits = logits.clone()
|
| 1164 |
+
|
| 1165 |
+
# Top-k filtering
|
| 1166 |
+
if top_k > 0:
|
| 1167 |
+
top_k = min(top_k, logits.size(-1))
|
| 1168 |
+
indices_to_remove = logits < torch.topk(logits, top_k)[0][..., -1, None]
|
| 1169 |
+
logits[indices_to_remove] = filter_value
|
| 1170 |
+
|
| 1171 |
+
# Top-p (nucleus) filtering
|
| 1172 |
+
if top_p > 0.0:
|
| 1173 |
+
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
| 1174 |
+
probs = F.softmax(sorted_logits, dim=-1)
|
| 1175 |
+
cumulative_probs = torch.cumsum(probs, dim=-1)
|
| 1176 |
+
|
| 1177 |
+
sorted_indices_to_remove = cumulative_probs > top_p
|
| 1178 |
+
# keep the first token that exceeds top_p
|
| 1179 |
+
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
| 1180 |
+
sorted_indices_to_remove[..., 0] = 0
|
| 1181 |
+
|
| 1182 |
+
indices_to_remove = sorted_indices[sorted_indices_to_remove]
|
| 1183 |
+
logits[0, indices_to_remove] = filter_value
|
| 1184 |
+
|
| 1185 |
+
return logits
|
| 1186 |
+
|
| 1187 |
+
|
| 1188 |
+
class StreamDecoder:
|
| 1189 |
+
def __init__(self, llm, tokenizer, special_token_ids=None, forbidden_token_ids=None):
|
| 1190 |
+
self.m = llm
|
| 1191 |
+
self.tokenizer = tokenizer
|
| 1192 |
+
self.listen_id = self.tokenizer.eos_token_id
|
| 1193 |
+
|
| 1194 |
+
self.chunk_eos_id = self.tokenizer.convert_tokens_to_ids("<|chunk_eos|>")
|
| 1195 |
+
self.chunk_tts_eos_id = self.tokenizer.convert_tokens_to_ids("<|chunk_tts_eos|>")
|
| 1196 |
+
self.turn_eos_id = self.tokenizer.convert_tokens_to_ids("<|turn_eos|>")
|
| 1197 |
+
self.speak_id = self.tokenizer.convert_tokens_to_ids("<|speak|>")
|
| 1198 |
+
|
| 1199 |
+
self.special_token_ids = special_token_ids if special_token_ids is not None else []
|
| 1200 |
+
|
| 1201 |
+
# cache special tokens (used for context sliding window filtering)
|
| 1202 |
+
self._all_special_ids = set()
|
| 1203 |
+
self._all_special_tokens_text = set()
|
| 1204 |
+
if self.tokenizer:
|
| 1205 |
+
if hasattr(self.tokenizer, "all_special_ids"):
|
| 1206 |
+
self._all_special_ids = set(self.tokenizer.all_special_ids)
|
| 1207 |
+
if hasattr(self.tokenizer, "all_special_tokens"):
|
| 1208 |
+
self._all_special_tokens_text = set(self.tokenizer.all_special_tokens)
|
| 1209 |
+
|
| 1210 |
+
custom_special_tokens = [
|
| 1211 |
+
"<unit>",
|
| 1212 |
+
"</unit>",
|
| 1213 |
+
"<image>",
|
| 1214 |
+
"</image>",
|
| 1215 |
+
"<slice>",
|
| 1216 |
+
"</slice>",
|
| 1217 |
+
"<|listen|>",
|
| 1218 |
+
"<|speak|>",
|
| 1219 |
+
"<|tts_bos|>",
|
| 1220 |
+
"<|tts_eos|>",
|
| 1221 |
+
"<|audio_start|>",
|
| 1222 |
+
"<|audio_end|>",
|
| 1223 |
+
"<|chunk_eos|>",
|
| 1224 |
+
"<|chunk_tts_eos|>",
|
| 1225 |
+
"<|turn_eos|>",
|
| 1226 |
+
"<|audio_start|>",
|
| 1227 |
+
"<|audio_end|>",
|
| 1228 |
+
]
|
| 1229 |
+
self._all_special_tokens_text.update(custom_special_tokens)
|
| 1230 |
+
for token in custom_special_tokens:
|
| 1231 |
+
token_id = self.tokenizer.convert_tokens_to_ids(token)
|
| 1232 |
+
if token_id is not None and token_id != self.tokenizer.unk_token_id:
|
| 1233 |
+
self._all_special_ids.add(token_id)
|
| 1234 |
+
|
| 1235 |
+
if forbidden_token_ids is None:
|
| 1236 |
+
self.forbidden_token_ids = []
|
| 1237 |
+
elif isinstance(forbidden_token_ids, int):
|
| 1238 |
+
self.forbidden_token_ids = [self.forbidden_token_ids]
|
| 1239 |
+
else:
|
| 1240 |
+
self.forbidden_token_ids = forbidden_token_ids
|
| 1241 |
+
self.forbidden_token_ids.append(self.chunk_eos_id)
|
| 1242 |
+
|
| 1243 |
+
assert isinstance(self.forbidden_token_ids, list)
|
| 1244 |
+
|
| 1245 |
+
self.cache = None
|
| 1246 |
+
self.context = ""
|
| 1247 |
+
self.generated_tokens = [] # track generated tokens
|
| 1248 |
+
self.generated_special_tokens = [] # track generated special tokens
|
| 1249 |
+
self.reset()
|
| 1250 |
+
self.embeds = None
|
| 1251 |
+
self.system_embeds = None
|
| 1252 |
+
|
| 1253 |
+
# sliding window related states
|
| 1254 |
+
self._unit_history: List[Dict[str, Any]] = []
|
| 1255 |
+
self._next_unit_id: int = 0
|
| 1256 |
+
self._pending_unit_id: Optional[int] = None
|
| 1257 |
+
self._pending_unit_start_cache_len: int = 0
|
| 1258 |
+
self._system_preserve_length: int = 0
|
| 1259 |
+
self._position_offset: int = 0
|
| 1260 |
+
self._window_config = DuplexWindowConfig()
|
| 1261 |
+
self._window_enabled: bool = True
|
| 1262 |
+
self._rope_inv_freq_cache: Dict[Tuple, torch.Tensor] = {}
|
| 1263 |
+
|
| 1264 |
+
# context preserving sliding window states
|
| 1265 |
+
# initial cache layout: [prefix] [suffix] [units...]
|
| 1266 |
+
# after first sliding window: [prefix] [previous_marker + content] [suffix] [units...]
|
| 1267 |
+
# fixed dynamic sliding region fixed
|
| 1268 |
+
self._preserve_prefix_length: int = 0 # original prefix length (fixed)
|
| 1269 |
+
self._previous_content_length: int = 0 # previous content length (dynamic, including marker)
|
| 1270 |
+
self._suffix_token_ids: List[int] = [] # suffix token ids (e.g. <|im_end|>)
|
| 1271 |
+
|
| 1272 |
+
# previous marker (added dynamically after first sliding window)
|
| 1273 |
+
self._previous_marker: str = "\n\nprevious: " # fixed prefix marker
|
| 1274 |
+
self._previous_marker_token_ids: List[int] = [] # marker token ids (initialized)
|
| 1275 |
+
self._has_previous: bool = False # whether previous marker has been added
|
| 1276 |
+
|
| 1277 |
+
# previous content
|
| 1278 |
+
self._previous_text: str = "" # accumulated generated text (without marker)
|
| 1279 |
+
self._previous_token_ids: List[int] = [] # previous full token ids (including marker)
|
| 1280 |
+
|
| 1281 |
+
# validation statistics
|
| 1282 |
+
self._sliding_event_count: int = 0 # sliding window trigger count
|
| 1283 |
+
self._total_dropped_tokens: int = 0 # total dropped token count
|
| 1284 |
+
self._total_dropped_units: int = 0 # total dropped unit count
|
| 1285 |
+
|
| 1286 |
+
def sliding_embeds(self):
|
| 1287 |
+
# tmp = system_embeds
|
| 1288 |
+
# tmp +-》 embeds after 5s
|
| 1289 |
+
# reset
|
| 1290 |
+
# feed
|
| 1291 |
+
pass
|
| 1292 |
+
|
| 1293 |
+
def reset(self):
|
| 1294 |
+
self.context = ""
|
| 1295 |
+
self.cache = None
|
| 1296 |
+
self.generated_tokens = []
|
| 1297 |
+
self.generated_special_tokens = []
|
| 1298 |
+
self.embeds = None
|
| 1299 |
+
self.system_embeds = None
|
| 1300 |
+
|
| 1301 |
+
# sliding window state reset
|
| 1302 |
+
old_unit_count = len(self._unit_history) if hasattr(self, "_unit_history") else 0
|
| 1303 |
+
self._unit_history = []
|
| 1304 |
+
self._next_unit_id = 0
|
| 1305 |
+
self._pending_unit_id = None
|
| 1306 |
+
self._pending_unit_start_cache_len = 0
|
| 1307 |
+
self._system_preserve_length = 0
|
| 1308 |
+
self._position_offset = 0
|
| 1309 |
+
self._rope_inv_freq_cache = {}
|
| 1310 |
+
|
| 1311 |
+
# context preserving sliding window state reset
|
| 1312 |
+
self._preserve_prefix_length = 0
|
| 1313 |
+
self._previous_content_length = 0
|
| 1314 |
+
self._suffix_token_ids = []
|
| 1315 |
+
self._previous_marker = "\n\nprevious: "
|
| 1316 |
+
self._previous_marker_token_ids = []
|
| 1317 |
+
self._has_previous = False
|
| 1318 |
+
self._previous_text = ""
|
| 1319 |
+
self._previous_token_ids = []
|
| 1320 |
+
|
| 1321 |
+
# validation statistics
|
| 1322 |
+
self._sliding_event_count = 0 # sliding window trigger count
|
| 1323 |
+
self._total_dropped_tokens = 0 # total dropped token count
|
| 1324 |
+
self._total_dropped_units = 0 # total dropped unit count
|
| 1325 |
+
|
| 1326 |
+
def get_cache_length(self) -> int:
|
| 1327 |
+
if self.cache is None:
|
| 1328 |
+
return 0
|
| 1329 |
+
if isinstance(self.cache, DynamicCache):
|
| 1330 |
+
if len(self.cache.key_cache) > 0 and self.cache.key_cache[0].numel() > 0:
|
| 1331 |
+
return self.cache.key_cache[0].shape[2]
|
| 1332 |
+
return 0
|
| 1333 |
+
# Tuple cache format
|
| 1334 |
+
return self.cache[0][0].shape[2]
|
| 1335 |
+
|
| 1336 |
+
def get_total_generated_tokens(self) -> int:
|
| 1337 |
+
return sum(len(u.get("generated_tokens", [])) for u in self._unit_history)
|
| 1338 |
+
|
| 1339 |
+
def register_unit_start(self) -> int:
|
| 1340 |
+
self._pending_unit_id = self._next_unit_id
|
| 1341 |
+
self._pending_unit_start_cache_len = self.get_cache_length()
|
| 1342 |
+
return self._pending_unit_id
|
| 1343 |
+
|
| 1344 |
+
def register_unit_end(
|
| 1345 |
+
self,
|
| 1346 |
+
input_type: str,
|
| 1347 |
+
generated_tokens: Optional[List[int]] = None,
|
| 1348 |
+
is_listen: bool = False,
|
| 1349 |
+
generated_text: Optional[str] = None,
|
| 1350 |
+
):
|
| 1351 |
+
"""Call when unit ends, record unit information
|
| 1352 |
+
|
| 1353 |
+
Should be called after feeding </unit> token
|
| 1354 |
+
|
| 1355 |
+
Args:
|
| 1356 |
+
input_type: "audio" / "video" / "omni" / "system"
|
| 1357 |
+
generated_tokens: tokens generated by the unit (token ids)
|
| 1358 |
+
is_listen: whether the unit is in listen state
|
| 1359 |
+
generated_text: text generated by the unit (used for context preserving mode)
|
| 1360 |
+
"""
|
| 1361 |
+
if self._pending_unit_id is None:
|
| 1362 |
+
logger.warning("register_unit_end called without register_unit_start")
|
| 1363 |
+
return
|
| 1364 |
+
|
| 1365 |
+
# calculate the length of the unit
|
| 1366 |
+
current_cache_len = self.get_cache_length()
|
| 1367 |
+
unit_len = current_cache_len - self._pending_unit_start_cache_len
|
| 1368 |
+
|
| 1369 |
+
if unit_len > 0:
|
| 1370 |
+
entry = {
|
| 1371 |
+
"unit_id": self._pending_unit_id,
|
| 1372 |
+
"length": unit_len,
|
| 1373 |
+
"type": input_type,
|
| 1374 |
+
"generated_tokens": generated_tokens or [],
|
| 1375 |
+
"generated_text": generated_text or "", # used for context preserving mode
|
| 1376 |
+
"is_listen": is_listen,
|
| 1377 |
+
}
|
| 1378 |
+
self._unit_history.append(entry)
|
| 1379 |
+
|
| 1380 |
+
self._pending_unit_id = None
|
| 1381 |
+
self._pending_unit_start_cache_len = 0
|
| 1382 |
+
self._next_unit_id += 1
|
| 1383 |
+
|
| 1384 |
+
def register_system_prompt(self):
|
| 1385 |
+
"""Call after system prompt prefill, record preserve length"""
|
| 1386 |
+
self._system_preserve_length = self.get_cache_length()
|
| 1387 |
+
|
| 1388 |
+
# sliding window core methods
|
| 1389 |
+
|
| 1390 |
+
def _get_rope_theta(self) -> float:
|
| 1391 |
+
"""get model rope_theta configuration"""
|
| 1392 |
+
return float(getattr(self.m.config, "rope_theta", 10000.0))
|
| 1393 |
+
|
| 1394 |
+
def _drop_tokens_from_cache(self, length: int) -> bool:
|
| 1395 |
+
"""remove specified number of tokens from cache (protect system prompt)
|
| 1396 |
+
|
| 1397 |
+
remove tokens in the range [preserve, preserve + length)
|
| 1398 |
+
supports DynamicCache and tuple cache formats
|
| 1399 |
+
"""
|
| 1400 |
+
if self.cache is None or length <= 0:
|
| 1401 |
+
return False
|
| 1402 |
+
|
| 1403 |
+
cache_type = "DynamicCache" if isinstance(self.cache, DynamicCache) else "TupleCache"
|
| 1404 |
+
cache_len_before = self.get_cache_length()
|
| 1405 |
+
offset_before = self._position_offset
|
| 1406 |
+
|
| 1407 |
+
new_cache, new_offset, success = drop_tokens_from_cache(
|
| 1408 |
+
cache=self.cache,
|
| 1409 |
+
length=length,
|
| 1410 |
+
preserve=self._system_preserve_length,
|
| 1411 |
+
position_offset=self._position_offset,
|
| 1412 |
+
rope_theta=self._get_rope_theta(),
|
| 1413 |
+
inv_freq_cache=self._rope_inv_freq_cache,
|
| 1414 |
+
)
|
| 1415 |
+
if success:
|
| 1416 |
+
self.cache = new_cache # For DynamicCache this is the same object (in-place)
|
| 1417 |
+
self._position_offset = new_offset
|
| 1418 |
+
|
| 1419 |
+
return success
|
| 1420 |
+
|
| 1421 |
+
def _drop_unit(self, unit_id: int) -> bool:
|
| 1422 |
+
"""remove specified unit"""
|
| 1423 |
+
entries = [u for u in self._unit_history if u["unit_id"] == unit_id]
|
| 1424 |
+
if not entries:
|
| 1425 |
+
return False
|
| 1426 |
+
|
| 1427 |
+
total_len = sum(e["length"] for e in entries)
|
| 1428 |
+
if total_len <= 0:
|
| 1429 |
+
for e in entries:
|
| 1430 |
+
self._unit_history.remove(e)
|
| 1431 |
+
return False
|
| 1432 |
+
|
| 1433 |
+
if not self._drop_tokens_from_cache(total_len):
|
| 1434 |
+
return False
|
| 1435 |
+
|
| 1436 |
+
for e in entries:
|
| 1437 |
+
self._unit_history.remove(e)
|
| 1438 |
+
|
| 1439 |
+
return True
|
| 1440 |
+
|
| 1441 |
+
def _drop_next_unit(self) -> bool:
|
| 1442 |
+
"""remove the earliest non-system unit"""
|
| 1443 |
+
for entry in self._unit_history:
|
| 1444 |
+
unit_id = entry.get("unit_id")
|
| 1445 |
+
if unit_id is None:
|
| 1446 |
+
continue
|
| 1447 |
+
# skip system type
|
| 1448 |
+
if entry.get("type") == "system":
|
| 1449 |
+
continue
|
| 1450 |
+
if self._drop_unit(unit_id):
|
| 1451 |
+
return True
|
| 1452 |
+
return False
|
| 1453 |
+
|
| 1454 |
+
def enforce_window(self) -> bool:
|
| 1455 |
+
"""enforce sliding window strategy (same as single-mode, only look at cache length)
|
| 1456 |
+
|
| 1457 |
+
when cache length exceeds high water line, loop to remove the earliest unit,
|
| 1458 |
+
until cache length drops below the low water line.
|
| 1459 |
+
"""
|
| 1460 |
+
if not self._window_enabled:
|
| 1461 |
+
return False
|
| 1462 |
+
|
| 1463 |
+
cfg = self._window_config
|
| 1464 |
+
cache_len_before = self.get_cache_length()
|
| 1465 |
+
|
| 1466 |
+
if cache_len_before <= cfg.basic_window_high_tokens:
|
| 1467 |
+
return False # not above high water line, no trigger
|
| 1468 |
+
|
| 1469 |
+
dropped_count = 0
|
| 1470 |
+
cache_len = cache_len_before
|
| 1471 |
+
while cache_len > cfg.basic_window_low_tokens:
|
| 1472 |
+
if not self._drop_next_unit():
|
| 1473 |
+
break
|
| 1474 |
+
dropped_count += 1
|
| 1475 |
+
cache_len = self.get_cache_length()
|
| 1476 |
+
|
| 1477 |
+
if dropped_count > 0:
|
| 1478 |
+
# update statistics counters
|
| 1479 |
+
self._sliding_event_count += 1
|
| 1480 |
+
self._total_dropped_tokens += cache_len_before - cache_len
|
| 1481 |
+
self._total_dropped_units += dropped_count
|
| 1482 |
+
|
| 1483 |
+
# consistency check
|
| 1484 |
+
expected = self._system_preserve_length + sum(u["length"] for u in self._unit_history)
|
| 1485 |
+
is_consistent = expected == cache_len
|
| 1486 |
+
if not is_consistent:
|
| 1487 |
+
logger.error(
|
| 1488 |
+
"CONSISTENCY ERROR! preserve=%d + sum(units)=%d != cache=%d, offset=%d",
|
| 1489 |
+
self._system_preserve_length,
|
| 1490 |
+
sum(u["length"] for u in self._unit_history),
|
| 1491 |
+
cache_len,
|
| 1492 |
+
self._position_offset,
|
| 1493 |
+
)
|
| 1494 |
+
|
| 1495 |
+
return dropped_count > 0
|
| 1496 |
+
|
| 1497 |
+
# context preserving sliding window methods
|
| 1498 |
+
|
| 1499 |
+
def register_system_prompt_with_context(
|
| 1500 |
+
self,
|
| 1501 |
+
suffix_token_ids: Optional[List[int]] = None,
|
| 1502 |
+
context_previous_marker: str = "\n\nprevious: ",
|
| 1503 |
+
):
|
| 1504 |
+
"""register system prompt (with context preserving mode)
|
| 1505 |
+
|
| 1506 |
+
initial cache layout: [prefix] [suffix] [units...]
|
| 1507 |
+
after first sliding window: [prefix] [context_previous_marker + content] [suffix] [units...]
|
| 1508 |
+
|
| 1509 |
+
when calling this method, cache should only have prefix (without previous marker)
|
| 1510 |
+
suffix will be fed in later
|
| 1511 |
+
|
| 1512 |
+
Args:
|
| 1513 |
+
suffix_token_ids: suffix token ids (e.g. id of <|im_end|>)
|
| 1514 |
+
context_previous_marker: previous marker prefix, e.g. "\\n\\nprevious: "
|
| 1515 |
+
"""
|
| 1516 |
+
# prefix = current cache content (fixed, without previous marker)
|
| 1517 |
+
self._preserve_prefix_length = self.get_cache_length()
|
| 1518 |
+
self._previous_content_length = 0 # initially no previous content
|
| 1519 |
+
self._suffix_token_ids = suffix_token_ids or []
|
| 1520 |
+
# total preserve length = prefix + suffix (initially no previous)
|
| 1521 |
+
self._system_preserve_length = self._preserve_prefix_length + len(self._suffix_token_ids)
|
| 1522 |
+
|
| 1523 |
+
# initialize previous related states
|
| 1524 |
+
self._previous_marker = context_previous_marker
|
| 1525 |
+
self._previous_marker_token_ids = (
|
| 1526 |
+
self.tokenizer.encode(context_previous_marker, add_special_tokens=False) if self.tokenizer else []
|
| 1527 |
+
)
|
| 1528 |
+
self._has_previous = False
|
| 1529 |
+
self._previous_text = ""
|
| 1530 |
+
self._previous_token_ids = []
|
| 1531 |
+
|
| 1532 |
+
def _extract_generated_text(self, units: List[Dict[str, Any]]) -> Tuple[str, List[int]]:
|
| 1533 |
+
"""extract generated text and token ids from units
|
| 1534 |
+
|
| 1535 |
+
Args:
|
| 1536 |
+
units: list of units to extract
|
| 1537 |
+
|
| 1538 |
+
Returns:
|
| 1539 |
+
(text, token_ids): concatenated text and token ids (filtered out special tokens)
|
| 1540 |
+
"""
|
| 1541 |
+
text_parts = []
|
| 1542 |
+
token_ids = []
|
| 1543 |
+
|
| 1544 |
+
for u in units:
|
| 1545 |
+
# only keep generated content of non-listen units
|
| 1546 |
+
if u.get("is_listen", False):
|
| 1547 |
+
continue
|
| 1548 |
+
gen_text = u.get("generated_text", "")
|
| 1549 |
+
gen_tokens = u.get("generated_tokens", [])
|
| 1550 |
+
|
| 1551 |
+
# filter out special tokens from text
|
| 1552 |
+
if gen_text:
|
| 1553 |
+
clean_text = gen_text
|
| 1554 |
+
for st in self._all_special_tokens_text:
|
| 1555 |
+
clean_text = clean_text.replace(st, "")
|
| 1556 |
+
if clean_text.strip():
|
| 1557 |
+
text_parts.append(clean_text)
|
| 1558 |
+
|
| 1559 |
+
# filter out special tokens
|
| 1560 |
+
if gen_tokens:
|
| 1561 |
+
filtered_tokens = [t for t in gen_tokens if t not in self._all_special_ids]
|
| 1562 |
+
token_ids.extend(filtered_tokens)
|
| 1563 |
+
|
| 1564 |
+
return "".join(text_parts), token_ids
|
| 1565 |
+
|
| 1566 |
+
def _rebuild_cache_with_previous(
|
| 1567 |
+
self,
|
| 1568 |
+
new_previous_tokens: List[int],
|
| 1569 |
+
units_to_keep_len: Optional[int] = None,
|
| 1570 |
+
) -> bool:
|
| 1571 |
+
"""rebuild cache, insert new previous content between prefix and suffix
|
| 1572 |
+
|
| 1573 |
+
cache layout change:
|
| 1574 |
+
[prefix] [old_prev] [suffix] [old_units] → [prefix] [new_prev] [suffix] [remaining_units]
|
| 1575 |
+
|
| 1576 |
+
Args:
|
| 1577 |
+
new_previous_tokens: new previous token ids
|
| 1578 |
+
units_to_keep_len: length of units to keep (from cache end backwards)
|
| 1579 |
+
if None, calculate based on unit_history
|
| 1580 |
+
|
| 1581 |
+
Returns:
|
| 1582 |
+
whether successful rebuild
|
| 1583 |
+
"""
|
| 1584 |
+
if self.cache is None:
|
| 1585 |
+
return False
|
| 1586 |
+
|
| 1587 |
+
old_previous_len = self._previous_content_length
|
| 1588 |
+
new_previous_len = len(new_previous_tokens)
|
| 1589 |
+
suffix_len = len(self._suffix_token_ids)
|
| 1590 |
+
total_cache_len = self.get_cache_length()
|
| 1591 |
+
|
| 1592 |
+
# calculate length of units to keep
|
| 1593 |
+
if units_to_keep_len is None:
|
| 1594 |
+
units_to_keep_len = sum(u["length"] for u in self._unit_history)
|
| 1595 |
+
|
| 1596 |
+
# special case: if previous is unchanged (new and old are empty), no need to rebuild prefix+suffix part of cache
|
| 1597 |
+
# but still need to reindex units RoPE (because a unit was deleted, position changed)
|
| 1598 |
+
if new_previous_len == 0 and old_previous_len == 0:
|
| 1599 |
+
# cache layout: [prefix(7)] [suffix(1)] [units...]
|
| 1600 |
+
# only keep prefix + suffix + remaining_units
|
| 1601 |
+
preserve_len = self._preserve_prefix_length + suffix_len
|
| 1602 |
+
|
| 1603 |
+
# simply slice cache: [prefix+suffix] + [remaining_units]
|
| 1604 |
+
# remaining_units in cache end
|
| 1605 |
+
if units_to_keep_len > 0:
|
| 1606 |
+
# [0:preserve_len] + [total-units_to_keep_len:total]
|
| 1607 |
+
prefix_suffix_cache = self._slice_cache(0, preserve_len)
|
| 1608 |
+
units_cache = self._slice_cache(total_cache_len - units_to_keep_len, None)
|
| 1609 |
+
|
| 1610 |
+
# calculate number of dropped tokens
|
| 1611 |
+
dropped_tokens = total_cache_len - preserve_len - units_to_keep_len
|
| 1612 |
+
|
| 1613 |
+
# reindex units RoPE: position from (preserve_len + dropped_tokens) to preserve_len
|
| 1614 |
+
# note: no position_offset, because cache position has been compressed (from 0 start)
|
| 1615 |
+
if dropped_tokens > 0:
|
| 1616 |
+
old_start = preserve_len + dropped_tokens
|
| 1617 |
+
new_start = preserve_len
|
| 1618 |
+
units_cache = self._reindex_rope_for_cache(units_cache, old_start, new_start, units_to_keep_len)
|
| 1619 |
+
|
| 1620 |
+
self.cache = self._concat_caches(prefix_suffix_cache, units_cache)
|
| 1621 |
+
else:
|
| 1622 |
+
self.cache = self._slice_cache(0, preserve_len)
|
| 1623 |
+
|
| 1624 |
+
return True
|
| 1625 |
+
|
| 1626 |
+
# 1. get prefix cache (fixed)
|
| 1627 |
+
prefix_end = self._preserve_prefix_length
|
| 1628 |
+
prefix_cache = self._slice_cache(0, prefix_end)
|
| 1629 |
+
|
| 1630 |
+
# 2. get units cache to keep (from end)
|
| 1631 |
+
units_start_in_old_cache = total_cache_len - units_to_keep_len
|
| 1632 |
+
units_cache = None
|
| 1633 |
+
if units_to_keep_len > 0:
|
| 1634 |
+
units_cache = self._slice_cache(units_start_in_old_cache, None)
|
| 1635 |
+
|
| 1636 |
+
# 3. calculate new previous + suffix cache (needs forward)
|
| 1637 |
+
# merge previous tokens and suffix tokens
|
| 1638 |
+
prev_suffix_tokens = new_previous_tokens + self._suffix_token_ids
|
| 1639 |
+
prev_suffix_len = len(prev_suffix_tokens)
|
| 1640 |
+
|
| 1641 |
+
new_prefix_prev_suffix_cache = prefix_cache
|
| 1642 |
+
if prev_suffix_len > 0:
|
| 1643 |
+
# Embed tokens
|
| 1644 |
+
prev_suffix_embeds = self.embed_tokens(prev_suffix_tokens)
|
| 1645 |
+
# calculate start position (after prefix)
|
| 1646 |
+
start_pos = self._preserve_prefix_length + self._position_offset
|
| 1647 |
+
|
| 1648 |
+
# forward calculate KV cache
|
| 1649 |
+
with torch.no_grad():
|
| 1650 |
+
device = prev_suffix_embeds.device
|
| 1651 |
+
position_ids = torch.arange(
|
| 1652 |
+
start_pos,
|
| 1653 |
+
start_pos + prev_suffix_len,
|
| 1654 |
+
device=device,
|
| 1655 |
+
).unsqueeze(0)
|
| 1656 |
+
|
| 1657 |
+
# use prefix cache as past_key_values
|
| 1658 |
+
outputs = self.m(
|
| 1659 |
+
inputs_embeds=(
|
| 1660 |
+
prev_suffix_embeds.unsqueeze(0) if prev_suffix_embeds.dim() == 2 else prev_suffix_embeds
|
| 1661 |
+
),
|
| 1662 |
+
position_ids=position_ids,
|
| 1663 |
+
past_key_values=prefix_cache,
|
| 1664 |
+
use_cache=True,
|
| 1665 |
+
return_dict=True,
|
| 1666 |
+
)
|
| 1667 |
+
# new cache contains prefix + new_previous + suffix
|
| 1668 |
+
new_prefix_prev_suffix_cache = outputs.past_key_values
|
| 1669 |
+
|
| 1670 |
+
# 4. adjust units cache RoPE
|
| 1671 |
+
# new layout: [prefix] [new_prev] [suffix] [units]
|
| 1672 |
+
# note: no position_offset, because cache position has been compressed (from 0 start)
|
| 1673 |
+
new_system_total = prefix_end + new_previous_len + suffix_len
|
| 1674 |
+
if units_cache is not None and self._get_cache_len(units_cache) > 0:
|
| 1675 |
+
old_start = units_start_in_old_cache
|
| 1676 |
+
new_start = new_system_total
|
| 1677 |
+
|
| 1678 |
+
if old_start != new_start:
|
| 1679 |
+
units_cache = self._reindex_rope_for_cache(units_cache, old_start, new_start, units_to_keep_len)
|
| 1680 |
+
|
| 1681 |
+
# 5. concatenate new cache
|
| 1682 |
+
if units_cache is not None and self._get_cache_len(units_cache) > 0:
|
| 1683 |
+
self.cache = self._concat_caches(new_prefix_prev_suffix_cache, units_cache)
|
| 1684 |
+
else:
|
| 1685 |
+
self.cache = new_prefix_prev_suffix_cache
|
| 1686 |
+
|
| 1687 |
+
# 6. update length
|
| 1688 |
+
self._previous_content_length = new_previous_len
|
| 1689 |
+
# total preserve length = prefix + previous + suffix
|
| 1690 |
+
self._system_preserve_length = prefix_end + new_previous_len + suffix_len
|
| 1691 |
+
|
| 1692 |
+
# print detailed cache layout information
|
| 1693 |
+
prev_text_preview = self._previous_text[:50] + "..." if len(self._previous_text) > 50 else self._previous_text
|
| 1694 |
+
suffix_preview = self.tokenizer.decode(self._suffix_token_ids) if self._suffix_token_ids else ""
|
| 1695 |
+
return True
|
| 1696 |
+
|
| 1697 |
+
def _slice_cache(self, start: int, end: Optional[int], clone: bool = True):
|
| 1698 |
+
"""slice cache
|
| 1699 |
+
|
| 1700 |
+
Args:
|
| 1701 |
+
start: start position
|
| 1702 |
+
end: end position (None means to end)
|
| 1703 |
+
clone: whether to clone (default True, to prevent shared memory issues)
|
| 1704 |
+
"""
|
| 1705 |
+
if self.cache is None:
|
| 1706 |
+
return None
|
| 1707 |
+
if isinstance(self.cache, DynamicCache):
|
| 1708 |
+
# DynamicCache
|
| 1709 |
+
new_key_cache = [
|
| 1710 |
+
k[:, :, start:end, :].clone() if clone else k[:, :, start:end, :] for k in self.cache.key_cache
|
| 1711 |
+
]
|
| 1712 |
+
new_value_cache = [
|
| 1713 |
+
v[:, :, start:end, :].clone() if clone else v[:, :, start:end, :] for v in self.cache.value_cache
|
| 1714 |
+
]
|
| 1715 |
+
new_cache = DynamicCache()
|
| 1716 |
+
new_cache.key_cache = new_key_cache
|
| 1717 |
+
new_cache.value_cache = new_value_cache
|
| 1718 |
+
return new_cache
|
| 1719 |
+
else:
|
| 1720 |
+
# Tuple cache
|
| 1721 |
+
if clone:
|
| 1722 |
+
return tuple(
|
| 1723 |
+
(layer[0][:, :, start:end, :].clone(), layer[1][:, :, start:end, :].clone()) for layer in self.cache
|
| 1724 |
+
)
|
| 1725 |
+
else:
|
| 1726 |
+
return tuple((layer[0][:, :, start:end, :], layer[1][:, :, start:end, :]) for layer in self.cache)
|
| 1727 |
+
|
| 1728 |
+
@staticmethod
|
| 1729 |
+
def _get_cache_len(cache) -> int:
|
| 1730 |
+
if cache is None:
|
| 1731 |
+
return 0
|
| 1732 |
+
if isinstance(cache, DynamicCache):
|
| 1733 |
+
if len(cache.key_cache) > 0 and cache.key_cache[0].numel() > 0:
|
| 1734 |
+
return cache.key_cache[0].shape[2]
|
| 1735 |
+
return 0
|
| 1736 |
+
|
| 1737 |
+
if cache and cache[0] and cache[0][0] is not None:
|
| 1738 |
+
return cache[0][0].shape[2]
|
| 1739 |
+
return 0
|
| 1740 |
+
|
| 1741 |
+
@staticmethod
|
| 1742 |
+
def _concat_caches(cache1, cache2):
|
| 1743 |
+
if cache1 is None:
|
| 1744 |
+
return cache2
|
| 1745 |
+
if cache2 is None:
|
| 1746 |
+
return cache1
|
| 1747 |
+
|
| 1748 |
+
if isinstance(cache1, DynamicCache):
|
| 1749 |
+
new_cache = DynamicCache()
|
| 1750 |
+
new_cache.key_cache = [torch.cat([k1, k2], dim=2) for k1, k2 in zip(cache1.key_cache, cache2.key_cache)]
|
| 1751 |
+
new_cache.value_cache = [
|
| 1752 |
+
torch.cat([v1, v2], dim=2) for v1, v2 in zip(cache1.value_cache, cache2.value_cache)
|
| 1753 |
+
]
|
| 1754 |
+
return new_cache
|
| 1755 |
+
else:
|
| 1756 |
+
return tuple(
|
| 1757 |
+
(
|
| 1758 |
+
torch.cat([layer1[0], layer2[0]], dim=2),
|
| 1759 |
+
torch.cat([layer1[1], layer2[1]], dim=2),
|
| 1760 |
+
)
|
| 1761 |
+
for layer1, layer2 in zip(cache1, cache2)
|
| 1762 |
+
)
|
| 1763 |
+
|
| 1764 |
+
def _reindex_rope_for_cache(self, cache, old_start: int, new_start: int, length: int):
|
| 1765 |
+
"""reindex RoPE position for cache"""
|
| 1766 |
+
if cache is None or length <= 0:
|
| 1767 |
+
return cache
|
| 1768 |
+
|
| 1769 |
+
if isinstance(cache, DynamicCache):
|
| 1770 |
+
device = cache.key_cache[0].device if cache.key_cache else None
|
| 1771 |
+
else:
|
| 1772 |
+
device = cache[0][0].device if cache and cache[0] else None
|
| 1773 |
+
|
| 1774 |
+
if device is None:
|
| 1775 |
+
return cache
|
| 1776 |
+
|
| 1777 |
+
old_positions = torch.arange(old_start, old_start + length, device=device, dtype=torch.long)
|
| 1778 |
+
new_positions = torch.arange(new_start, new_start + length, device=device, dtype=torch.long)
|
| 1779 |
+
|
| 1780 |
+
rope_theta = self._get_rope_theta()
|
| 1781 |
+
|
| 1782 |
+
if isinstance(cache, DynamicCache):
|
| 1783 |
+
new_key_cache = []
|
| 1784 |
+
for k in cache.key_cache:
|
| 1785 |
+
new_k = realign_rotary_suffix(k, old_positions, new_positions, rope_theta, self._rope_inv_freq_cache)
|
| 1786 |
+
new_key_cache.append(new_k)
|
| 1787 |
+
cache.key_cache = new_key_cache
|
| 1788 |
+
return cache
|
| 1789 |
+
else:
|
| 1790 |
+
new_cache = []
|
| 1791 |
+
for layer in cache:
|
| 1792 |
+
new_k = realign_rotary_suffix(
|
| 1793 |
+
layer[0], old_positions, new_positions, rope_theta, self._rope_inv_freq_cache
|
| 1794 |
+
)
|
| 1795 |
+
new_cache.append((new_k, layer[1]))
|
| 1796 |
+
return tuple(new_cache)
|
| 1797 |
+
|
| 1798 |
+
def _update_previous(
|
| 1799 |
+
self,
|
| 1800 |
+
new_text: str,
|
| 1801 |
+
new_tokens: List[int],
|
| 1802 |
+
max_tokens: int,
|
| 1803 |
+
) -> None:
|
| 1804 |
+
"""update previous context (also update cache)
|
| 1805 |
+
|
| 1806 |
+
when first sliding window, dynamically add marker + text, subsequent sliding window append text
|
| 1807 |
+
when content exceeds max_tokens, truncate content (keep marker)
|
| 1808 |
+
rebuild cache to maintain consistency
|
| 1809 |
+
|
| 1810 |
+
Args:
|
| 1811 |
+
new_text: new text
|
| 1812 |
+
new_tokens: new token ids
|
| 1813 |
+
max_tokens: previous content maximum token count (without marker)
|
| 1814 |
+
"""
|
| 1815 |
+
marker_len = len(self._previous_marker_token_ids)
|
| 1816 |
+
tokens_to_drop = 0
|
| 1817 |
+
|
| 1818 |
+
# if no new content, do not add marker, but still need to rebuild cache
|
| 1819 |
+
if not new_tokens and not new_text:
|
| 1820 |
+
# still need to rebuild cache (because a unit was deleted)
|
| 1821 |
+
self._rebuild_cache_with_previous(self._previous_token_ids)
|
| 1822 |
+
return
|
| 1823 |
+
|
| 1824 |
+
if not self._has_previous:
|
| 1825 |
+
# when first has actual content: add marker + text
|
| 1826 |
+
self._previous_text = new_text
|
| 1827 |
+
self._previous_token_ids = self._previous_marker_token_ids.copy() + new_tokens
|
| 1828 |
+
self._has_previous = True
|
| 1829 |
+
else:
|
| 1830 |
+
# subsequent sliding window: append text to previous
|
| 1831 |
+
self._previous_text += new_text
|
| 1832 |
+
self._previous_token_ids.extend(new_tokens)
|
| 1833 |
+
|
| 1834 |
+
# calculate token count of content (without marker)
|
| 1835 |
+
content_token_count = len(self._previous_token_ids) - marker_len
|
| 1836 |
+
|
| 1837 |
+
# check if need to truncate content (keep marker)
|
| 1838 |
+
if content_token_count > max_tokens:
|
| 1839 |
+
# truncate left content, keep marker + latest max_tokens content
|
| 1840 |
+
tokens_to_drop = content_token_count - max_tokens
|
| 1841 |
+
old_text = self._previous_text
|
| 1842 |
+
# keep marker + truncated content
|
| 1843 |
+
content_tokens = self._previous_token_ids[marker_len + tokens_to_drop :]
|
| 1844 |
+
self._previous_token_ids = self._previous_marker_token_ids.copy() + content_tokens
|
| 1845 |
+
# redecode text (only decode content part)
|
| 1846 |
+
try:
|
| 1847 |
+
self._previous_text = self.tokenizer.decode(
|
| 1848 |
+
content_tokens,
|
| 1849 |
+
skip_special_tokens=True,
|
| 1850 |
+
)
|
| 1851 |
+
except Exception as e:
|
| 1852 |
+
logger.warning("_update_previous: decode failed: %s", e)
|
| 1853 |
+
|
| 1854 |
+
# rebuild cache
|
| 1855 |
+
self._rebuild_cache_with_previous(self._previous_token_ids)
|
| 1856 |
+
|
| 1857 |
+
def _drop_unit_with_context(
|
| 1858 |
+
self,
|
| 1859 |
+
unit_id: int,
|
| 1860 |
+
max_previous_tokens: int,
|
| 1861 |
+
) -> Tuple[bool, str, List[int]]:
|
| 1862 |
+
"""remove specified unit and return its generated content (for context preserving)
|
| 1863 |
+
|
| 1864 |
+
process:
|
| 1865 |
+
1. extract generated content of unit
|
| 1866 |
+
2. remove unit from cache (without prefix+previous)
|
| 1867 |
+
3. append generated content to previous
|
| 1868 |
+
4. rebuild cache (in _update_previous)
|
| 1869 |
+
|
| 1870 |
+
Args:
|
| 1871 |
+
unit_id: unit ID to remove
|
| 1872 |
+
max_previous_tokens: previous maximum token count
|
| 1873 |
+
|
| 1874 |
+
Returns:
|
| 1875 |
+
(success, extracted_text, extracted_tokens): whether successful, extracted text and tokens
|
| 1876 |
+
"""
|
| 1877 |
+
entries = [u for u in self._unit_history if u["unit_id"] == unit_id]
|
| 1878 |
+
if not entries:
|
| 1879 |
+
return False, "", []
|
| 1880 |
+
|
| 1881 |
+
# extract generated content
|
| 1882 |
+
extracted_text, extracted_tokens = self._extract_generated_text(entries)
|
| 1883 |
+
|
| 1884 |
+
# calculate total length
|
| 1885 |
+
total_len = sum(e["length"] for e in entries)
|
| 1886 |
+
if total_len <= 0:
|
| 1887 |
+
for e in entries:
|
| 1888 |
+
self._unit_history.remove(e)
|
| 1889 |
+
return False, extracted_text, extracted_tokens
|
| 1890 |
+
|
| 1891 |
+
cache_before = self.get_cache_length()
|
| 1892 |
+
|
| 1893 |
+
# remove from unit_history (record for later processing)
|
| 1894 |
+
for e in entries:
|
| 1895 |
+
self._unit_history.remove(e)
|
| 1896 |
+
|
| 1897 |
+
# note: here no longer call _drop_tokens_from_cache
|
| 1898 |
+
# because _update_previous will rebuild the entire cache
|
| 1899 |
+
|
| 1900 |
+
# update previous (also rebuild cache)
|
| 1901 |
+
self._update_previous(extracted_text, extracted_tokens, max_previous_tokens)
|
| 1902 |
+
|
| 1903 |
+
return True, extracted_text, extracted_tokens
|
| 1904 |
+
|
| 1905 |
+
def _drop_next_unit_with_context(self, max_previous_tokens: int) -> bool:
|
| 1906 |
+
"""remove the earliest non-system unit (with context preserving)"""
|
| 1907 |
+
for entry in self._unit_history:
|
| 1908 |
+
unit_id = entry.get("unit_id")
|
| 1909 |
+
if unit_id is None:
|
| 1910 |
+
continue
|
| 1911 |
+
if entry.get("type") == "system":
|
| 1912 |
+
continue
|
| 1913 |
+
success, _, _ = self._drop_unit_with_context(unit_id, max_previous_tokens)
|
| 1914 |
+
if success:
|
| 1915 |
+
return True
|
| 1916 |
+
return False
|
| 1917 |
+
|
| 1918 |
+
def enforce_window_with_context(self) -> bool:
|
| 1919 |
+
"""context preserving sliding window execution
|
| 1920 |
+
|
| 1921 |
+
when unit count exceeds max_units, remove the earliest unit,
|
| 1922 |
+
and accumulate its generated content to previous.
|
| 1923 |
+
Cache will be automatically rebuilt in _update_previous.
|
| 1924 |
+
|
| 1925 |
+
Returns:
|
| 1926 |
+
whether sliding window is executed
|
| 1927 |
+
"""
|
| 1928 |
+
if not self._window_enabled:
|
| 1929 |
+
return False
|
| 1930 |
+
|
| 1931 |
+
cfg = self._window_config
|
| 1932 |
+
|
| 1933 |
+
if cfg.sliding_window_mode != "context":
|
| 1934 |
+
# if not context mode, fallback to basic sliding window
|
| 1935 |
+
return self.enforce_window()
|
| 1936 |
+
|
| 1937 |
+
cache_len_before = self.get_cache_length()
|
| 1938 |
+
units_before = len(self._unit_history)
|
| 1939 |
+
|
| 1940 |
+
# context preserving mode: only check if unit count exceeds limit
|
| 1941 |
+
# (previous exceeds limit in _update_previous will automatically truncate left)
|
| 1942 |
+
if units_before <= cfg.context_max_units:
|
| 1943 |
+
return False
|
| 1944 |
+
|
| 1945 |
+
# sliding window loop: remove unit until count ≤ max_units
|
| 1946 |
+
dropped_count = 0
|
| 1947 |
+
while len(self._unit_history) > cfg.context_max_units:
|
| 1948 |
+
if not self._drop_next_unit_with_context(cfg.context_previous_max_tokens):
|
| 1949 |
+
break
|
| 1950 |
+
|
| 1951 |
+
dropped_count += 1
|
| 1952 |
+
|
| 1953 |
+
cache_len_after = self.get_cache_length()
|
| 1954 |
+
|
| 1955 |
+
if dropped_count > 0:
|
| 1956 |
+
# update statistics counter
|
| 1957 |
+
self._sliding_event_count += 1
|
| 1958 |
+
self._total_dropped_tokens += cache_len_before - cache_len_after
|
| 1959 |
+
self._total_dropped_units += dropped_count
|
| 1960 |
+
|
| 1961 |
+
# consistency check
|
| 1962 |
+
expected = self._system_preserve_length + sum(u["length"] for u in self._unit_history)
|
| 1963 |
+
|
| 1964 |
+
return dropped_count > 0
|
| 1965 |
+
|
| 1966 |
+
def get_previous_context(self) -> Tuple[str, List[int]]:
|
| 1967 |
+
"""get current accumulated previous context
|
| 1968 |
+
|
| 1969 |
+
Returns:
|
| 1970 |
+
(previous_text, previous_token_ids): current accumulated text and token ids
|
| 1971 |
+
"""
|
| 1972 |
+
return self._previous_text, self._previous_token_ids.copy()
|
| 1973 |
+
|
| 1974 |
+
def get_window_stats(self) -> Dict[str, Any]:
|
| 1975 |
+
"""get sliding window statistics"""
|
| 1976 |
+
unit_lengths = [u["length"] for u in self._unit_history]
|
| 1977 |
+
return {
|
| 1978 |
+
"cache_length": self.get_cache_length(),
|
| 1979 |
+
"unit_count": len(self._unit_history),
|
| 1980 |
+
"unit_lengths": unit_lengths,
|
| 1981 |
+
"unit_total_length": sum(unit_lengths),
|
| 1982 |
+
"system_preserve_length": self._system_preserve_length,
|
| 1983 |
+
"position_offset": self._position_offset,
|
| 1984 |
+
"window_enabled": self._window_enabled,
|
| 1985 |
+
"total_generated_tokens": self.get_total_generated_tokens(),
|
| 1986 |
+
"pending_unit_id": self._pending_unit_id,
|
| 1987 |
+
"next_unit_id": self._next_unit_id,
|
| 1988 |
+
"config": {
|
| 1989 |
+
"sliding_window_mode": self._window_config.sliding_window_mode,
|
| 1990 |
+
"basic_window_high_tokens": self._window_config.basic_window_high_tokens,
|
| 1991 |
+
"basic_window_low_tokens": self._window_config.basic_window_low_tokens,
|
| 1992 |
+
"context_previous_max_tokens": self._window_config.context_previous_max_tokens,
|
| 1993 |
+
"context_max_units": self._window_config.context_max_units,
|
| 1994 |
+
},
|
| 1995 |
+
# context preserving related
|
| 1996 |
+
"preserve_prefix_length": self._preserve_prefix_length,
|
| 1997 |
+
"previous_content_length": self._previous_content_length,
|
| 1998 |
+
"suffix_token_count": len(self._suffix_token_ids),
|
| 1999 |
+
"previous_text_length": len(self._previous_text),
|
| 2000 |
+
"previous_token_count": len(self._previous_token_ids),
|
| 2001 |
+
"has_system_template": self._system_prompt_template is not None,
|
| 2002 |
+
}
|
| 2003 |
+
|
| 2004 |
+
def _verify_consistency(self) -> bool:
|
| 2005 |
+
"""verify unit history and cache length consistency"""
|
| 2006 |
+
expected = self._system_preserve_length + sum(u["length"] for u in self._unit_history)
|
| 2007 |
+
actual = self.get_cache_length()
|
| 2008 |
+
return expected == actual
|
| 2009 |
+
|
| 2010 |
+
def print_verification_summary(self) -> Dict[str, Any]:
|
| 2011 |
+
"""print verification summary (for comparing off/basic/context mode)
|
| 2012 |
+
|
| 2013 |
+
Returns:
|
| 2014 |
+
dictionary containing key verification data
|
| 2015 |
+
"""
|
| 2016 |
+
cfg = self._window_config
|
| 2017 |
+
|
| 2018 |
+
# collect all generated text
|
| 2019 |
+
all_generated_text = []
|
| 2020 |
+
all_generated_tokens = []
|
| 2021 |
+
for u in self._unit_history:
|
| 2022 |
+
if not u.get("is_listen", False):
|
| 2023 |
+
gen_text = u.get("generated_text", "")
|
| 2024 |
+
gen_tokens = u.get("generated_tokens", [])
|
| 2025 |
+
if gen_text:
|
| 2026 |
+
all_generated_text.append(gen_text)
|
| 2027 |
+
if gen_tokens:
|
| 2028 |
+
all_generated_tokens.extend(gen_tokens)
|
| 2029 |
+
|
| 2030 |
+
combined_text = "".join(all_generated_text)
|
| 2031 |
+
|
| 2032 |
+
summary = {
|
| 2033 |
+
"mode": cfg.sliding_window_mode,
|
| 2034 |
+
"final_cache_length": self.get_cache_length(),
|
| 2035 |
+
"final_unit_count": len(self._unit_history),
|
| 2036 |
+
"sliding_event_count": self._sliding_event_count,
|
| 2037 |
+
"total_dropped_tokens": self._total_dropped_tokens,
|
| 2038 |
+
"total_dropped_units": self._total_dropped_units,
|
| 2039 |
+
"total_generated_tokens": len(all_generated_tokens),
|
| 2040 |
+
"generated_text": combined_text,
|
| 2041 |
+
"previous_text": self._previous_text,
|
| 2042 |
+
"previous_token_count": len(self._previous_token_ids),
|
| 2043 |
+
"position_offset": self._position_offset,
|
| 2044 |
+
"system_preserve_length": self._system_preserve_length,
|
| 2045 |
+
}
|
| 2046 |
+
|
| 2047 |
+
return summary
|
| 2048 |
+
|
| 2049 |
+
def set_window_config(self, config: DuplexWindowConfig) -> None:
|
| 2050 |
+
"""set sliding window configuration"""
|
| 2051 |
+
self._window_config = config
|
| 2052 |
+
|
| 2053 |
+
def set_window_enabled(self, enabled: bool) -> None:
|
| 2054 |
+
"""enable/disable sliding window"""
|
| 2055 |
+
old_enabled = self._window_enabled
|
| 2056 |
+
self._window_enabled = enabled
|
| 2057 |
+
|
| 2058 |
+
def get_context(self):
|
| 2059 |
+
return self.context
|
| 2060 |
+
|
| 2061 |
+
def embed_token(self, tid):
|
| 2062 |
+
if isinstance(tid, int):
|
| 2063 |
+
tid = torch.tensor([tid], device=self.m.device)
|
| 2064 |
+
return self.m.model.embed_tokens(tid)
|
| 2065 |
+
|
| 2066 |
+
def embed_tokens(self, token_ids: List[int]) -> torch.Tensor:
|
| 2067 |
+
"""batch embed multiple tokens
|
| 2068 |
+
|
| 2069 |
+
Args:
|
| 2070 |
+
token_ids: list of token ids
|
| 2071 |
+
|
| 2072 |
+
Returns:
|
| 2073 |
+
embeddings tensor [L, H]
|
| 2074 |
+
"""
|
| 2075 |
+
if not token_ids:
|
| 2076 |
+
return torch.empty(0, self.m.config.hidden_size, device=self.m.device)
|
| 2077 |
+
tids = torch.tensor(token_ids, device=self.m.device)
|
| 2078 |
+
return self.m.model.embed_tokens(tids)
|
| 2079 |
+
|
| 2080 |
+
@torch.no_grad()
|
| 2081 |
+
def feed(self, embeds: torch.Tensor, return_logits: bool = False):
|
| 2082 |
+
"""
|
| 2083 |
+
embeds : [L, H] —— new embedding sequence fed into model at once
|
| 2084 |
+
"""
|
| 2085 |
+
L = embeds.size(0)
|
| 2086 |
+
device = embeds.device
|
| 2087 |
+
|
| 2088 |
+
past_len = self.get_cache_length()
|
| 2089 |
+
pos_ids = torch.arange(past_len, past_len + L, device=device).unsqueeze(0) # [1, L]
|
| 2090 |
+
|
| 2091 |
+
out = self.m(
|
| 2092 |
+
inputs_embeds=embeds.unsqueeze(0), # [1, L, H]
|
| 2093 |
+
position_ids=pos_ids,
|
| 2094 |
+
past_key_values=self.cache,
|
| 2095 |
+
# use_cache = True,
|
| 2096 |
+
return_dict=True,
|
| 2097 |
+
output_hidden_states=True,
|
| 2098 |
+
# attention_mask=attention_mask
|
| 2099 |
+
)
|
| 2100 |
+
self.cache = out.past_key_values
|
| 2101 |
+
|
| 2102 |
+
if return_logits:
|
| 2103 |
+
logits = self.m.lm_head(out.hidden_states[-1])[:, -1] # [1, vocab]
|
| 2104 |
+
return logits, out.hidden_states[-1]
|
| 2105 |
+
|
| 2106 |
+
@torch.no_grad()
|
| 2107 |
+
def decode(
|
| 2108 |
+
self,
|
| 2109 |
+
logits,
|
| 2110 |
+
mode: Literal["sampling", "greedy"] = "sampling",
|
| 2111 |
+
temperature=0.7,
|
| 2112 |
+
top_k=20,
|
| 2113 |
+
top_p=0.8,
|
| 2114 |
+
listen_top_k=None,
|
| 2115 |
+
listen_prob_scale=1.0,
|
| 2116 |
+
text_repetition_penalty=1.05,
|
| 2117 |
+
text_repetition_window_size=512,
|
| 2118 |
+
):
|
| 2119 |
+
"""
|
| 2120 |
+
Args:
|
| 2121 |
+
logits:
|
| 2122 |
+
mode: sampling or greedy
|
| 2123 |
+
temperature:
|
| 2124 |
+
top_k:
|
| 2125 |
+
top_p:
|
| 2126 |
+
listen_top_k: force listen_id to be in top-k to keep
|
| 2127 |
+
listen_prob_scale: multiply listen_id probability by a weight (<1 means decrease, >1 means increase)
|
| 2128 |
+
text_repetition_penalty: repetition penalty coefficient, >1.0 means decrease repetition, <1.0 means increase repetition
|
| 2129 |
+
text_repetition_window_size: repetition penalty window size
|
| 2130 |
+
|
| 2131 |
+
Sampling strategy:
|
| 2132 |
+
1. first sample all tokens with original logits (apply temperature)
|
| 2133 |
+
2. if sampled chunk_eos, return directly (keep the original model's decision of when to stop)
|
| 2134 |
+
3. if not sampled chunk_eos, mask it (set logit to -inf), continue sampling text tokens
|
| 2135 |
+
4. apply repetition penalty, top-k, top-p, etc. to the text tokens for the final sampling
|
| 2136 |
+
"""
|
| 2137 |
+
|
| 2138 |
+
logits = logits.clone()
|
| 2139 |
+
|
| 2140 |
+
# 0. independently check chunk_eos before sampling
|
| 2141 |
+
eos_id = self.chunk_eos_id
|
| 2142 |
+
|
| 2143 |
+
with torch.no_grad():
|
| 2144 |
+
if mode == "greedy":
|
| 2145 |
+
sampled_token = torch.argmax(logits[0]).item()
|
| 2146 |
+
else:
|
| 2147 |
+
original_probs = F.softmax(logits[0], dim=-1)
|
| 2148 |
+
sampled_token = torch.multinomial(original_probs, num_samples=1).item()
|
| 2149 |
+
|
| 2150 |
+
# if sampled chunk_eos, return directly
|
| 2151 |
+
if sampled_token == eos_id:
|
| 2152 |
+
next_token_id = torch.tensor([eos_id], device=logits.device)
|
| 2153 |
+
next_token_str = self.tokenizer.decode(next_token_id)
|
| 2154 |
+
|
| 2155 |
+
return next_token_id
|
| 2156 |
+
|
| 2157 |
+
# if not sampled chunk_eos, set its logit to -inf
|
| 2158 |
+
if self.forbidden_token_ids:
|
| 2159 |
+
logits[:, self.forbidden_token_ids] = float("-inf")
|
| 2160 |
+
|
| 2161 |
+
# 1. apply repetition penalty
|
| 2162 |
+
if text_repetition_penalty != 1.0 and len(self.generated_tokens) > 0:
|
| 2163 |
+
# get recent tokens (within window size) considering special tokens and normal tokens
|
| 2164 |
+
recent_tokens = self.generated_tokens[-text_repetition_window_size:]
|
| 2165 |
+
|
| 2166 |
+
# make it unique
|
| 2167 |
+
recent_tokens = list(set(recent_tokens))
|
| 2168 |
+
|
| 2169 |
+
# apply penalty to repeated tokens
|
| 2170 |
+
for token_id in recent_tokens:
|
| 2171 |
+
if token_id < logits.size(-1): # ensure token_id is in vocabulary range
|
| 2172 |
+
if text_repetition_penalty > 1.0:
|
| 2173 |
+
# penalize repetition: decrease logits
|
| 2174 |
+
logits[0, token_id] /= text_repetition_penalty
|
| 2175 |
+
else:
|
| 2176 |
+
# encourage repetition: increase logits
|
| 2177 |
+
logits[0, token_id] *= 1.0 / text_repetition_penalty
|
| 2178 |
+
|
| 2179 |
+
if listen_prob_scale != 1.0: # modify listen token logit separately
|
| 2180 |
+
logits[0, self.listen_id] *= listen_prob_scale
|
| 2181 |
+
|
| 2182 |
+
listen_rank = (logits[0] > logits[0, self.listen_id]).sum().item()
|
| 2183 |
+
|
| 2184 |
+
if listen_top_k is not None and listen_rank < listen_top_k: # listen_id is in top-k, return directly
|
| 2185 |
+
next_token_id = torch.tensor([self.listen_id], device=logits.device)
|
| 2186 |
+
next_token_str = self.tokenizer.decode(next_token_id)
|
| 2187 |
+
|
| 2188 |
+
if next_token_str == "<|listen|>":
|
| 2189 |
+
self.context += " "
|
| 2190 |
+
else:
|
| 2191 |
+
self.context += next_token_str
|
| 2192 |
+
|
| 2193 |
+
return next_token_id
|
| 2194 |
+
|
| 2195 |
+
if mode == "greedy":
|
| 2196 |
+
next_token_id = torch.argmax(logits, dim=-1)
|
| 2197 |
+
elif mode == "sampling":
|
| 2198 |
+
logits = logits / temperature
|
| 2199 |
+
logits = top_k_top_p_filtering(logits, top_k=top_k, top_p=top_p)
|
| 2200 |
+
probs = F.softmax(logits, dim=-1)
|
| 2201 |
+
next_token_id = torch.multinomial(probs, num_samples=1).squeeze(1)
|
| 2202 |
+
else:
|
| 2203 |
+
raise ValueError(f"Unsupported decode mode: {mode}")
|
| 2204 |
+
|
| 2205 |
+
if next_token_id.item() not in self.special_token_ids:
|
| 2206 |
+
self.generated_tokens.append(next_token_id.item())
|
| 2207 |
+
else:
|
| 2208 |
+
self.generated_special_tokens.append(next_token_id.item())
|
| 2209 |
+
|
| 2210 |
+
return next_token_id
|
| 2211 |
+
|
| 2212 |
+
|
| 2213 |
+
def _download_url_to_tempfile(url: str, suffix: str = "", timeout: int = 60) -> str:
|
| 2214 |
+
"""
|
| 2215 |
+
Download a URL to a temporary file and return the path.
|
| 2216 |
+
|
| 2217 |
+
Args:
|
| 2218 |
+
url: HTTP/HTTPS URL to download
|
| 2219 |
+
suffix: File suffix (e.g., ".jpg", ".wav", ".mp4")
|
| 2220 |
+
timeout: Download timeout in seconds
|
| 2221 |
+
|
| 2222 |
+
Returns:
|
| 2223 |
+
Path to the downloaded temporary file
|
| 2224 |
+
"""
|
| 2225 |
+
import tempfile
|
| 2226 |
+
|
| 2227 |
+
import requests
|
| 2228 |
+
|
| 2229 |
+
response = requests.get(url, timeout=timeout)
|
| 2230 |
+
response.raise_for_status()
|
| 2231 |
+
|
| 2232 |
+
with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as f:
|
| 2233 |
+
f.write(response.content)
|
| 2234 |
+
return f.name
|
| 2235 |
+
|
| 2236 |
+
|
| 2237 |
+
def _is_url(path: str) -> bool:
|
| 2238 |
+
return path.startswith(("http://", "https://"))
|
| 2239 |
+
|
| 2240 |
+
|
| 2241 |
+
def normalize_content_item(item) -> Union[str, Any, List[Any]]:
|
| 2242 |
+
"""Normalize structured content item to native format.
|
| 2243 |
+
|
| 2244 |
+
Supports:
|
| 2245 |
+
- Native format: str, PIL.Image, np.ndarray (pass through)
|
| 2246 |
+
- OpenAI structured format:
|
| 2247 |
+
- {"type": "text", "text": "..."} -> str
|
| 2248 |
+
- {"type": "image_url", "image_url": {"url": "..."}} -> PIL.Image
|
| 2249 |
+
- {"type": "audio_url", "audio_url": {"url": "..."}} -> np.ndarray
|
| 2250 |
+
- {"type": "video_url", "video_url": {"url": "...", ...}} -> List[Image, ndarray, ...]
|
| 2251 |
+
|
| 2252 |
+
URL formats supported:
|
| 2253 |
+
- Local file path: "/path/to/file.jpg"
|
| 2254 |
+
- HTTP/HTTPS URL: "https://example.com/image.jpg"
|
| 2255 |
+
|
| 2256 |
+
Args:
|
| 2257 |
+
item: Content item to normalize
|
| 2258 |
+
|
| 2259 |
+
Returns:
|
| 2260 |
+
Normalized item. For video_url, returns a tuple ("__video_contents__", list)
|
| 2261 |
+
that will be flattened by normalize_content().
|
| 2262 |
+
|
| 2263 |
+
Raises:
|
| 2264 |
+
ValueError: If content type is unknown or unsupported
|
| 2265 |
+
"""
|
| 2266 |
+
import os
|
| 2267 |
+
|
| 2268 |
+
import numpy as np
|
| 2269 |
+
from PIL import Image
|
| 2270 |
+
|
| 2271 |
+
if isinstance(item, str):
|
| 2272 |
+
return item
|
| 2273 |
+
if isinstance(item, Image.Image):
|
| 2274 |
+
return item
|
| 2275 |
+
if isinstance(item, np.ndarray):
|
| 2276 |
+
return item
|
| 2277 |
+
|
| 2278 |
+
if isinstance(item, dict):
|
| 2279 |
+
item_type = item.get("type")
|
| 2280 |
+
|
| 2281 |
+
if item_type == "text":
|
| 2282 |
+
return item.get("text", "")
|
| 2283 |
+
|
| 2284 |
+
elif item_type == "image_url":
|
| 2285 |
+
image_url_obj = item.get("image_url", {})
|
| 2286 |
+
url = image_url_obj.get("url", "") if isinstance(image_url_obj, dict) else image_url_obj
|
| 2287 |
+
|
| 2288 |
+
if _is_url(url):
|
| 2289 |
+
# Download to temp file
|
| 2290 |
+
temp_path = _download_url_to_tempfile(url, suffix=".jpg", timeout=30)
|
| 2291 |
+
img = Image.open(temp_path)
|
| 2292 |
+
os.unlink(temp_path)
|
| 2293 |
+
return img
|
| 2294 |
+
else:
|
| 2295 |
+
return Image.open(url)
|
| 2296 |
+
elif item_type == "audio_url":
|
| 2297 |
+
import librosa
|
| 2298 |
+
|
| 2299 |
+
audio_url_obj = item.get("audio_url", {})
|
| 2300 |
+
url = audio_url_obj.get("url", "") if isinstance(audio_url_obj, dict) else audio_url_obj
|
| 2301 |
+
|
| 2302 |
+
if _is_url(url):
|
| 2303 |
+
# Download to temp file
|
| 2304 |
+
temp_path = _download_url_to_tempfile(url, suffix=".wav", timeout=60)
|
| 2305 |
+
audio_np, _ = librosa.load(temp_path, sr=16000, mono=True)
|
| 2306 |
+
os.unlink(temp_path)
|
| 2307 |
+
return audio_np
|
| 2308 |
+
else:
|
| 2309 |
+
audio_np, _ = librosa.load(url, sr=16000, mono=True)
|
| 2310 |
+
return audio_np
|
| 2311 |
+
elif item_type == "video_url":
|
| 2312 |
+
# Video processing - returns a LIST of items (frames + audio segments)
|
| 2313 |
+
# Note: Unlike image_url/audio_url which return single items,
|
| 2314 |
+
# video_url returns a list that will be flattened into the content
|
| 2315 |
+
from minicpmo.utils import get_video_frame_audio_segments
|
| 2316 |
+
|
| 2317 |
+
video_url_obj = item.get("video_url", {})
|
| 2318 |
+
if isinstance(video_url_obj, dict):
|
| 2319 |
+
video_url = video_url_obj.get("url", "")
|
| 2320 |
+
# Get optional parameters from video_url object (OpenAI style)
|
| 2321 |
+
stack_frames = video_url_obj.get("stack_frames", 1)
|
| 2322 |
+
use_ffmpeg = video_url_obj.get("use_ffmpeg", False)
|
| 2323 |
+
use_audio = video_url_obj.get("use_audio", True)
|
| 2324 |
+
else:
|
| 2325 |
+
video_url = video_url_obj
|
| 2326 |
+
stack_frames = 1
|
| 2327 |
+
use_ffmpeg = False
|
| 2328 |
+
use_audio = True
|
| 2329 |
+
|
| 2330 |
+
# Handle HTTP/HTTPS URL - download to temp file
|
| 2331 |
+
temp_video_path = None
|
| 2332 |
+
if _is_url(video_url):
|
| 2333 |
+
temp_video_path = _download_url_to_tempfile(video_url, suffix=".mp4", timeout=120)
|
| 2334 |
+
video_path = temp_video_path
|
| 2335 |
+
else:
|
| 2336 |
+
video_path = video_url
|
| 2337 |
+
|
| 2338 |
+
# Extract frames and audio segments
|
| 2339 |
+
video_frames, audio_segments, stacked_frames = get_video_frame_audio_segments(
|
| 2340 |
+
video_path,
|
| 2341 |
+
stack_frames=stack_frames,
|
| 2342 |
+
use_ffmpeg=use_ffmpeg,
|
| 2343 |
+
use_audio=use_audio
|
| 2344 |
+
)
|
| 2345 |
+
|
| 2346 |
+
# Clean up temp file if downloaded
|
| 2347 |
+
if temp_video_path is not None:
|
| 2348 |
+
os.unlink(temp_video_path)
|
| 2349 |
+
|
| 2350 |
+
# Build omni_contents (interleaved frames and audio, or frames only)
|
| 2351 |
+
omni_contents = []
|
| 2352 |
+
for i in range(len(video_frames)):
|
| 2353 |
+
omni_contents.append(video_frames[i])
|
| 2354 |
+
if use_audio and audio_segments is not None:
|
| 2355 |
+
omni_contents.append(audio_segments[i])
|
| 2356 |
+
if stacked_frames is not None and i < len(stacked_frames) and stacked_frames[i] is not None:
|
| 2357 |
+
omni_contents.append(stacked_frames[i])
|
| 2358 |
+
|
| 2359 |
+
# Return as a special marker to be flattened later
|
| 2360 |
+
return "__video_contents__", omni_contents
|
| 2361 |
+
else:
|
| 2362 |
+
raise ValueError(f"Unknown content type: {item_type}")
|
| 2363 |
+
|
| 2364 |
+
raise ValueError(f"Cannot normalize content item of type: {type(item)}")
|
| 2365 |
+
|
| 2366 |
+
|
| 2367 |
+
def normalize_content(content) -> list:
|
| 2368 |
+
"""Normalize message content to list of native items.
|
| 2369 |
+
|
| 2370 |
+
Input formats:
|
| 2371 |
+
- str: "hello" -> ["hello"]
|
| 2372 |
+
- list of native items: [str, Image, np.ndarray] -> pass through with normalization
|
| 2373 |
+
- list of structured items: [{"type": "text", ...}] -> normalize each
|
| 2374 |
+
- video type: automatically expanded to omni_contents
|
| 2375 |
+
- mixed: works too
|
| 2376 |
+
|
| 2377 |
+
Args:
|
| 2378 |
+
content: Message content in any supported format
|
| 2379 |
+
|
| 2380 |
+
Returns:
|
| 2381 |
+
List of native items (str, PIL.Image, np.ndarray)
|
| 2382 |
+
|
| 2383 |
+
Examples:
|
| 2384 |
+
>>> normalize_content("hello")
|
| 2385 |
+
["hello"]
|
| 2386 |
+
|
| 2387 |
+
>>> normalize_content([{"type": "text", "text": "hi"}])
|
| 2388 |
+
["hi"]
|
| 2389 |
+
|
| 2390 |
+
>>> normalize_content([{"type": "video", "video": "/path/to/video.mp4"}])
|
| 2391 |
+
[<PIL.Image>, <np.ndarray>, <PIL.Image>, <np.ndarray>, ...]
|
| 2392 |
+
"""
|
| 2393 |
+
import numpy as np
|
| 2394 |
+
from PIL import Image
|
| 2395 |
+
|
| 2396 |
+
if isinstance(content, str):
|
| 2397 |
+
return [content]
|
| 2398 |
+
|
| 2399 |
+
if isinstance(content, list):
|
| 2400 |
+
result = []
|
| 2401 |
+
for item in content:
|
| 2402 |
+
normalized = normalize_content_item(item)
|
| 2403 |
+
# Handle video content (returns tuple with marker)
|
| 2404 |
+
if isinstance(normalized, tuple) and len(normalized) == 2 and normalized[0] == "__video_contents__":
|
| 2405 |
+
# Flatten video contents into result
|
| 2406 |
+
result.extend(normalized[1])
|
| 2407 |
+
else:
|
| 2408 |
+
result.append(normalized)
|
| 2409 |
+
return result
|
| 2410 |
+
|
| 2411 |
+
# Single non-list item (Image or np.ndarray)
|
| 2412 |
+
if isinstance(content, (Image.Image, np.ndarray)):
|
| 2413 |
+
return [content]
|
| 2414 |
+
|
| 2415 |
+
normalized = normalize_content_item(content)
|
| 2416 |
+
if isinstance(normalized, tuple) and len(normalized) == 2 and normalized[0] == "__video_contents__":
|
| 2417 |
+
return normalized[1]
|
| 2418 |
+
return [normalized]
|
Realtime-Venus-Audio/vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Realtime-Venus-Omni/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright OpenBMB
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
Realtime-Venus-Omni/NOTICE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Realtime-Venus-Omni
|
| 2 |
+
Copyright 2026 The Realtime-Venus Team. All rights reserved.
|
| 3 |
+
|
| 4 |
+
This product includes software derived from MiniCPM-o 4.5
|
| 5 |
+
(https://github.com/OpenBMB/MiniCPM-o), Copyright The OpenBMB Team,
|
| 6 |
+
licensed under the Apache License, Version 2.0.
|
| 7 |
+
|
| 8 |
+
Modifications include the Realtime-Venus identity and system prompts,
|
| 9 |
+
full-duplex delegation output handling, the long-video Memory runtime
|
| 10 |
+
under memory_adapter/, and rebranded module, class, and configuration names.
|
Realtime-Venus-Omni/added_tokens.json
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</answer>": 151686,
|
| 3 |
+
"</box>": 151674,
|
| 4 |
+
"</focus>": 151688,
|
| 5 |
+
"</image>": 151670,
|
| 6 |
+
"</image_id>": 151682,
|
| 7 |
+
"</image_save_to>": 151696,
|
| 8 |
+
"</line>": 151690,
|
| 9 |
+
"</perception>": 151692,
|
| 10 |
+
"</point>": 151678,
|
| 11 |
+
"</quad>": 151676,
|
| 12 |
+
"</ref>": 151672,
|
| 13 |
+
"</slice>": 151680,
|
| 14 |
+
"</source_image>": 151694,
|
| 15 |
+
"</think>": 151668,
|
| 16 |
+
"</tool_call>": 151658,
|
| 17 |
+
"</tool_response>": 151666,
|
| 18 |
+
"</unit>": 151684,
|
| 19 |
+
"<answer>": 151685,
|
| 20 |
+
"<box>": 151673,
|
| 21 |
+
"<focus>": 151687,
|
| 22 |
+
"<image>": 151669,
|
| 23 |
+
"<image_id>": 151681,
|
| 24 |
+
"<image_save_to>": 151695,
|
| 25 |
+
"<line>": 151689,
|
| 26 |
+
"<perception>": 151691,
|
| 27 |
+
"<point>": 151677,
|
| 28 |
+
"<quad>": 151675,
|
| 29 |
+
"<ref>": 151671,
|
| 30 |
+
"<slice>": 151679,
|
| 31 |
+
"<source_image>": 151693,
|
| 32 |
+
"<think>": 151667,
|
| 33 |
+
"<tool_call>": 151657,
|
| 34 |
+
"<tool_response>": 151665,
|
| 35 |
+
"<unit>": 151683,
|
| 36 |
+
"<|audio_end|>": 151699,
|
| 37 |
+
"<|audio_start|>": 151697,
|
| 38 |
+
"<|audio|>": 151698,
|
| 39 |
+
"<|box_end|>": 151649,
|
| 40 |
+
"<|box_start|>": 151648,
|
| 41 |
+
"<|emotion_end|>": 151711,
|
| 42 |
+
"<|emotion_start|>": 151710,
|
| 43 |
+
"<|endoftext|>": 151643,
|
| 44 |
+
"<|file_sep|>": 151664,
|
| 45 |
+
"<|fim_middle|>": 151660,
|
| 46 |
+
"<|fim_pad|>": 151662,
|
| 47 |
+
"<|fim_prefix|>": 151659,
|
| 48 |
+
"<|fim_suffix|>": 151661,
|
| 49 |
+
"<|im_end|>": 151645,
|
| 50 |
+
"<|im_start|>": 151644,
|
| 51 |
+
"<|image_pad|>": 151655,
|
| 52 |
+
"<|interrupt|>": 151707,
|
| 53 |
+
"<|listen|>": 151705,
|
| 54 |
+
"<|object_ref_end|>": 151647,
|
| 55 |
+
"<|object_ref_start|>": 151646,
|
| 56 |
+
"<|pitch_end|>": 151715,
|
| 57 |
+
"<|pitch_start|>": 151714,
|
| 58 |
+
"<|quad_end|>": 151651,
|
| 59 |
+
"<|quad_start|>": 151650,
|
| 60 |
+
"<|repo_name|>": 151663,
|
| 61 |
+
"<|speak|>": 151706,
|
| 62 |
+
"<|speed_end|>": 151713,
|
| 63 |
+
"<|speed_start|>": 151712,
|
| 64 |
+
"<|spk_bos|>": 151700,
|
| 65 |
+
"<|spk_eos|>": 151702,
|
| 66 |
+
"<|spk|>": 151701,
|
| 67 |
+
"<|turn_bos|>": 151716,
|
| 68 |
+
"<|timbre_10|>": 151726,
|
| 69 |
+
"<|timbre_11|>": 151727,
|
| 70 |
+
"<|timbre_12|>": 151728,
|
| 71 |
+
"<|timbre_13|>": 151729,
|
| 72 |
+
"<|timbre_14|>": 151730,
|
| 73 |
+
"<|timbre_15|>": 151731,
|
| 74 |
+
"<|timbre_16|>": 151732,
|
| 75 |
+
"<|timbre_17|>": 151733,
|
| 76 |
+
"<|timbre_18|>": 151734,
|
| 77 |
+
"<|timbre_19|>": 151735,
|
| 78 |
+
"<|turn_eos|>": 151717,
|
| 79 |
+
"<|timbre_20|>": 151736,
|
| 80 |
+
"<|timbre_21|>": 151737,
|
| 81 |
+
"<|timbre_22|>": 151738,
|
| 82 |
+
"<|timbre_23|>": 151739,
|
| 83 |
+
"<|timbre_24|>": 151740,
|
| 84 |
+
"<|timbre_25|>": 151741,
|
| 85 |
+
"<|timbre_26|>": 151742,
|
| 86 |
+
"<|timbre_27|>": 151743,
|
| 87 |
+
"<|timbre_28|>": 151744,
|
| 88 |
+
"<|timbre_29|>": 151745,
|
| 89 |
+
"<|chunk_eos|>": 151718,
|
| 90 |
+
"<|timbre_30|>": 151746,
|
| 91 |
+
"<|timbre_31|>": 151747,
|
| 92 |
+
"<|chunk_bos|>": 151719,
|
| 93 |
+
"<|chunk_tts_bos|>": 151720,
|
| 94 |
+
"<|chunk_tts_eos|>": 151721,
|
| 95 |
+
"<|tts_pad|>": 151722,
|
| 96 |
+
"<|timbre_7|>": 151723,
|
| 97 |
+
"<|timbre_8|>": 151724,
|
| 98 |
+
"<|timbre_9|>": 151725,
|
| 99 |
+
"<|tts_bos|>": 151703,
|
| 100 |
+
"<|tts_eos|>": 151704,
|
| 101 |
+
"<|vad_end|>": 151709,
|
| 102 |
+
"<|vad_start|>": 151708,
|
| 103 |
+
"<|video_pad|>": 151656,
|
| 104 |
+
"<|vision_end|>": 151653,
|
| 105 |
+
"<|vision_pad|>": 151654,
|
| 106 |
+
"<|vision_start|>": 151652,
|
| 107 |
+
"<delegate>": 151748,
|
| 108 |
+
"</delegate>": 151749,
|
| 109 |
+
"<backend>": 151750,
|
| 110 |
+
"</backend>": 151751
|
| 111 |
+
}
|
Realtime-Venus-Omni/assets/HT_ref_audio.wav
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cb8f06ba5080cdf548969138881fb8ad8b04e2516108f4e08ba0363b68b613ea
|
| 3 |
+
size 192590
|
Realtime-Venus-Omni/assets/sample_1_real.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ba1aa1242318beef20e56dd3b19e5cf15503a121fe1a348a6366926fb86a4146
|
| 3 |
+
size 95813537
|
Realtime-Venus-Omni/assets/speech_in.mp4
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d44faf7742d6597440c1fbb94dc87e4db27086af8cf549f6fc28fdd2020b0155
|
| 3 |
+
size 1691744
|
Realtime-Venus-Omni/assets/token2wav/campplus.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a6ac6a63997761ae2997373e2ee1c47040854b4b759ea41ec48e4e42df0f4d73
|
| 3 |
+
size 28303423
|
Realtime-Venus-Omni/assets/token2wav/flow.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:15ccff24256ff61537c7f8b51e025116b83405f3fb017b54b008fc97da115446
|
| 3 |
+
size 623466603
|
Realtime-Venus-Omni/assets/token2wav/flow.yaml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flow: !new:cosyvoice2.flow.flow.CausalMaskedDiffWithXvec
|
| 2 |
+
input_size: 512
|
| 3 |
+
output_size: 80
|
| 4 |
+
spk_embed_dim: 192
|
| 5 |
+
output_type: 'mel'
|
| 6 |
+
vocab_size: 6561
|
| 7 |
+
encoder: !new:cosyvoice2.transformer.upsample_encoder_v2.UpsampleConformerEncoderV2
|
| 8 |
+
input_size: 512
|
| 9 |
+
output_size: 512
|
| 10 |
+
input_layer: 'linear'
|
| 11 |
+
pre_lookahead_len: 3
|
| 12 |
+
num_blocks: 6
|
| 13 |
+
num_up_blocks: 4
|
| 14 |
+
up_stride: 2
|
| 15 |
+
up_scale_factor: 2
|
| 16 |
+
attention_heads: 8
|
| 17 |
+
pos_enc_layer_type: 'rel_pos_espnet'
|
| 18 |
+
selfattention_layer_type: 'rel_selfattn'
|
| 19 |
+
key_bias: true
|
| 20 |
+
linear_units: 2048
|
| 21 |
+
dropout_rate: 0.1
|
| 22 |
+
positional_dropout_rate: 0.1
|
| 23 |
+
attention_dropout_rate: 0.1
|
| 24 |
+
normalize_before: True
|
| 25 |
+
decoder: !new:cosyvoice2.flow.flow_matching.CausalConditionalCFM
|
| 26 |
+
inference_cfg_rate: 0.7
|
| 27 |
+
estimator: !new:cosyvoice2.flow.decoder_dit.DiT
|
| 28 |
+
in_channels: 320
|
| 29 |
+
out_channels: 80
|
| 30 |
+
mlp_ratio: 4.0
|
| 31 |
+
depth: 16
|
| 32 |
+
num_heads: 8
|
| 33 |
+
head_dim: 64
|
| 34 |
+
hidden_size: 512
|
Realtime-Venus-Omni/assets/token2wav/hift.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3386cc880324d4e98e05987b99107f49e40ed925b8ecc87c1f4939432d429879
|
| 3 |
+
size 83390254
|
Realtime-Venus-Omni/assets/token2wav/speech_tokenizer_v2_25hz.onnx
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d43342aa12163a80bf07bffb94c9de2e120a8df2f9917cd2f642e7f4219c6f71
|
| 3 |
+
size 496082973
|
Realtime-Venus-Omni/config.json
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"RealtimeVenusOmni"
|
| 4 |
+
],
|
| 5 |
+
"version": "4.5",
|
| 6 |
+
"attention_bias": false,
|
| 7 |
+
"attention_dropout": 0.0,
|
| 8 |
+
"audio_chunk_length": 1.0,
|
| 9 |
+
"audio_config": {
|
| 10 |
+
"_attn_implementation_autoset": true,
|
| 11 |
+
"_name_or_path": "openai/whisper-medium",
|
| 12 |
+
"activation_dropout": 0.0,
|
| 13 |
+
"activation_function": "gelu",
|
| 14 |
+
"apply_spec_augment": false,
|
| 15 |
+
"architectures": [
|
| 16 |
+
"RealtimeVenusOmniWhisperEncoder"
|
| 17 |
+
],
|
| 18 |
+
"attention_dropout": 0.0,
|
| 19 |
+
"begin_suppress_tokens": [
|
| 20 |
+
220,
|
| 21 |
+
50257
|
| 22 |
+
],
|
| 23 |
+
"bos_token_id": 50257,
|
| 24 |
+
"classifier_proj_size": 256,
|
| 25 |
+
"d_model": 1024,
|
| 26 |
+
"decoder_attention_heads": 16,
|
| 27 |
+
"decoder_ffn_dim": 4096,
|
| 28 |
+
"decoder_layerdrop": 0.0,
|
| 29 |
+
"decoder_layers": 24,
|
| 30 |
+
"decoder_start_token_id": 50258,
|
| 31 |
+
"dropout": 0.0,
|
| 32 |
+
"encoder_attention_heads": 16,
|
| 33 |
+
"encoder_ffn_dim": 4096,
|
| 34 |
+
"encoder_layerdrop": 0.0,
|
| 35 |
+
"encoder_layers": 24,
|
| 36 |
+
"eos_token_id": 50257,
|
| 37 |
+
"forced_decoder_ids": [
|
| 38 |
+
[
|
| 39 |
+
1,
|
| 40 |
+
50259
|
| 41 |
+
],
|
| 42 |
+
[
|
| 43 |
+
2,
|
| 44 |
+
50359
|
| 45 |
+
],
|
| 46 |
+
[
|
| 47 |
+
3,
|
| 48 |
+
50363
|
| 49 |
+
]
|
| 50 |
+
],
|
| 51 |
+
"init_std": 0.02,
|
| 52 |
+
"mask_feature_length": 10,
|
| 53 |
+
"mask_feature_min_masks": 0,
|
| 54 |
+
"mask_feature_prob": 0.0,
|
| 55 |
+
"mask_time_length": 10,
|
| 56 |
+
"mask_time_min_masks": 2,
|
| 57 |
+
"mask_time_prob": 0.05,
|
| 58 |
+
"max_length": 448,
|
| 59 |
+
"max_source_positions": 1500,
|
| 60 |
+
"max_target_positions": 448,
|
| 61 |
+
"median_filter_width": 7,
|
| 62 |
+
"model_type": "whisper",
|
| 63 |
+
"num_hidden_layers": 24,
|
| 64 |
+
"num_mel_bins": 80,
|
| 65 |
+
"pad_token_id": 50257,
|
| 66 |
+
"scale_embedding": false,
|
| 67 |
+
"suppress_tokens": [
|
| 68 |
+
1,
|
| 69 |
+
2,
|
| 70 |
+
7,
|
| 71 |
+
8,
|
| 72 |
+
9,
|
| 73 |
+
10,
|
| 74 |
+
14,
|
| 75 |
+
25,
|
| 76 |
+
26,
|
| 77 |
+
27,
|
| 78 |
+
28,
|
| 79 |
+
29,
|
| 80 |
+
31,
|
| 81 |
+
58,
|
| 82 |
+
59,
|
| 83 |
+
60,
|
| 84 |
+
61,
|
| 85 |
+
62,
|
| 86 |
+
63,
|
| 87 |
+
90,
|
| 88 |
+
91,
|
| 89 |
+
92,
|
| 90 |
+
93,
|
| 91 |
+
359,
|
| 92 |
+
503,
|
| 93 |
+
522,
|
| 94 |
+
542,
|
| 95 |
+
873,
|
| 96 |
+
893,
|
| 97 |
+
902,
|
| 98 |
+
918,
|
| 99 |
+
922,
|
| 100 |
+
931,
|
| 101 |
+
1350,
|
| 102 |
+
1853,
|
| 103 |
+
1982,
|
| 104 |
+
2460,
|
| 105 |
+
2627,
|
| 106 |
+
3246,
|
| 107 |
+
3253,
|
| 108 |
+
3268,
|
| 109 |
+
3536,
|
| 110 |
+
3846,
|
| 111 |
+
3961,
|
| 112 |
+
4183,
|
| 113 |
+
4667,
|
| 114 |
+
6585,
|
| 115 |
+
6647,
|
| 116 |
+
7273,
|
| 117 |
+
9061,
|
| 118 |
+
9383,
|
| 119 |
+
10428,
|
| 120 |
+
10929,
|
| 121 |
+
11938,
|
| 122 |
+
12033,
|
| 123 |
+
12331,
|
| 124 |
+
12562,
|
| 125 |
+
13793,
|
| 126 |
+
14157,
|
| 127 |
+
14635,
|
| 128 |
+
15265,
|
| 129 |
+
15618,
|
| 130 |
+
16553,
|
| 131 |
+
16604,
|
| 132 |
+
18362,
|
| 133 |
+
18956,
|
| 134 |
+
20075,
|
| 135 |
+
21675,
|
| 136 |
+
22520,
|
| 137 |
+
26130,
|
| 138 |
+
26161,
|
| 139 |
+
26435,
|
| 140 |
+
28279,
|
| 141 |
+
29464,
|
| 142 |
+
31650,
|
| 143 |
+
32302,
|
| 144 |
+
32470,
|
| 145 |
+
36865,
|
| 146 |
+
42863,
|
| 147 |
+
47425,
|
| 148 |
+
49870,
|
| 149 |
+
50254,
|
| 150 |
+
50258,
|
| 151 |
+
50358,
|
| 152 |
+
50359,
|
| 153 |
+
50360,
|
| 154 |
+
50361,
|
| 155 |
+
50362
|
| 156 |
+
],
|
| 157 |
+
"torch_dtype": "float32",
|
| 158 |
+
"use_cache": true,
|
| 159 |
+
"use_weighted_layer_sum": false,
|
| 160 |
+
"vocab_size": 51865
|
| 161 |
+
},
|
| 162 |
+
"audio_pool_step": 5,
|
| 163 |
+
"auto_map": {
|
| 164 |
+
"AutoConfig": "configuration_realtime_venus_omni.RealtimeVenusOmniConfig",
|
| 165 |
+
"AutoModel": "modeling_realtime_venus_omni.RealtimeVenusOmni",
|
| 166 |
+
"AutoModelForCausalLM": "modeling_realtime_venus_omni.RealtimeVenusOmni"
|
| 167 |
+
},
|
| 168 |
+
"batch_vision_input": true,
|
| 169 |
+
"bos_token_id": 151643,
|
| 170 |
+
"drop_vision_last_layer": false,
|
| 171 |
+
"eos_token_id": 151645,
|
| 172 |
+
"head_dim": 128,
|
| 173 |
+
"hidden_act": "silu",
|
| 174 |
+
"hidden_size": 4096,
|
| 175 |
+
"image_size": 448,
|
| 176 |
+
"init_audio": true,
|
| 177 |
+
"init_tts": true,
|
| 178 |
+
"init_vision": true,
|
| 179 |
+
"initializer_range": 0.02,
|
| 180 |
+
"intermediate_size": 12288,
|
| 181 |
+
"listen_speak_type": "asr",
|
| 182 |
+
"max_position_embeddings": 40960,
|
| 183 |
+
"max_window_layers": 36,
|
| 184 |
+
"model_type": "realtime_venus_omni",
|
| 185 |
+
"num_attention_heads": 32,
|
| 186 |
+
"num_hidden_layers": 36,
|
| 187 |
+
"num_key_value_heads": 8,
|
| 188 |
+
"patch_size": 14,
|
| 189 |
+
"query_num": 64,
|
| 190 |
+
"rms_norm_eps": 1e-06,
|
| 191 |
+
"rope_scaling": null,
|
| 192 |
+
"rope_theta": 1000000,
|
| 193 |
+
"slice_config": {
|
| 194 |
+
"max_slice_nums": 1,
|
| 195 |
+
"model_type": "realtime_venus_omni_vision",
|
| 196 |
+
"patch_size": 14,
|
| 197 |
+
"scale_resolution": 448
|
| 198 |
+
},
|
| 199 |
+
"slice_mode": true,
|
| 200 |
+
"sliding_window": null,
|
| 201 |
+
"stream_input": true,
|
| 202 |
+
"tie_word_embeddings": false,
|
| 203 |
+
"torch_dtype": "bfloat16",
|
| 204 |
+
"transformers_version": "4.51.0",
|
| 205 |
+
"tts_config": {
|
| 206 |
+
"_attn_implementation_autoset": true,
|
| 207 |
+
"attention_type": "full_attention",
|
| 208 |
+
"attn_implementation": "sdpa",
|
| 209 |
+
"audio_bos_token_id": 151687,
|
| 210 |
+
"audio_tokenizer_sample_rate": 16000,
|
| 211 |
+
"audio_tokenizer_type": "s3tokenizer",
|
| 212 |
+
"aug_layer_loss_weight": false,
|
| 213 |
+
"aug_loss_weight": false,
|
| 214 |
+
"backbone_model": "llama",
|
| 215 |
+
"condition_type": "hidden_text_merge",
|
| 216 |
+
"cosyvoice_config_path": null,
|
| 217 |
+
"cosyvoice_model_dir": null,
|
| 218 |
+
"filter_tts_loss": false,
|
| 219 |
+
"hidden_act": "silu",
|
| 220 |
+
"hidden_size": 768,
|
| 221 |
+
"interleaved": false,
|
| 222 |
+
"intermediate_size": 3072,
|
| 223 |
+
"llm_dim": 4096,
|
| 224 |
+
"llm_dim_model_base": 256,
|
| 225 |
+
"llm_down_scale": false,
|
| 226 |
+
"llm_hidden_size": 4096,
|
| 227 |
+
"llm_intermediate_size": 768,
|
| 228 |
+
"long_weight": 0.1,
|
| 229 |
+
"max_position_embeddings": 4096,
|
| 230 |
+
"model_type": "realtime_venus_omni_tts",
|
| 231 |
+
"normalize_projected_hidden": true,
|
| 232 |
+
"num_attention_heads": 12,
|
| 233 |
+
"num_audio_tokens": 6562,
|
| 234 |
+
"num_hidden_layers": 20,
|
| 235 |
+
"num_key_value_heads": 12,
|
| 236 |
+
"num_mel_bins": 100,
|
| 237 |
+
"num_text_tokens": 152064,
|
| 238 |
+
"num_vq": 1,
|
| 239 |
+
"projector_type": "mlp",
|
| 240 |
+
"recomputed_chunks": 1,
|
| 241 |
+
"s3_stream_chunk_size": 25,
|
| 242 |
+
"s3_stream_generate": false,
|
| 243 |
+
"s3_stream_n_timesteps": 10,
|
| 244 |
+
"s3_stream_prelook_size": 3,
|
| 245 |
+
"short_weight": 0.1,
|
| 246 |
+
"streaming": false,
|
| 247 |
+
"streaming_audio_chunk_size": 50,
|
| 248 |
+
"streaming_sliding_window": false,
|
| 249 |
+
"streaming_sliding_window_audio_frame_rate": 50,
|
| 250 |
+
"streaming_sliding_window_audio_init_text_length": 10,
|
| 251 |
+
"streaming_sliding_window_audio_window_size": 300,
|
| 252 |
+
"streaming_sliding_window_average_speed": 5,
|
| 253 |
+
"streaming_sliding_window_fast_speed": 7,
|
| 254 |
+
"streaming_sliding_window_max_text_len": 500,
|
| 255 |
+
"streaming_sliding_window_slow_speed": 3,
|
| 256 |
+
"streaming_sliding_window_text_window_size": 50,
|
| 257 |
+
"streaming_text_chunk_max": 7,
|
| 258 |
+
"streaming_text_chunk_min": 3,
|
| 259 |
+
"streaming_text_reserved_len": 300,
|
| 260 |
+
"text_eos_token_id": 151692,
|
| 261 |
+
"tts_filter_loss_fix": false,
|
| 262 |
+
"use_llm_hidden_state": false,
|
| 263 |
+
"use_text": true,
|
| 264 |
+
"window_size": 2
|
| 265 |
+
},
|
| 266 |
+
"use_cache": true,
|
| 267 |
+
"use_image_id": true,
|
| 268 |
+
"use_sliding_window": false,
|
| 269 |
+
"vision_batch_size": 16,
|
| 270 |
+
"vision_config": {
|
| 271 |
+
"_attn_implementation_autoset": true,
|
| 272 |
+
"attention_dropout": 0.0,
|
| 273 |
+
"hidden_act": "gelu_pytorch_tanh",
|
| 274 |
+
"hidden_size": 1152,
|
| 275 |
+
"image_size": 980,
|
| 276 |
+
"intermediate_size": 4304,
|
| 277 |
+
"layer_norm_eps": 1e-06,
|
| 278 |
+
"model_type": "siglip_vision_model",
|
| 279 |
+
"num_attention_heads": 16,
|
| 280 |
+
"num_channels": 3,
|
| 281 |
+
"num_hidden_layers": 27,
|
| 282 |
+
"patch_size": 14
|
| 283 |
+
},
|
| 284 |
+
"vocab_size": 151752
|
| 285 |
+
}
|
Realtime-Venus-Omni/configuration_realtime_venus_omni.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
# Copyright 2026 The OpenBMB Team. All rights reserved.
|
| 4 |
+
# Modifications Copyright 2026 The Realtime-Venus Team.
|
| 5 |
+
#
|
| 6 |
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
| 7 |
+
# you may not use this file except in compliance with the License.
|
| 8 |
+
# You may obtain a copy of the License at
|
| 9 |
+
#
|
| 10 |
+
# http://www.apache.org/licenses/LICENSE-2.0
|
| 11 |
+
#
|
| 12 |
+
# Unless required by applicable law or agreed to in writing, software
|
| 13 |
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
| 14 |
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 15 |
+
# See the License for the specific language governing permissions and
|
| 16 |
+
# limitations under the License.
|
| 17 |
+
|
| 18 |
+
import os
|
| 19 |
+
from typing import Union
|
| 20 |
+
|
| 21 |
+
from transformers import PretrainedConfig
|
| 22 |
+
from transformers import Qwen3Config
|
| 23 |
+
from transformers import WhisperConfig
|
| 24 |
+
from transformers.utils import logging
|
| 25 |
+
|
| 26 |
+
from .modeling_navit_siglip import SiglipVisionConfig
|
| 27 |
+
|
| 28 |
+
logger = logging.get_logger(__name__)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class RealtimeVenusOmniSliceConfig(PretrainedConfig):
|
| 32 |
+
model_type = "realtime_venus_omni_vision"
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
patch_size=14,
|
| 37 |
+
max_slice_nums=9,
|
| 38 |
+
scale_resolution=448,
|
| 39 |
+
**kwargs,
|
| 40 |
+
):
|
| 41 |
+
super().__init__(**kwargs)
|
| 42 |
+
self.patch_size = patch_size
|
| 43 |
+
self.max_slice_nums = max_slice_nums
|
| 44 |
+
self.scale_resolution = scale_resolution
|
| 45 |
+
|
| 46 |
+
@classmethod
|
| 47 |
+
def from_pretrained(cls, pretrained_model_name_or_path: Union[str, os.PathLike], **kwargs) -> "PretrainedConfig":
|
| 48 |
+
cls._set_token_in_kwargs(kwargs)
|
| 49 |
+
|
| 50 |
+
config_dict, kwargs = cls.get_config_dict(pretrained_model_name_or_path, **kwargs)
|
| 51 |
+
|
| 52 |
+
if config_dict.get("model_type") == "realtime_venus_omni_vision":
|
| 53 |
+
config_dict = config_dict["slice_config"]
|
| 54 |
+
|
| 55 |
+
if "model_type" in config_dict and hasattr(cls, "model_type") and config_dict["model_type"] != cls.model_type:
|
| 56 |
+
logger.warning(
|
| 57 |
+
f"You are using a model of type {config_dict['model_type']} to instantiate a model of type "
|
| 58 |
+
f"{cls.model_type}. This is not supported for all configurations of models and can yield errors."
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
return cls.from_dict(config_dict, **kwargs)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class RealtimeVenusOmniTTSConfig(PretrainedConfig):
|
| 65 |
+
model_type = "realtime_venus_omni_tts"
|
| 66 |
+
|
| 67 |
+
def __init__(
|
| 68 |
+
self,
|
| 69 |
+
llm_dim: int = 2560,
|
| 70 |
+
llm_intermediate_size: int = 768,
|
| 71 |
+
llm_down_scale: bool = False,
|
| 72 |
+
llm_dim_model_base: int = 256,
|
| 73 |
+
projector_type: str = "mlp",
|
| 74 |
+
hidden_act: str = "silu",
|
| 75 |
+
aug_loss_weight: bool = False,
|
| 76 |
+
aug_layer_loss_weight: bool = False,
|
| 77 |
+
filter_tts_loss: bool = False,
|
| 78 |
+
tts_filter_loss_fix: bool = False,
|
| 79 |
+
long_weight: float = 0.1,
|
| 80 |
+
short_weight: float = 0.1,
|
| 81 |
+
hidden_size: int = 768,
|
| 82 |
+
intermediate_size: int = 3072,
|
| 83 |
+
num_attention_heads: int = 12,
|
| 84 |
+
num_hidden_layers: int = 20,
|
| 85 |
+
num_key_value_heads: int = 12,
|
| 86 |
+
max_position_embeddings: int = 4096,
|
| 87 |
+
num_audio_tokens: int = 4097,
|
| 88 |
+
num_text_tokens: int = 21178,
|
| 89 |
+
num_mel_bins: int = 100,
|
| 90 |
+
num_vq: int = 1,
|
| 91 |
+
use_llm_hidden_state: bool = False,
|
| 92 |
+
audio_bos_token_id: int = 21132,
|
| 93 |
+
text_eos_token_id: int = 21133,
|
| 94 |
+
use_text: bool = True,
|
| 95 |
+
streaming: bool = False,
|
| 96 |
+
streaming_text_chunk_min: int = 3,
|
| 97 |
+
streaming_text_chunk_max: int = 7,
|
| 98 |
+
streaming_text_reserved_len: int = 300,
|
| 99 |
+
streaming_audio_chunk_size: int = 50,
|
| 100 |
+
attn_implementation: str = "sdpa",
|
| 101 |
+
condition_type: str = "llm_hidden",
|
| 102 |
+
backbone_model: str = "llama",
|
| 103 |
+
audio_tokenizer_type: str = "wavtokenizer",
|
| 104 |
+
audio_tokenizer_sample_rate: int = 24000,
|
| 105 |
+
streaming_sliding_window: bool = False,
|
| 106 |
+
streaming_sliding_window_max_text_len: int = 500,
|
| 107 |
+
streaming_sliding_window_average_speed: int = 5,
|
| 108 |
+
streaming_sliding_window_fast_speed: int = 7,
|
| 109 |
+
streaming_sliding_window_slow_speed: int = 3,
|
| 110 |
+
streaming_sliding_window_audio_frame_rate: int = 50,
|
| 111 |
+
streaming_sliding_window_audio_init_text_length: int = 10,
|
| 112 |
+
streaming_sliding_window_audio_window_size: int = 300,
|
| 113 |
+
normalize_projected_hidden: bool = False,
|
| 114 |
+
interleaved: bool = False,
|
| 115 |
+
attention_type: str = "sliding_recompute",
|
| 116 |
+
recomputed_chunks: int = 1,
|
| 117 |
+
window_size: int = 2,
|
| 118 |
+
**kwargs,
|
| 119 |
+
):
|
| 120 |
+
super().__init__(**kwargs)
|
| 121 |
+
|
| 122 |
+
self.llm_dim = llm_dim
|
| 123 |
+
self.llm_hidden_size = llm_dim
|
| 124 |
+
self.llm_intermediate_size = llm_intermediate_size
|
| 125 |
+
self.llm_down_scale = llm_down_scale
|
| 126 |
+
self.llm_dim_model_base = llm_dim_model_base
|
| 127 |
+
self.projector_type = projector_type
|
| 128 |
+
self.aug_loss_weight = aug_loss_weight
|
| 129 |
+
self.aug_layer_loss_weight = aug_layer_loss_weight
|
| 130 |
+
self.tts_filter_loss_fix = tts_filter_loss_fix
|
| 131 |
+
self.filter_tts_loss = filter_tts_loss
|
| 132 |
+
self.long_weight = long_weight
|
| 133 |
+
self.short_weight = short_weight
|
| 134 |
+
self.hidden_act = hidden_act
|
| 135 |
+
|
| 136 |
+
self.hidden_size = hidden_size
|
| 137 |
+
self.intermediate_size = intermediate_size
|
| 138 |
+
self.num_attention_heads = num_attention_heads
|
| 139 |
+
self.num_hidden_layers = num_hidden_layers
|
| 140 |
+
self.num_key_value_heads = num_key_value_heads
|
| 141 |
+
self.max_position_embeddings = max_position_embeddings
|
| 142 |
+
self.num_audio_tokens = num_audio_tokens
|
| 143 |
+
self.num_text_tokens = num_text_tokens
|
| 144 |
+
self.num_mel_bins = num_mel_bins
|
| 145 |
+
self.num_vq = num_vq
|
| 146 |
+
self.use_llm_hidden_state = use_llm_hidden_state
|
| 147 |
+
self.audio_bos_token_id = audio_bos_token_id
|
| 148 |
+
self.text_eos_token_id = text_eos_token_id
|
| 149 |
+
self.use_text = use_text
|
| 150 |
+
self.streaming = streaming
|
| 151 |
+
self.streaming_text_chunk_min = streaming_text_chunk_min
|
| 152 |
+
self.streaming_text_chunk_max = streaming_text_chunk_max
|
| 153 |
+
self.streaming_text_reserved_len = streaming_text_reserved_len
|
| 154 |
+
self.streaming_audio_chunk_size = streaming_audio_chunk_size
|
| 155 |
+
self.attn_implementation = attn_implementation
|
| 156 |
+
self.condition_type = condition_type
|
| 157 |
+
self.backbone_model = backbone_model
|
| 158 |
+
self.audio_tokenizer_type = audio_tokenizer_type
|
| 159 |
+
self.audio_tokenizer_sample_rate = audio_tokenizer_sample_rate
|
| 160 |
+
|
| 161 |
+
self.streaming_sliding_window = streaming_sliding_window
|
| 162 |
+
self.streaming_sliding_window_max_text_len = streaming_sliding_window_max_text_len
|
| 163 |
+
self.streaming_sliding_window_average_speed = streaming_sliding_window_average_speed
|
| 164 |
+
self.streaming_sliding_window_fast_speed = streaming_sliding_window_fast_speed
|
| 165 |
+
self.streaming_sliding_window_slow_speed = streaming_sliding_window_slow_speed
|
| 166 |
+
self.streaming_sliding_window_audio_frame_rate = streaming_sliding_window_audio_frame_rate
|
| 167 |
+
self.streaming_sliding_window_audio_init_text_length = streaming_sliding_window_audio_init_text_length
|
| 168 |
+
self.streaming_sliding_window_audio_window_size = streaming_sliding_window_audio_window_size
|
| 169 |
+
|
| 170 |
+
self.normalize_projected_hidden = normalize_projected_hidden
|
| 171 |
+
|
| 172 |
+
self.interleaved = interleaved
|
| 173 |
+
self.attention_type = attention_type
|
| 174 |
+
self.recomputed_chunks = recomputed_chunks
|
| 175 |
+
self.window_size = window_size
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
class RealtimeVenusOmniConfig(Qwen3Config):
|
| 179 |
+
model_type = "realtime_venus_omni"
|
| 180 |
+
keys_to_ignore_at_inference = ["past_key_values"]
|
| 181 |
+
|
| 182 |
+
default_vision_config = {
|
| 183 |
+
"hidden_size": 1152,
|
| 184 |
+
"image_size": 980,
|
| 185 |
+
"intermediate_size": 4304,
|
| 186 |
+
"model_type": "siglip",
|
| 187 |
+
"num_attention_heads": 16,
|
| 188 |
+
"num_hidden_layers": 27,
|
| 189 |
+
"patch_size": 14,
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
def __init__(
|
| 193 |
+
self,
|
| 194 |
+
use_cache=True,
|
| 195 |
+
query_num=64,
|
| 196 |
+
image_size=448,
|
| 197 |
+
drop_vision_last_layer=True,
|
| 198 |
+
batch_vision_input=True,
|
| 199 |
+
slice_config=None,
|
| 200 |
+
vision_config=None,
|
| 201 |
+
audio_config=None,
|
| 202 |
+
tts_config=None,
|
| 203 |
+
use_image_id=True,
|
| 204 |
+
vision_batch_size=16,
|
| 205 |
+
audio_pool_step=5,
|
| 206 |
+
audio_chunk_length=1.0,
|
| 207 |
+
stream_input=False,
|
| 208 |
+
listen_speak_type="asr",
|
| 209 |
+
init_vision=True,
|
| 210 |
+
init_audio=True,
|
| 211 |
+
init_tts=True,
|
| 212 |
+
**kwargs,
|
| 213 |
+
):
|
| 214 |
+
self.use_cache = use_cache
|
| 215 |
+
self.query_num = query_num
|
| 216 |
+
self.image_size = image_size
|
| 217 |
+
self.drop_vision_last_layer = drop_vision_last_layer
|
| 218 |
+
self.batch_vision_input = batch_vision_input
|
| 219 |
+
self.use_image_id = use_image_id
|
| 220 |
+
self.vision_batch_size = vision_batch_size
|
| 221 |
+
self.audio_pool_step = audio_pool_step
|
| 222 |
+
self.audio_chunk_length = audio_chunk_length
|
| 223 |
+
self.stream_input = stream_input
|
| 224 |
+
self.listen_speak_type = listen_speak_type
|
| 225 |
+
|
| 226 |
+
self.init_vision = init_vision
|
| 227 |
+
self.init_audio = init_audio
|
| 228 |
+
self.init_tts = init_tts
|
| 229 |
+
|
| 230 |
+
if slice_config is None:
|
| 231 |
+
self.slice_config = RealtimeVenusOmniSliceConfig(max_slice_nums=1)
|
| 232 |
+
else:
|
| 233 |
+
self.slice_config = RealtimeVenusOmniSliceConfig(**slice_config)
|
| 234 |
+
self.slice_mode = True
|
| 235 |
+
|
| 236 |
+
# same as HuggingFaceM4/siglip-so400m-14-980-flash-attn2-navit add tgt_sizes
|
| 237 |
+
if vision_config is None:
|
| 238 |
+
self.vision_config = SiglipVisionConfig(**self.default_vision_config)
|
| 239 |
+
logger.info("vision_config is None, using default vision config")
|
| 240 |
+
elif isinstance(vision_config, dict):
|
| 241 |
+
self.vision_config = SiglipVisionConfig(**vision_config)
|
| 242 |
+
elif isinstance(vision_config, SiglipVisionConfig):
|
| 243 |
+
self.vision_config = vision_config
|
| 244 |
+
|
| 245 |
+
if audio_config is None:
|
| 246 |
+
self.audio_config = WhisperConfig()
|
| 247 |
+
elif isinstance(audio_config, dict):
|
| 248 |
+
self.audio_config = WhisperConfig(**audio_config)
|
| 249 |
+
elif isinstance(audio_config, WhisperConfig):
|
| 250 |
+
self.audio_config = audio_config
|
| 251 |
+
|
| 252 |
+
if tts_config is None:
|
| 253 |
+
self.tts_config = RealtimeVenusOmniTTSConfig()
|
| 254 |
+
elif isinstance(tts_config, dict):
|
| 255 |
+
self.tts_config = RealtimeVenusOmniTTSConfig(**tts_config)
|
| 256 |
+
elif isinstance(tts_config, RealtimeVenusOmniTTSConfig):
|
| 257 |
+
self.tts_config = tts_config
|
| 258 |
+
|
| 259 |
+
self.patch_size = self.vision_config.patch_size
|
| 260 |
+
|
| 261 |
+
super().__init__(**kwargs)
|
Realtime-Venus-Omni/generation_config.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"do_sample": true,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
151645,
|
| 6 |
+
151643
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 151643,
|
| 9 |
+
"temperature": 0.6,
|
| 10 |
+
"top_k": 20,
|
| 11 |
+
"top_p": 0.95
|
| 12 |
+
}
|