autotools commited on
Commit
c1a2228
·
verified ·
1 Parent(s): cf49572

Complete OmniVoice source mirror

Browse files
runtime/omnivoice-python/omnivoice-source/.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ *.egg
6
+ dist/
7
+ build/
8
+ .venv/
9
+ .env
10
+ .DS_Store
11
+ .pytest_cache/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ *.so
15
+ /.cache*
16
+ /exp*/
17
+ /.tmp/
18
+ /results/
19
+ /data/
20
+ /download
21
+ /local/
22
+ /run*
23
+ example.py
24
+ results/
25
+ examples/data*
26
+ examples/download*
27
+ examples/exp*
28
+ .claude/
29
+ *.wav
30
+ *.jsonl
runtime/omnivoice-python/omnivoice-source/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 2026 Xiaomi Corp.
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.
runtime/omnivoice-python/omnivoice-source/docs/OmniVoice.ipynb ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": "# OmniVoice Quick Start\n\n[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/k2-fsa/OmniVoice/blob/master/docs/OmniVoice.ipynb)\n\nThis notebook demonstrates the basic usage of [OmniVoice](https://github.com/k2-fsa/OmniVoice), a massively multilingual zero-shot TTS model supporting 600+ languages.\n\n**Contents:**\n1. Installation\n2. Option A — Gradio Demo (interactive web UI, no code needed)\n3. Option B — Python API\n - 3.1 Load Model\n - 3.2 Voice Cloning\n - 3.3 Voice Design\n - 3.4 Auto Voice"
7
+ },
8
+ {
9
+ "cell_type": "markdown",
10
+ "metadata": {},
11
+ "source": [
12
+ "## 1. Installation\n",
13
+ "\n",
14
+ "Colab already provides a compatible PyTorch + CUDA environment, so we only need to install OmniVoice."
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": null,
20
+ "metadata": {},
21
+ "outputs": [],
22
+ "source": [
23
+ "!pip install omnivoice"
24
+ ]
25
+ },
26
+ {
27
+ "cell_type": "markdown",
28
+ "metadata": {},
29
+ "source": "## 2. Option A — Gradio Demo\n\nLaunch an interactive web UI with a public Gradio link. The `--share` flag creates a temporary public URL so you can access the demo from any browser.\n\n> **If you prefer to use the Python API directly, skip to Option B below.**"
30
+ },
31
+ {
32
+ "cell_type": "code",
33
+ "execution_count": null,
34
+ "metadata": {},
35
+ "outputs": [],
36
+ "source": [
37
+ "!omnivoice-demo --share"
38
+ ]
39
+ },
40
+ {
41
+ "cell_type": "markdown",
42
+ "metadata": {},
43
+ "source": "## 3. Option B — Python API\n\n### 3.1 Load Model"
44
+ },
45
+ {
46
+ "cell_type": "code",
47
+ "execution_count": null,
48
+ "metadata": {},
49
+ "outputs": [],
50
+ "source": "from omnivoice import OmniVoice\nimport soundfile as sf\nimport torch\nfrom IPython.display import Audio, display\n\nmodel = OmniVoice.from_pretrained(\n \"k2-fsa/OmniVoice\",\n device_map=\"cuda:0\",\n dtype=torch.float16,\n load_asr=True,\n)"
51
+ },
52
+ {
53
+ "cell_type": "markdown",
54
+ "metadata": {},
55
+ "source": "### 3.2 Voice Cloning\n\nClone a voice from a short (3-10s) reference audio clip. Upload your own `ref.wav` or use any audio file.\n\n`ref_text` is optional — if omitted, the model uses Whisper ASR to auto-transcribe it."
56
+ },
57
+ {
58
+ "cell_type": "code",
59
+ "execution_count": null,
60
+ "metadata": {},
61
+ "outputs": [],
62
+ "source": [
63
+ "from google.colab import files\n",
64
+ "\n",
65
+ "print(\"Upload a reference audio file (wav/mp3/flac):\")\n",
66
+ "uploaded = files.upload()\n",
67
+ "ref_audio_path = list(uploaded.keys())[0]\n",
68
+ "print(f\"Uploaded: {ref_audio_path}\")"
69
+ ]
70
+ },
71
+ {
72
+ "cell_type": "code",
73
+ "execution_count": null,
74
+ "metadata": {},
75
+ "outputs": [],
76
+ "source": [
77
+ "audio = model.generate(\n",
78
+ " text=\"Hello, this is a test of zero-shot voice cloning.\",\n",
79
+ " ref_audio=ref_audio_path,\n",
80
+ " # ref_text=\"Transcription of the reference audio.\", # optional\n",
81
+ ")\n",
82
+ "\n",
83
+ "sf.write(\"clone_out.wav\", audio[0], 24000)\n",
84
+ "display(Audio(audio[0], rate=24000))"
85
+ ]
86
+ },
87
+ {
88
+ "cell_type": "markdown",
89
+ "metadata": {},
90
+ "source": "### 3.3 Voice Design\n\nDescribe the desired voice with speaker attributes — no reference audio needed.\n\nSupported attributes: gender, age, pitch, style (whisper), English accent, Chinese dialect. See [docs/voice-design.md](https://github.com/k2-fsa/OmniVoice/blob/master/docs/voice-design.md) for the full list."
91
+ },
92
+ {
93
+ "cell_type": "code",
94
+ "execution_count": null,
95
+ "metadata": {},
96
+ "outputs": [],
97
+ "source": [
98
+ "audio = model.generate(\n",
99
+ " text=\"Hello, this is a test of zero-shot voice design.\",\n",
100
+ " instruct=\"female, low pitch, british accent\",\n",
101
+ ")\n",
102
+ "\n",
103
+ "sf.write(\"design_out.wav\", audio[0], 24000)\n",
104
+ "display(Audio(audio[0], rate=24000))"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "markdown",
109
+ "metadata": {},
110
+ "source": "### 3.4 Auto Voice\n\nLet the model choose a voice automatically — no reference audio or instruct needed."
111
+ },
112
+ {
113
+ "cell_type": "code",
114
+ "execution_count": null,
115
+ "metadata": {},
116
+ "outputs": [],
117
+ "source": [
118
+ "audio = model.generate(\n",
119
+ " text=\"This is a sentence generated with automatic voice selection.\",\n",
120
+ ")\n",
121
+ "\n",
122
+ "sf.write(\"auto_out.wav\", audio[0], 24000)\n",
123
+ "display(Audio(audio[0], rate=24000))"
124
+ ]
125
+ }
126
+ ],
127
+ "metadata": {
128
+ "accelerator": "GPU",
129
+ "colab": {
130
+ "gpuType": "T4",
131
+ "provenance": []
132
+ },
133
+ "kernelspec": {
134
+ "display_name": "Python 3",
135
+ "name": "python3"
136
+ },
137
+ "language_info": {
138
+ "name": "python",
139
+ "version": "3.10.0"
140
+ }
141
+ },
142
+ "nbformat": 4,
143
+ "nbformat_minor": 0
144
+ }
runtime/omnivoice-python/omnivoice-source/docs/lang_id_name_map.tsv ADDED
@@ -0,0 +1,647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language_id language_name iso_639_3_id train_data_duration
2
+ aae Arbëreshë Albanian aae 6.11
3
+ aal Afade aal 10.19
4
+ aao Algerian Saharan Arabic aao 2.02
5
+ ab Abkhazian abk 57.27
6
+ abb Bankon abb 11.2
7
+ abn Abua abn 10.27
8
+ abr Abron abr 9.22
9
+ abs Ambonese Malay abs 10.03
10
+ abv Baharna Arabic abv 10.41
11
+ acm Mesopotamian Arabic acm 3.78
12
+ acw Hijazi Arabic acw 22.32
13
+ acx Omani Arabic acx 22.03
14
+ adf Dhofari Arabic adf 0.31
15
+ adx Amdo Tibetan adx 56.94
16
+ ady Adyghe ady 32.6
17
+ aeb Tunisian Arabic aeb 21.63
18
+ aec Saidi Arabic aec 9.28
19
+ af Afrikaans afr 4.4
20
+ afb Gulf Arabic afb 98.55
21
+ afo Eloyi afo 11.21
22
+ ahl Igo ahl 9.22
23
+ ahs Ashe ahs 10.62
24
+ ajg Aja (Benin) ajg 5.63
25
+ aju Judeo-Moroccan Arabic aju 7.21
26
+ ala Alago ala 11.04
27
+ aln Gheg Albanian aln 3.92
28
+ alo Larike-Wakasihu alo 9.97
29
+ am Amharic amh 12.83
30
+ amu Guerrero Amuzgo amu 10.1
31
+ an Aragonese arg 16.4
32
+ anc Ngas anc 10.14
33
+ ank Goemai ank 10.0
34
+ anp Angika anp 10.65
35
+ anw Anaang anw 9.65
36
+ aom Ömie aom 8.19
37
+ apc Levantine Arabic apc 15.65
38
+ apd Sudanese Arabic apd 9.93
39
+ arb Standard Arabic arb 1483.53
40
+ arq Algerian Arabic arq 9.64
41
+ ars Najdi Arabic ars 203.54
42
+ ary Moroccan Arabic ary 104.67
43
+ arz Egyptian Arabic arz 23.23
44
+ as Assamese asm 270.85
45
+ ast Asturian ast 8.48
46
+ avl Eastern Egyptian Bedawi Arabic avl 1.86
47
+ awo Awak awo 10.22
48
+ ayl Libyan Arabic ayl 20.13
49
+ ayp North Mesopotamian Arabic ayp 10.92
50
+ az Azerbaijani aze 9.84
51
+ ba Bashkir bak 249.1
52
+ bag Tuki bag 10.97
53
+ bas Basa (Cameroon) bas 10.66
54
+ bax Bamun bax 10.24
55
+ bba Baatonum bba 10.53
56
+ bbj Ghomálá' bbj 7.32
57
+ bbl Bats bbl 11.22
58
+ bbu Kulung (Nigeria) bbu 10.39
59
+ bce Bamenyam bce 9.9
60
+ bci Baoulé bci 10.21
61
+ bcs Kohumono bcs 10.45
62
+ bcy Bacama bcy 9.94
63
+ bda Bayot bda 9.47
64
+ bde Bade bde 9.89
65
+ bdm Buduma bdm 10.17
66
+ be Belarusian bel 1809.43
67
+ beb Bebele beb 7.52
68
+ bew Betawi bew 11.15
69
+ bfd Bafut bfd 9.03
70
+ bft Balti bft 16.28
71
+ bg Bulgarian bul 2190.76
72
+ bgp Eastern Balochi bgp 10.98
73
+ bhb Bhili bhb 9.98
74
+ bhh Bukharic bhh 11.38
75
+ bho Bhojpuri bho 10.05
76
+ bhp Bima bhp 10.67
77
+ bhr Bara Malagasy bhr 12.14
78
+ bjj Kanauji bjj 11.01
79
+ bjk Barok bjk 10.16
80
+ bjn Banjar bjn 11.68
81
+ bjt Balanta-Ganja bjt 9.41
82
+ bkh Bakoko bkh 6.0
83
+ bkm Kom (Cameroon) bkm 10.76
84
+ bky Bokyi bky 9.85
85
+ bmm Northern Betsimisaraka Malagasy bmm 19.12
86
+ bmq Bomu bmq 10.68
87
+ bn Bengali ben 271.76
88
+ bnm Batanga bnm 15.01
89
+ bnn Bunun bnn 9.26
90
+ bns Bundeli bns 10.88
91
+ bo Tibetan bod 82.27
92
+ bou Bondei bou 9.98
93
+ bqg Bago-Kusuntu bqg 8.86
94
+ br Breton bre 25.48
95
+ bra Braj bra 10.68
96
+ brh Brahui brh 19.89
97
+ bri Mokpwe bri 7.53
98
+ brx Bodo brx 231.57
99
+ bs Bosnian bos 690.73
100
+ bsh Kati bsh 8.77
101
+ bsj Bangwinji bsj 10.0
102
+ bsk Burushaski bsk 9.14
103
+ btm Batak Mandailing btm 11.09
104
+ btv Bateri btv 9.8
105
+ bug Buginese bug 11.09
106
+ bum Bulu (Cameroon) bum 9.06
107
+ buo Terei buo 9.48
108
+ bux Boghom bux 10.48
109
+ bwr Bura-Pabir bwr 10.4
110
+ bxf Bilur bxf 10.84
111
+ byc Ubaghara byc 11.11
112
+ bys Burak bys 9.92
113
+ byv Medumba byv 10.95
114
+ byx Qaqet byx 9.79
115
+ bzc Southern Betsimisaraka Malagasy bzc 17.45
116
+ bzw Basa (Nigeria) bzw 10.27
117
+ ca Catalan cat 3358.6
118
+ ccg Samba Daka ccg 10.11
119
+ ceb Cebuano ceb 12.17
120
+ cen Cen cen 9.85
121
+ cfa Dijim-Bwilim cfa 10.32
122
+ cgg Chiga cgg 10.84
123
+ chq Quiotepec Chinantec chq 9.76
124
+ cjk Chokwe cjk 11.01
125
+ ckb Central Kurdish ckb 137.52
126
+ ckl Cibak ckl 10.91
127
+ ckr Kairak ckr 10.51
128
+ cky Cakfem-Mushere cky 8.96
129
+ cnh Hakha Chin cnh 2.24
130
+ cpy South Ucayali Ashéninka cpy 9.15
131
+ cs Czech ces 148.13
132
+ cte Tepinapa Chinantec cte 9.54
133
+ ctl Tlacoatzintepec Chinantec ctl 10.04
134
+ cut Teutila Cuicatec cut 8.04
135
+ cux Tepeuxila Cuicatec cux 7.83
136
+ cv Chuvash chv 23.96
137
+ cy Welsh cym 131.21
138
+ da Danish dan 1665.98
139
+ dag Dagbani dag 10.14
140
+ dar Dargwa dar 1.22
141
+ dav Taita dav 9.12
142
+ dbd Dadiya dbd 9.61
143
+ dcc Deccan dcc 10.38
144
+ de German deu 21927.13
145
+ deg Degema deg 11.07
146
+ dgh Dghwede dgh 9.95
147
+ dgo Dogri dgo 117.04
148
+ dje Zarma dje 10.72
149
+ dmk Domaaki dmk 6.38
150
+ dml Dameli dml 9.18
151
+ dru Rukai dru 9.26
152
+ dty Dotyali dty 10.85
153
+ dua Duala dua 12.13
154
+ dv Dhivehi div 38.61
155
+ dyu Dyula dyu 0.34
156
+ dzg Dazaga dzg 9.96
157
+ ebr Ebrié ebr 1.5
158
+ ebu Embu ebu 9.81
159
+ ego Eggon ego 9.95
160
+ eiv Askopan eiv 10.44
161
+ eko Koti eko 8.15
162
+ ekr Yace ekr 10.76
163
+ el Greek ell 2412.54
164
+ elm Eleme elm 11.27
165
+ en English eng 206061.1
166
+ eo Esperanto epo 1396.64
167
+ es Spanish spa 27559.74
168
+ esu Central Yupik esu 2.18
169
+ et Estonian est 960.37
170
+ eto Eton (Cameroon) eto 7.43
171
+ ets Yekhee ets 10.11
172
+ etu Ejagham etu 10.3
173
+ eu Basque eus 479.86
174
+ ewo Ewondo ewo 12.71
175
+ ext Extremaduran ext 13.59
176
+ eyo Keiyo eyo 9.24
177
+ fa Persian fas 366.07
178
+ fan Fang (Equatorial Guinea) fan 3.51
179
+ fat Fanti fat 11.38
180
+ ff Fulah ful 13.84
181
+ ffm Maasina Fulfulde ffm 10.46
182
+ fi Finnish fin 468.62
183
+ fia Nobiin fia 9.96
184
+ fil Filipino fil 7.71
185
+ fip Fipa fip 10.55
186
+ fkk Kirya-Konzəl fkk 9.98
187
+ fmp Fe'fe' fmp 9.86
188
+ fr French fra 23675.32
189
+ fub Adamawa Fulfulde fub 13.12
190
+ fuc Pulaar fuc 14.77
191
+ fue Borgu Fulfulde fue 20.1
192
+ fuf Pular fuf 13.77
193
+ fuh Western Niger Fulfulde fuh 9.69
194
+ fui Bagirmi Fulfulde fui 15.04
195
+ fuq Central-Eastern Niger Fulfulde fuq 9.28
196
+ fuv Nigerian Fulfulde fuv 9.97
197
+ fy Western Frisian fry 70.41
198
+ ga Irish gle 21.4
199
+ gbm Garhwali gbm 19.14
200
+ gbr Gbagyi gbr 12.12
201
+ gby Gbari gby 12.59
202
+ gcc Mali gcc 9.87
203
+ gdf Guduf-Gava gdf 12.21
204
+ gej Gen gej 5.39
205
+ ges Geser-Gorom ges 10.08
206
+ ggg Gurgula ggg 7.12
207
+ gid Gidar gid 10.06
208
+ gig Goaria gig 9.41
209
+ giz South Giziga giz 10.03
210
+ gjk Kachi Koli gjk 20.83
211
+ gju Gujari gju 8.66
212
+ gl Galician glg 208.81
213
+ glw Glavda glw 10.51
214
+ gn Guarani grn 4.06
215
+ gol Gola gol 9.26
216
+ gom Goan Konkani gom 9.82
217
+ gsl Gusilay gsl 10.0
218
+ gu Gujarati guj 91.18
219
+ gui Eastern Bolivian Guaraní gui 22.72
220
+ gur Farefare gur 9.24
221
+ guz Gusii guz 9.5
222
+ gv Manx glv 10.07
223
+ gwc Gawri gwc 10.83
224
+ gwe Gweno gwe 8.87
225
+ gwt Gawar-Bati gwt 12.16
226
+ gya Northwest Gbaya gya 8.45
227
+ gyz Geji gyz 10.49
228
+ ha Hausa hau 17.75
229
+ hah Hahon hah 9.64
230
+ hao Hakö hao 8.56
231
+ haw Hawaiian haw 11.79
232
+ haz Hazaragi haz 9.69
233
+ hbb Huba hbb 10.7
234
+ he Hebrew heb 13.4
235
+ hem Hemba hem 9.53
236
+ hi Hindi hin 117.17
237
+ hia Lamang hia 11.07
238
+ hkk Hunjara-Kaina Ke hkk 8.69
239
+ hla Halia hla 9.86
240
+ hno Northern Hindko hno 20.04
241
+ hoj Hadothi hoj 10.08
242
+ hr Croatian hrv 2795.31
243
+ hsb Upper Sorbian hsb 2.71
244
+ ht Haitian hat 0.04
245
+ hu Hungarian hun 255.83
246
+ hue San Francisco Del Mar Huave hue 9.45
247
+ hul Hula hul 10.33
248
+ hux Nüpode Huitoto hux 9.04
249
+ hwo Hwana hwo 11.23
250
+ hy Armenian hye 42.15
251
+ hz Herero her 9.59
252
+ ia Interlingua (International Auxiliary Language Association) ina 13.48
253
+ ibb Ibibio ibb 7.38
254
+ id Indonesian ind 6327.87
255
+ ida Idakho-Isukha-Tiriki ida 9.31
256
+ idu Idoma idu 11.16
257
+ ig Igbo ibo 13.69
258
+ ijc Izon ijc 9.95
259
+ ijn Kalabari ijn 11.04
260
+ ik Inupiaq ipk 2.11
261
+ ikw Ikwere ikw 10.0
262
+ is Icelandic isl 647.29
263
+ ish Esan ish 10.05
264
+ iso Isoko iso 10.33
265
+ it Italian ita 9402.46
266
+ its Isekiri its 11.85
267
+ itw Ito itw 9.19
268
+ itz Itzá itz 7.08
269
+ ja Japanese jpn 36914.4
270
+ jal Yalahatan jal 11.18
271
+ jax Jambi Malay jax 10.29
272
+ jgo Ngomba jgo 10.15
273
+ jmx Western Juxtlahuaca Mixtec jmx 10.01
274
+ jns Jaunsari jns 11.25
275
+ jqr Jaqaru jqr 9.32
276
+ juk Wapan juk 10.22
277
+ juo Jiba juo 10.43
278
+ jv Javanese jav 11.19
279
+ ka Georgian kat 156.96
280
+ kab Kabyle kab 529.52
281
+ kai Karekare kai 10.52
282
+ kaj Jju kaj 10.16
283
+ kam Kamba kam 14.72
284
+ kbd Kabardian kbd 108.35
285
+ kbl Kanembu kbl 10.19
286
+ kbt Abadi kbt 9.73
287
+ kcq Kamo kcq 10.49
288
+ kdh Tem kdh 4.07
289
+ kea Kabuverdianu kea 10.51
290
+ keu Akebu keu 9.1
291
+ kfe Kota (India) kfe 10.25
292
+ kfk Kinnauri kfk 10.32
293
+ kfp Korwa kfp 11.87
294
+ khg Khams Tibetan khg 6.38
295
+ khw Khowar khw 15.55
296
+ kj Kuanyama kua 9.88
297
+ kjc Coastal Konjo kjc 10.18
298
+ kjk Highland Konjo kjk 10.21
299
+ kk Kazakh kaz 1537.29
300
+ kln Kalenjin kln 40.42
301
+ kls Kalasha kls 9.11
302
+ km Khmer khm 7.1
303
+ kmr Northern Kurdish kmr 69.59
304
+ kmy Koma kmy 10.28
305
+ kn Kannada kan 128.06
306
+ kna Dera (Nigeria) kna 11.91
307
+ knn Konkani knn 112.83
308
+ ko Korean kor 8609.28
309
+ kol Kol (Papua New Guinea) kol 9.95
310
+ koo Konzo koo 13.23
311
+ kpo Ikposo kpo 7.83
312
+ kqo Eastern Krahn kqo 9.28
313
+ ks Kashmiri kas 110.42
314
+ ksd Kuanua ksd 9.91
315
+ ksf Bafia ksf 16.43
316
+ kto Kuot kto 9.77
317
+ kuh Kushi kuh 10.35
318
+ kvx Parkari Koli kvx 11.04
319
+ kw Cornish cor 12.15
320
+ kwm Kwambi kwm 9.9
321
+ kxp Wadiyara Koli kxp 20.0
322
+ ky Kirghiz kir 46.63
323
+ kyx Rapoisi kyx 9.17
324
+ lag Rangi lag 9.47
325
+ lb Luxembourgish ltz 8.46
326
+ lcm Tungag lcm 9.77
327
+ ldb Dũya ldb 11.31
328
+ lg Ganda lug 447.82
329
+ lij Ligurian lij 15.97
330
+ lir Liberian English lir 10.26
331
+ lkb Kabras lkb 9.99
332
+ lla Lala-Roba lla 10.38
333
+ ln Lingala lin 17.99
334
+ lnu Longuda lnu 10.46
335
+ lo Lao lao 7.63
336
+ loa Loloda loa 9.31
337
+ lrk Loarki lrk 10.5
338
+ lss Lasi lss 6.53
339
+ lt Lithuanian lit 2629.45
340
+ ltg Latgalian ltg 27.23
341
+ lto Tsotso lto 9.77
342
+ lua Luba-Lulua lua 8.47
343
+ luo Luo luo 36.17
344
+ lus Lushai lus 20.24
345
+ lv Latvian lav 1441.58
346
+ lwg Wanga lwg 9.36
347
+ mab Yutanduchi Mixtec mab 9.26
348
+ maf Mafa maf 9.97
349
+ mai Maithili mai 131.37
350
+ mau Huautla Mazatec mau 6.39
351
+ max North Moluccan Malay max 9.43
352
+ mbo Mbo (Cameroon) mbo 9.51
353
+ mcf Matsés mcf 9.61
354
+ mcn Masana mcn 10.09
355
+ mcx Mpiemo mcx 9.88
356
+ mdd Mbum mdd 9.82
357
+ mde Maba (Chad) mde 9.5
358
+ mdf Moksha mdf 0.47
359
+ mek Mekeo mek 9.18
360
+ mer Meru mer 9.89
361
+ meu Motu meu 9.88
362
+ mfm Marghi South mfm 10.05
363
+ mfn Cross River Mbembe mfn 10.03
364
+ mfo Mbe mfo 10.24
365
+ mfv Mandjak mfv 9.55
366
+ mgg Mpumpong mgg 4.94
367
+ mgi Lijili mgi 10.89
368
+ mhk Mungaka mhk 7.53
369
+ mhr Eastern Mari mhr 272.31
370
+ mi Maori mri 18.02
371
+ mig San Miguel El Grande Mixtec mig 9.66
372
+ miu Cacaloxtepec Mixtec miu 9.18
373
+ mk Macedonian mkd 27.21
374
+ mkf Miya mkf 10.16
375
+ mki Dhatki mki 8.83
376
+ ml Malayalam mal 166.57
377
+ mlq Western Maninkakan mlq 9.83
378
+ mn Mongolian mon 269.08
379
+ mne Naba mne 10.37
380
+ mni Manipuri mni 44.46
381
+ mqy Manggarai mqy 10.5
382
+ mr Marathi mar 156.71
383
+ mrj Western Mari mrj 32.26
384
+ mrr Maria (India) mrr 11.0
385
+ mrt Marghi Central mrt 10.36
386
+ ms Malay msa 9.57
387
+ mse Musey mse 7.21
388
+ msh Masikoro Malagasy msh 14.16
389
+ msw Mansoanka msw 9.32
390
+ mt Maltese mlt 630.29
391
+ mtr Mewari mtr 10.58
392
+ mtu Tututepec Mixtec mtu 10.13
393
+ mtx Tidaá Mixtec mtx 9.09
394
+ mua Mundang mua 9.2
395
+ mug Musgu mug 4.74
396
+ mui Musi mui 10.52
397
+ mve Marwari (Pakistan) mve 9.96
398
+ mvy Indus Kohistani mvy 21.64
399
+ mxs Huitepec Mixtec mxs 9.64
400
+ mxu Mada (Cameroon) mxu 12.0
401
+ mxy Southeastern Nochixtlán Mixtec mxy 9.48
402
+ my Burmese mya 12.14
403
+ myv Erzya myv 3.1
404
+ mzl Mazatlán Mixe mzl 10.05
405
+ nal Nalik nal 10.33
406
+ nan Min Nan Chinese nan 17.55
407
+ nap Neapolitan nap 9.97
408
+ nb Norwegian Bokmål nob 12.7
409
+ nbh Ngamo nbh 10.04
410
+ ncf Notsi ncf 9.84
411
+ nco Sibe nco 9.96
412
+ ncx Central Puebla Nahuatl ncx 9.86
413
+ ndi Samba Leko ndi 11.27
414
+ ng Ndonga ndo 9.08
415
+ ngi Ngizim ngi 10.06
416
+ nhg Tetelcingo Nahuatl nhg 8.92
417
+ nhi Zacatlán-Ahuacatlán-Tepetzintla Nahuatl nhi 0.05
418
+ nhn Central Nahuatl nhn 9.51
419
+ nhq Huaxcaleca Nahuatl nhq 5.07
420
+ nja Nzanyi nja 10.02
421
+ nl Dutch nld 2264.13
422
+ nla Ngombale nla 8.79
423
+ nlv Orizaba Nahuatl nlv 11.42
424
+ nmg Kwasio nmg 10.39
425
+ nmz Nawdm nmz 6.3
426
+ nn Norwegian Nynorsk nno 1.54
427
+ nnh Ngiemboon nnh 16.15
428
+ no Norwegian nor 3849.8
429
+ noe Nimadi noe 11.12
430
+ npi Nepali npi 171.5
431
+ nso Pedi nso 12.64
432
+ ny Chichewa nya 10.8
433
+ nyu Nyungwe nyu 8.98
434
+ oc Occitan oci 16.8
435
+ odk Od odk 20.26
436
+ odu Odual odu 10.57
437
+ ogo Khana ogo 10.51
438
+ om Oromo orm 6.6
439
+ orc Orma orc 22.01
440
+ oru Ormuri oru 16.74
441
+ ory Odia ory 144.81
442
+ os Iron Ossetic oss 1.38
443
+ pa Panjabi pan 147.37
444
+ pbs Central Pame pbs 9.69
445
+ pbt Southern Pashto pbt 11.6
446
+ pbu Northern Pashto pbu 11.03
447
+ pcm Nigerian Pidgin pcm 11.04
448
+ pex Petats pex 10.2
449
+ phl Phalura phl 20.69
450
+ phr Pahari-Potwari phr 24.03
451
+ pip Pero pip 9.85
452
+ piy Piya-Kwonci piy 10.38
453
+ pko Pökoot pko 10.4
454
+ pl Polish pol 911.68
455
+ plk Kohistani Shina plk 12.75
456
+ plt Plateau Malagasy plt 19.39
457
+ pmq Northern Pame pmq 10.24
458
+ pms Piemontese pms 16.01
459
+ pmy Papuan Malay pmy 10.17
460
+ pnb Western Panjabi pnb 10.0
461
+ poc Poqomam poc 9.63
462
+ poe San Juan Atzingo Popoloca poe 10.01
463
+ pow San Felipe Otlaltepec Popoloca pow 8.84
464
+ prq Ashéninka Perené prq 7.16
465
+ ps Pushto pus 88.62
466
+ pst Central Pashto pst 11.4
467
+ pt Portuguese por 16855.05
468
+ pua Western Highland Purepecha pua 10.17
469
+ pwn Paiwan pwn 13.76
470
+ qug Chimborazo Highland Quichua qug 10.12
471
+ qum Sipacapense qum 9.37
472
+ qup Southern Pastaza Quechua qup 11.13
473
+ qur Yanahuanca Pasco Quechua qur 9.95
474
+ qus Santiago del Estero Quichua qus 9.55
475
+ quv Sacapulteco quv 8.9
476
+ qux Yauyos Quechua qux 9.35
477
+ quy Ayacucho Quechua quy 0.05
478
+ qva Ambo-Pasco Quechua qva 9.59
479
+ qvi Imbabura Highland Quichua qvi 11.0
480
+ qvj Loja Highland Quichua qvj 10.59
481
+ qvl Cajatambo North Lima Quechua qvl 9.95
482
+ qwa Corongo Ancash Quechua qwa 9.72
483
+ qws Sihuas Ancash Quechua qws 10.18
484
+ qxa Chiquián Ancash Quechua qxa 9.99
485
+ qxp Puno Quechua qxp 9.81
486
+ qxt Santa Ana de Tusi Pasco Quechua qxt 10.05
487
+ qxu Arequipa-La Unión Quechua qxu 10.12
488
+ qxw Jauja Wanca Quechua qxw 11.42
489
+ rag Logooli rag 9.39
490
+ rm Romansh roh 9.21
491
+ ro Romanian ron 70.23
492
+ rob Tae' rob 9.02
493
+ rof Rombo rof 18.9
494
+ roo Rotokas roo 9.07
495
+ rth Ratahan rth 9.34
496
+ ru Russian rus 20338.5
497
+ rup Macedo-Romanian rup 0.02
498
+ rw Kinyarwanda kin 2021.66
499
+ sa Sanskrit san 84.44
500
+ sah Yakut sah 16.08
501
+ sat Santali sat 98.37
502
+ sau Saleman sau 10.53
503
+ say Saya say 10.02
504
+ sbn Sindhi Bhil sbn 10.53
505
+ sc Sardinian srd 2.77
506
+ scl Shina scl 9.84
507
+ scn Sicilian scn 13.35
508
+ sd Sindhi snd 46.27
509
+ sei Seri sei 9.81
510
+ shu Chadian Arabic shu 2.29
511
+ si Sinhala sin 11.98
512
+ sip Sikkimese sip 10.07
513
+ siw Siwai siw 10.47
514
+ sjr Siar-Lak sjr 9.87
515
+ sk Slovak slk 2478.46
516
+ skg Sakalava Malagasy skg 9.02
517
+ skr Saraiki skr 4.13
518
+ sl Slovenian slv 1172.61
519
+ sn Shona sna 9.96
520
+ snc Sinaugoro snc 10.38
521
+ snk Soninke snk 10.04
522
+ so Somali som 13.22
523
+ sol Solos sol 9.95
524
+ sps Saposa sps 9.81
525
+ sq Albanian sqi 8.59
526
+ sr Serbian srp 1855.33
527
+ src Logudorese Sardinian src 10.67
528
+ sro Campidanese Sardinian sro 10.16
529
+ ssi Sansi ssi 10.47
530
+ ste Liana-Seti ste 10.43
531
+ sua Sulka sua 10.12
532
+ sv Swedish swe 2453.14
533
+ sva Svan sva 15.11
534
+ sw Swahili swa 418.41
535
+ szy Sakizaya szy 11.47
536
+ ta Tamil tam 423.09
537
+ tan Tangale tan 10.14
538
+ tar Central Tarahumara tar 9.73
539
+ tay Atayal tay 7.02
540
+ tbf Mandara tbf 10.01
541
+ tcf Malinaltepec Me'phaa tcf 9.04
542
+ tcy Tulu tcy 11.72
543
+ tdn Tondano tdn 9.14
544
+ tdx Tandroy-Mahafaly Malagasy tdx 3.81
545
+ te Telugu tel 230.21
546
+ tg Tajik tgk 9.23
547
+ tgc Tigak tgc 9.71
548
+ th Thai tha 10499.77
549
+ the Chitwania Tharu the 10.06
550
+ thq Kochila Tharu thq 10.28
551
+ thr Rana Tharu thr 9.99
552
+ thv Tahaggart Tamahaq thv 4.25
553
+ ti Tigrinya tir 0.08
554
+ tig Tigre tig 7.49
555
+ tio Teop tio 9.85
556
+ tk Turkmen tuk 2.86
557
+ tkg Tesaka Malagasy tkg 17.86
558
+ tkt Kathoriya Tharu tkt 10.64
559
+ tli Tlingit tli 0.41
560
+ tlp Filomena Mata-Coahuitlán Totonac tlp 11.35
561
+ tn Tswana tsn 4.24
562
+ tok Toki Pona tok 13.51
563
+ tpl Tlacoapa Me'phaa tpl 9.28
564
+ tpz Tinputz tpz 9.33
565
+ tqp Tomoip tqp 10.1
566
+ tr Turkish tur 125.36
567
+ trp Kok Borok trp 10.74
568
+ trq San Martín Itunyoso Triqui trq 8.29
569
+ trv Sediq trv 7.77
570
+ trw Torwali trw 14.98
571
+ tt Tatar tat 30.03
572
+ ttj Tooro ttj 10.31
573
+ ttr Tera ttr 9.89
574
+ ttu Torau ttu 9.87
575
+ tui Tupuri tui 9.26
576
+ tul Tula tul 9.79
577
+ tuq Tedaga tuq 10.0
578
+ tuv Turkana tuv 10.17
579
+ tuy Tugen tuy 8.79
580
+ tvo Tidore tvo 10.31
581
+ tvu Tunen tvu 9.85
582
+ tw Twi twi 0.25
583
+ twu Termanu twu 11.45
584
+ txs Tonsea txs 9.32
585
+ txy Tanosy Malagasy txy 12.07
586
+ udl Wuzlam udl 9.23
587
+ ug Uighur uig 428.77
588
+ uk Ukrainian ukr 1851.97
589
+ uki Kui (India) uki 10.77
590
+ umb Umbundu umb 10.59
591
+ ur Urdu urd 211.27
592
+ ush Ushojo ush 6.36
593
+ uz Uzbek uzb 115.28
594
+ uzn Northern Uzbek uzn 15.23
595
+ vai Vai vai 8.76
596
+ var Huarijio var 9.28
597
+ ver Mom Jango ver 10.93
598
+ vi Vietnamese vie 8481.98
599
+ vmc Juxtlahuaca Mixtec vmc 9.43
600
+ vmj Ixtayutla Mixtec vmj 10.17
601
+ vmm Mitlatongo Mixtec vmm 9.95
602
+ vmp Soyaltepec Mazatec vmp 10.17
603
+ vmz Mazatlán Mazatec vmz 9.82
604
+ vot Votic vot 0.1
605
+ vro Võro vro 15.66
606
+ wbl Wakhi wbl 11.67
607
+ wci Waci Gbe wci 8.02
608
+ weo Wemale weo 9.09
609
+ wes Cameroon Pidgin wes 10.06
610
+ wja Waja wja 10.22
611
+ wji Warji wji 11.39
612
+ wo Wolof wol 8.71
613
+ wof Gambian Wolof wof 9.46
614
+ xh Xhosa xho 13.35
615
+ xhe Khetrani xhe 9.4
616
+ xka Kalkoti xka 8.0
617
+ xmf Mingrelian xmf 11.47
618
+ xmv Antankarana Malagasy xmv 17.9
619
+ xmw Tsimihety Malagasy xmw 11.53
620
+ xpe Liberia Kpelle xpe 9.5
621
+ xti Sinicahua Mixtec xti 9.5
622
+ xtu Cuyamecalco Mixtec xtu 9.4
623
+ yaq Yaqui yaq 9.93
624
+ yav Yangben yav 8.7
625
+ yay Agwagwune yay 8.26
626
+ ydd Eastern Yiddish ydd 18.43
627
+ ydg Yidgha ydg 9.89
628
+ yer Tarok yer 10.08
629
+ yes Nyankpa yes 10.26
630
+ yi Yiddish yid 1.81
631
+ yo Yoruba yor 15.66
632
+ yue Cantonese yue 13302.38
633
+ zga Kinga zga 9.5
634
+ zgh Standard Moroccan Tamazight zgh 1.19
635
+ zh Chinese cmn 111343.3
636
+ zoc Copainalá Zoque zoc 10.07
637
+ zoh Chimalapa Zoque zoh 9.35
638
+ zor Rayón Zoque zor 9.04
639
+ zpv Chichicapan Zapotec zpv 9.85
640
+ zpy Mazaltepec Zapotec zpy 9.47
641
+ ztg Xanaguía Zapotec ztg 9.86
642
+ ztn Santa Catarina Albarradas Zapotec ztn 10.02
643
+ ztp Loxicha Zapotec ztp 9.62
644
+ zts Tilquiapan Zapotec zts 9.33
645
+ ztu Güilá Zapotec ztu 9.17
646
+ zu Zulu zul 14.83
647
+ zza Zaza zza 1.52
runtime/omnivoice-python/omnivoice-source/examples/run_emilia.sh ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # This script demonstrates how to run the full training pipeline on the Emilia dataset.
4
+
5
+ set -euo pipefail
6
+
7
+ stage=0
8
+ stop_stage=2
9
+
10
+ # ====== Modify as needed ======
11
+ # GPUs to use
12
+ GPU_IDS="0,1,2,3,4,5,6,7"
13
+ NUM_GPUS=8
14
+
15
+ # Download directory for raw Emilia data
16
+ dl_dir="download"
17
+
18
+ # Directory containing JSONL manifests for train/dev splits
19
+ # Stage 0 will check for the presence of the following files:
20
+ # data/emilia/manifests/emilia_en_train.jsonl
21
+ # data/emilia/manifests/emilia_en_dev.jsonl
22
+ # data/emilia/manifests/emilia_zh_train.jsonl
23
+ # data/emilia/manifests/emilia_zh_dev.jsonl
24
+ MANIFEST_DIR="data/emilia/manifests"
25
+
26
+ # Directory to write tokenized WebDataset shards
27
+ TOKEN_DIR="data/emilia/tokens"
28
+
29
+ # Audio tokenizer model (HuggingFace repo or local path)
30
+ TOKENIZER_PATH="eustlb/higgs-audio-v2-tokenizer"
31
+
32
+ # Training config file
33
+ TRAIN_CONFIG="config/train_config_emilia.json"
34
+
35
+ # Data config file
36
+ data_config="config/data_config_emilia.json"
37
+
38
+ # Output directory for checkpoints
39
+ OUTPUT_DIR="exp/omnivoice_emilia"
40
+ # =================================
41
+
42
+ export PYTHONPATH="$(cd "$(dirname "$0")/.." && pwd):${PYTHONPATH:-}"
43
+
44
+
45
+ # Stage 0: Download data
46
+ if [ $stage -le 0 ] && [ $stop_stage -ge 0 ]; then
47
+ echo "Stage 0: Download data"
48
+
49
+ # You should manually download the Emilia dataset from
50
+ # https://openxlab.org.cn/datasets/Amphion/Emilia
51
+ # or https://huggingface.co/datasets/amphion/Emilia-Dataset/tree/fc71e07
52
+ # and place it in the download directory.
53
+ # Your download directory should at least contain the following structure:
54
+ #
55
+ # download/Amphion___Emilia
56
+ # ├── raw
57
+ # │ ├── EN
58
+ # │ └── ZH
59
+
60
+ if [ ! -d "$dl_dir"/Amphion___Emilia/raw ]; then
61
+ echo "Please refer https://openxlab.org.cn/datasets/Amphion/Emilia to download the dataset."
62
+ exit 1
63
+ fi
64
+
65
+ # We require JSONL manifests for the training and dev splits. You can
66
+ # either generate them yourself using the raw data and the provided
67
+ # metadata, or download our processed JSONL manifests from HuggingFace.
68
+ # https://huggingface.co/datasets/zhu-han/Emilia-Manifests
69
+ #
70
+ # Place them as data/emilia/manifests/{emilia_en_train,emilia_en_dev,emilia_zh_train,emilia_zh_dev}.jsonl
71
+
72
+ for split in emilia_en_dev emilia_zh_dev emilia_en_train emilia_zh_train; do
73
+ if [ ! -f "${MANIFEST_DIR}/${split}.jsonl" ]; then
74
+ echo "Please download the manifest for ${split} and place it in ${MANIFEST_DIR}/${split}.jsonl"
75
+ exit 1
76
+ fi
77
+ done
78
+
79
+ echo " Done. All manifests and data are in place."
80
+ fi
81
+
82
+
83
+ # Stage 1: Tokenize splits into directories matching data_config_emilia.json
84
+ if [ $stage -le 1 ] && [ $stop_stage -ge 1 ]; then
85
+ echo "Stage 1: Tokenizing audio"
86
+
87
+ for split in emilia_en_dev emilia_zh_dev emilia_en_train emilia_zh_train; do
88
+ echo " Tokenizing ${split} from ${MANIFEST_DIR}/${split}.jsonl"
89
+
90
+ CUDA_VISIBLE_DEVICES=${GPU_IDS} \
91
+ python -m omnivoice.scripts.extract_audio_tokens \
92
+ --input_jsonl "${MANIFEST_DIR}/${split}.jsonl" \
93
+ --tar_output_pattern "${TOKEN_DIR}/${split}/audios/shard-%06d.tar" \
94
+ --jsonl_output_pattern "${TOKEN_DIR}/${split}/txts/shard-%06d.jsonl" \
95
+ --tokenizer_path "${TOKENIZER_PATH}" \
96
+ --nj_per_gpu 3 \
97
+ --shuffle True
98
+
99
+ echo " Done. Tokens written to ${TOKEN_DIR}/${split}"
100
+ done
101
+ fi
102
+
103
+
104
+ # Stage 2: Train
105
+ if [ $stage -le 2 ] && [ $stop_stage -ge 2 ]; then
106
+ echo "Stage 2: Training"
107
+
108
+ accelerate launch \
109
+ --gpu_ids "${GPU_IDS}" \
110
+ --num_processes ${NUM_GPUS} \
111
+ -m omnivoice.cli.train \
112
+ --train_config ${TRAIN_CONFIG} \
113
+ --data_config ${data_config} \
114
+ --output_dir ${OUTPUT_DIR}
115
+ fi
runtime/omnivoice-python/omnivoice-source/examples/run_eval.sh ADDED
@@ -0,0 +1,283 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Evaluate OmniVoice models on TTS benchmarks.
4
+
5
+ # Stage 1: Download the test sets and evaluation models.
6
+ # Stage 2: LibriSpeech-PC
7
+ # Stage 3: seedtts_en
8
+ # Stage 4: seedtts_zh
9
+ # Stage 5: fleurs
10
+ # Stage 6: minimax
11
+
12
+ set -euo pipefail
13
+
14
+ # Specify the stages to run by setting the `stage` and `stop_stage` variables.
15
+ stage=1
16
+ stop_stage=6
17
+
18
+ # Available GPUs for evaluation. Adjust this according to your setup.
19
+ export CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7"
20
+
21
+ # Specify the checkpoint to evaluate.
22
+ CHECKPOINT=k2-fsa/OmniVoice
23
+ emilia_checkpoint=false
24
+
25
+ # CHECKPOINT=k2-fsa/OmniVoice
26
+ # emilia_checkpoint=true
27
+
28
+ # For the OmniVoice-Emilia checkpoint, we set denoise to False and lang_id to None
29
+ #, as the model is trained without prompt denoising or language id.
30
+
31
+ if [ "${emilia_checkpoint}" = true ]; then
32
+ infer_options="--preprocess_prompt False \
33
+ --postprocess_output False \
34
+ --batch_duration 600 \
35
+ --denoise False \
36
+ --lang_id None \
37
+ --audio_chunk_threshold 1000"
38
+ else
39
+ infer_options="--preprocess_prompt False \
40
+ --postprocess_output False \
41
+ --batch_duration 600 \
42
+ --audio_chunk_threshold 1000"
43
+ fi
44
+
45
+ export PYTHONPATH="$(cd "$(dirname "$0")/.." && pwd):${PYTHONPATH:-}"
46
+
47
+ download_dir="download"
48
+ TTS_EVAL_MODEL_DIR="${download_dir}/tts_eval_models/"
49
+ TTS_EVAL_DATA_DIR="${download_dir}/tts_eval_datasets/"
50
+
51
+ # Map test_name to its test.jsonl path.
52
+ get_test_list() {
53
+ case "$1" in
54
+ librispeech_pc) echo "${TTS_EVAL_DATA_DIR}/librispeech_pc_test_clean.jsonl" ;;
55
+ seedtts_en) echo "${TTS_EVAL_DATA_DIR}/seedtts_test_en.jsonl" ;;
56
+ seedtts_zh) echo "${TTS_EVAL_DATA_DIR}/seedtts_test_zh.jsonl" ;;
57
+ minimax) echo "${TTS_EVAL_DATA_DIR}/minimax_multilingual_24.jsonl" ;;
58
+ fleurs) echo "${TTS_EVAL_DATA_DIR}/fleurs_multilingual_102.jsonl" ;;
59
+ *) echo ""; return 1 ;;
60
+ esac
61
+ }
62
+
63
+ # ============================================================
64
+ # Stage 1: Prepare the test sets and evaluation models
65
+ # ============================================================
66
+
67
+ if [ ${stage} -le 1 ] && [ ${stop_stage} -ge 1 ]; then
68
+ echo "Stage 1: Download test sets and evaluation models"
69
+
70
+ hf_repo=k2-fsa/TTS_eval_datasets
71
+ mkdir -p ${TTS_EVAL_DATA_DIR}/
72
+ for file in \
73
+ librispeech_pc_test_clean.jsonl \
74
+ librispeech_pc_test_clean_transcript.jsonl \
75
+ seedtts_test_en.jsonl \
76
+ seedtts_test_zh.jsonl \
77
+ minimax_multilingual_24.jsonl \
78
+ fleurs_multilingual_102.jsonl; do
79
+ echo "Downloading ${file}..."
80
+ huggingface-cli download \
81
+ --repo-type dataset \
82
+ --local-dir ${TTS_EVAL_DATA_DIR}/ \
83
+ ${hf_repo} \
84
+ ${file}
85
+ done
86
+
87
+ for file in \
88
+ librispeech_pc_testset.tar.gz \
89
+ seedtts_testset.tar.gz \
90
+ minimax_multilingual_24.tar.gz \
91
+ fleurs_multilingual_102.tar.gz; do
92
+ echo "Downloading ${file}..."
93
+ huggingface-cli download \
94
+ --repo-type dataset \
95
+ --local-dir ${TTS_EVAL_DATA_DIR}/ \
96
+ ${hf_repo} \
97
+ ${file}
98
+
99
+ echo "Extracting ${file}..."
100
+ tar -xzf ${TTS_EVAL_DATA_DIR}/${file} -C ${TTS_EVAL_DATA_DIR}/
101
+ done
102
+
103
+ echo "Download all evaluation models"
104
+ hf_repo=k2-fsa/TTS_eval_models
105
+ mkdir -p ${TTS_EVAL_MODEL_DIR}
106
+ huggingface-cli download \
107
+ --local-dir ${TTS_EVAL_MODEL_DIR} \
108
+ ${hf_repo}
109
+ fi
110
+
111
+ # ============================================================
112
+ # Stage 2: Evaluation on LibriSpeech-PC
113
+ # ============================================================
114
+
115
+
116
+ if [ ${stage} -le 2 ] && [ ${stop_stage} -ge 2 ]; then
117
+ echo "Stage 2: Evaluation on LibriSpeech-PC"
118
+ wav_path="results/librispeech_pc"
119
+ test_jsonl="$(get_test_list librispeech_pc)"
120
+ transcript_jsonl="${TTS_EVAL_DATA_DIR}/librispeech_pc_test_clean_transcript.jsonl"
121
+
122
+ python -m omnivoice.cli.infer_batch \
123
+ --model "${CHECKPOINT}" \
124
+ --test_list "${test_jsonl}" \
125
+ --res_dir "${wav_path}" ${infer_options}
126
+
127
+ python -m omnivoice.eval.speaker_similarity.sim \
128
+ --wav-path "${wav_path}" \
129
+ --test-list "${test_jsonl}" \
130
+ --decode-path "${wav_path}.sim.log" \
131
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
132
+
133
+ python -m omnivoice.eval.wer.hubert \
134
+ --wav-path "${wav_path}" \
135
+ --test-list "${transcript_jsonl}" \
136
+ --decode-path "${wav_path}.wer.log" \
137
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
138
+
139
+ python -m omnivoice.eval.mos.utmos \
140
+ --wav-path "${wav_path}" \
141
+ --test-list "${test_jsonl}" \
142
+ --decode-path "${wav_path}.mos.log" \
143
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
144
+ fi
145
+
146
+
147
+ # ============================================================
148
+ # Stage 3: Evaluation on Seed-TTS en
149
+ # ============================================================
150
+
151
+ if [ ${stage} -le 3 ] && [ ${stop_stage} -ge 3 ]; then
152
+ echo "Stage 3: Evaluation on Seed-TTS en"
153
+ wav_path="results/seedtts_en"
154
+ test_jsonl="$(get_test_list seedtts_en)"
155
+
156
+ python -m omnivoice.cli.infer_batch \
157
+ --model "${CHECKPOINT}" \
158
+ --test_list "${test_jsonl}" \
159
+ --res_dir "${wav_path}" ${infer_options}
160
+
161
+
162
+ python -m omnivoice.eval.speaker_similarity.sim \
163
+ --wav-path "${wav_path}" \
164
+ --test-list "${test_jsonl}" \
165
+ --decode-path "${wav_path}.sim.log" \
166
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
167
+
168
+ python -m omnivoice.eval.wer.seedtts \
169
+ --wav-path "${wav_path}" \
170
+ --test-list "${test_jsonl}" \
171
+ --decode-path "${wav_path}.wer.log" \
172
+ --model-dir "${TTS_EVAL_MODEL_DIR}" \
173
+ --lang en
174
+
175
+ python -m omnivoice.eval.mos.utmos \
176
+ --wav-path "${wav_path}" \
177
+ --test-list "${test_jsonl}" \
178
+ --decode-path "${wav_path}.mos.log" \
179
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
180
+ fi
181
+
182
+
183
+ # ============================================================
184
+ # Stage 4: Evaluation on Seed-TTS zh
185
+ # ============================================================
186
+
187
+ if [ ${stage} -le 4 ] && [ ${stop_stage} -ge 4 ]; then
188
+ echo "Stage 4: Evaluation on Seed-TTS zh"
189
+ wav_path="results/seedtts_zh"
190
+ test_jsonl="$(get_test_list seedtts_zh)"
191
+
192
+ python -m omnivoice.cli.infer_batch \
193
+ --model "${CHECKPOINT}" \
194
+ --test_list "${test_jsonl}" \
195
+ --res_dir "${wav_path}" ${infer_options}
196
+
197
+
198
+ python -m omnivoice.eval.speaker_similarity.sim \
199
+ --wav-path "${wav_path}" \
200
+ --test-list "${test_jsonl}" \
201
+ --decode-path "${wav_path}.sim.log" \
202
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
203
+
204
+ python -m omnivoice.eval.wer.seedtts \
205
+ --wav-path "${wav_path}" \
206
+ --test-list "${test_jsonl}" \
207
+ --decode-path "${wav_path}.wer.log" \
208
+ --model-dir "${TTS_EVAL_MODEL_DIR}" \
209
+ --lang zh
210
+
211
+ python -m omnivoice.eval.mos.utmos \
212
+ --wav-path "${wav_path}" \
213
+ --test-list "${test_jsonl}" \
214
+ --decode-path "${wav_path}.mos.log" \
215
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
216
+ fi
217
+
218
+
219
+
220
+ # ============================================================
221
+ # Stage 5: Evaluation on MiniMax multilingual
222
+ # ============================================================
223
+
224
+ if [ ${stage} -le 5 ] && [ ${stop_stage} -ge 5 ]; then
225
+ echo "Stage 5: Evaluation on MiniMax multilingual"
226
+ wav_path="results/minimax"
227
+ test_jsonl="$(get_test_list minimax)"
228
+
229
+ python -m omnivoice.cli.infer_batch \
230
+ --model "${CHECKPOINT}" \
231
+ --test_list "${test_jsonl}" \
232
+ --res_dir "${wav_path}" ${infer_options}
233
+
234
+ python -m omnivoice.eval.speaker_similarity.sim \
235
+ --wav-path "${wav_path}" \
236
+ --test-list "${test_jsonl}" \
237
+ --decode-path "${wav_path}.sim.log" \
238
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
239
+
240
+ python -m omnivoice.eval.wer.minimax \
241
+ --wav-path "${wav_path}" \
242
+ --test-list "${test_jsonl}" \
243
+ --decode-path "${wav_path}.wer.log" \
244
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
245
+ fi
246
+
247
+
248
+ # ============================================================
249
+ # Stage 6: Evaluation on FLEURS multilingual
250
+ # ============================================================
251
+
252
+ if [ ${stage} -le 6 ] && [ ${stop_stage} -ge 6 ]; then
253
+ echo "Stage 6: Evaluation on FLEURS multilingual"
254
+ wav_path="results/fleurs"
255
+ test_jsonl="$(get_test_list fleurs)"
256
+
257
+ python -m omnivoice.cli.infer_batch \
258
+ --model "${CHECKPOINT}" \
259
+ --test_list "${test_jsonl}" \
260
+ --res_dir "${wav_path}" ${infer_options}
261
+
262
+
263
+ python -m omnivoice.eval.speaker_similarity.sim \
264
+ --wav-path "${wav_path}" \
265
+ --test-list "${test_jsonl}" \
266
+ --decode-path "${wav_path}.sim.log" \
267
+ --model-dir "${TTS_EVAL_MODEL_DIR}"
268
+
269
+ # Evaluation on FLEURS requires omnilingual-asr, which has dependencies that
270
+ # conflict with other packages (at least the transformers package) in our project.
271
+
272
+ # To evaluate on FLEURS, we suggest users to set up a separate virtual
273
+ # environment to install omnilingual-asr. Install instructions can be found in
274
+ # https://github.com/facebookresearch/omnilingual-asr
275
+
276
+ python ${PWD}/../omnivoice/eval/wer/fleurs.py \
277
+ --wav-path "${wav_path}" \
278
+ --test-list "${test_jsonl}" \
279
+ --decode-path "${wav_path}.wer.log" \
280
+ --model-card omniASR_LLM_Unlimited_7B_v2 \
281
+ --chunk-size 100 \
282
+ --batch-size 50
283
+ fi
runtime/omnivoice-python/omnivoice-source/examples/run_finetune.sh ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # This script demonstrates how to fine-tune OmniVoice from a JSONL manifest.
4
+
5
+ set -euo pipefail
6
+
7
+ stage=0
8
+ stop_stage=1
9
+
10
+ # ====== Modify as needed ======
11
+ # GPUs to use
12
+ GPU_IDS="0,1"
13
+ NUM_GPUS=2
14
+
15
+ # Path to your input JSONL file
16
+ # (each line: {"id": ..., "audio_path": ..., "text": ..., "language_id": ...})
17
+ TRAIN_JSONL="data/my_data_train.jsonl"
18
+
19
+ # Path to your dev JSONL file. Set to empty string to skip dev set.
20
+ DEV_JSONL="data/my_data_dev.jsonl"
21
+
22
+ # Directory to write tokenized WebDataset shards
23
+ TOKEN_DIR="data/finetune/tokens"
24
+
25
+ # Audio tokenizer model (HuggingFace repo or local path)
26
+ TOKENIZER_PATH="eustlb/higgs-audio-v2-tokenizer"
27
+
28
+ # Training config file
29
+ # If you encounter issues with flex_attention on your GPU, use the SDPA config instead:
30
+ # TRAIN_CONFIG="config/train_config_finetune_sdpa.json"
31
+ TRAIN_CONFIG="config/train_config_finetune.json"
32
+
33
+ # Data config file
34
+ data_config="config/data_config_finetune.json"
35
+
36
+ # Output directory for fine-tuned checkpoints
37
+ OUTPUT_DIR="exp/omnivoice_finetune"
38
+ # =================================
39
+
40
+ export PYTHONPATH="$(cd "$(dirname "$0")/.." && pwd):${PYTHONPATH:-}"
41
+
42
+
43
+ # Stage 0: Tokenize audio into WebDataset shards
44
+ if [ $stage -le 0 ] && [ $stop_stage -ge 0 ]; then
45
+ echo "Stage 0: Tokenizing audio"
46
+
47
+ for split_jsonl_path in ${TRAIN_JSONL} ${DEV_JSONL}; do
48
+ if [ -z "${split_jsonl_path}" ]; then
49
+ continue
50
+ fi
51
+
52
+ if [ "${split_jsonl_path}" = "${TRAIN_JSONL}" ]; then
53
+ split="train"
54
+ else
55
+ split="dev"
56
+ fi
57
+
58
+ echo " Tokenizing ${split} from ${split_jsonl_path}"
59
+
60
+ CUDA_VISIBLE_DEVICES=${GPU_IDS} \
61
+ python -m omnivoice.scripts.extract_audio_tokens \
62
+ --input_jsonl "${split_jsonl_path}" \
63
+ --tar_output_pattern "${TOKEN_DIR}/${split}/audios/shard-%06d.tar" \
64
+ --jsonl_output_pattern "${TOKEN_DIR}/${split}/txts/shard-%06d.jsonl" \
65
+ --tokenizer_path "${TOKENIZER_PATH}" \
66
+ --nj_per_gpu 3 \
67
+ --shuffle True
68
+
69
+ echo " Done. Manifest written to ${TOKEN_DIR}/${split}/data.lst"
70
+ done
71
+ fi
72
+
73
+
74
+ # Stage 1: Fine-tune
75
+ if [ $stage -le 1 ] && [ $stop_stage -ge 1 ]; then
76
+ echo "Stage 1: Fine-tuning"
77
+
78
+ accelerate launch \
79
+ --gpu_ids "${GPU_IDS}" \
80
+ --num_processes ${NUM_GPUS} \
81
+ -m omnivoice.cli.train \
82
+ --train_config ${TRAIN_CONFIG} \
83
+ --data_config ${data_config} \
84
+ --output_dir ${OUTPUT_DIR}
85
+ fi
runtime/omnivoice-python/omnivoice-source/omnivoice/eval/wer/punctuations.lst ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+  7355 INVALID UNICODE 0x81
2
+  5265 INVALID UNICODE 0x90
3
+  75 INVALID UNICODE 0x8
4
+  31 INVALID UNICODE 0x8d
5
+ ” 3 INVALID UNICODE 0x94
6
+  2 INVALID UNICODE 0x8f
7
+  2 INVALID UNICODE 0x1a
8
+  1 INVALID UNICODE 0x9d
9
+ “ 1 INVALID UNICODE 0x93
10
+ ’ 1 INVALID UNICODE 0x92
11
+  8647 INVALID UNICODE 0xe295
12
+  6650 INVALID UNICODE 0xf21d
13
+  6234 INVALID UNICODE 0xf62d
14
+  4815 INVALID UNICODE 0xf173
15
+  4789 INVALID UNICODE 0xe514
16
+  4409 INVALID UNICODE 0xe293
17
+  3881 INVALID UNICODE 0xf523
18
+  3788 INVALID UNICODE 0xe233
19
+  2448 INVALID UNICODE 0xf50f
20
+  2177 INVALID UNICODE 0xe232
21
+  1955 INVALID UNICODE 0xea7b
22
+  1926 INVALID UNICODE 0xf172
23
+  973 INVALID UNICODE 0xe290
24
+  972 INVALID UNICODE 0xf519
25
+  661 INVALID UNICODE 0xe292
26
+  591 INVALID UNICODE 0xe328
27
+  509 INVALID UNICODE 0xe2fa
28
+  458 INVALID UNICODE 0xe234
29
+  446 INVALID UNICODE 0xe043
30
+  419 INVALID UNICODE 0xe040
31
+  399 INVALID UNICODE 0xe2fb
32
+  387 INVALID UNICODE 0xe32b
33
+  381 INVALID UNICODE 0xe236
34
+  374 INVALID UNICODE 0xf511
35
+  314 INVALID UNICODE 0xe517
36
+  296 INVALID UNICODE 0xe2fe
37
+  293 INVALID UNICODE 0xe492
38
+  291 INVALID UNICODE 0xf52d
39
+  289 INVALID UNICODE 0xe2fc
40
+  195 INVALID UNICODE 0xf521
41
+  190 INVALID UNICODE 0xe516
42
+  182 INVALID UNICODE 0xe041
43
+  178 INVALID UNICODE 0xf529
44
+  113 INVALID UNICODE 0xe2f9
45
+  87 INVALID UNICODE 0xe2d9
46
+  78 INVALID UNICODE 0xe32a
47
+  76 INVALID UNICODE 0xe291
48
+  74 INVALID UNICODE 0xe296
49
+  66 INVALID UNICODE 0xe518
50
+  52 INVALID UNICODE 0xe32c
51
+  46 INVALID UNICODE 0xe2db
52
+  41 INVALID UNICODE 0xe231
53
+  34 INVALID UNICODE 0xf522
54
+  33 INVALID UNICODE 0xf518
55
+  32 INVALID UNICODE 0xf513
56
+  27 INVALID UNICODE 0xe32d
57
+  25 INVALID UNICODE 0xe32e
58
+  23 INVALID UNICODE 0xe06b
59
+  15 INVALID UNICODE 0xea01
60
+  12 INVALID UNICODE 0xe294
61
+  11 INVALID UNICODE 0xe203
62
+  8 INVALID UNICODE 0xf218
63
+  7 INVALID UNICODE 0xe070
64
+  7 INVALID UNICODE 0xe013
65
+  5 INVALID UNICODE 0xe2de
66
+  4 INVALID UNICODE 0xe493
67
+  3 INVALID UNICODE 0xf7e8
68
+  3 INVALID UNICODE 0xf7d0
69
+  3 INVALID UNICODE 0xe313
70
+  2 INVALID UNICODE 0xe329
71
+  2 INVALID UNICODE 0xe06d
72
+  2 INVALID UNICODE 0xe003
73
+  1 INVALID UNICODE 0xf50e
74
+  1 INVALID UNICODE 0xf171
75
+  1 INVALID UNICODE 0xe01d
76
+  71 NOMINAL DIGIT SHAPES 0x206f
77
+ ⁠ 3 WORD JOINER 0x2060
78
+ ― 126545 HORIZONTAL BAR 0x2015
79
+ ־ 1028 HEBREW PUNCTUATION MAQAF 0x5be
80
+ ) 98429 RIGHT PARENTHESIS 0x29
81
+ ] 27108 RIGHT SQUARE BRACKET 0x5d
82
+ ⌋ 1567 RIGHT FLOOR 0x230b
83
+ 〕 97 RIGHT TORTOISE SHELL BRACKET 0x3015
84
+ 】 36 RIGHT BLACK LENTICULAR BRACKET 0x3011
85
+ ﴾ 14 ORNATE LEFT PARENTHESIS 0xfd3e
86
+ & 170517 AMPERSAND 0x26
87
+ ། 106330 TIBETAN MARK SHAD 0xf0d
88
+ ። 90203 ETHIOPIC FULL STOP 0x1362
89
+ ፥ 60484 ETHIOPIC COLON 0x1365
90
+ ༌ 60464 TIBETAN MARK DELIMITER TSHEG BSTAR 0xf0c
91
+ ။ 51567 MYANMAR SIGN SECTION 0x104b
92
+ / 46929 SOLIDUS 0x2f
93
+ ၊ 38042 MYANMAR SIGN LITTLE SECTION 0x104a
94
+ · 37985 MIDDLE DOT 0xb7
95
+ ‸ 36310 CARET 0x2038
96
+ * 34793 ASTERISK 0x2a
97
+ ۔ 32432 ARABIC FULL STOP 0x6d4
98
+ ፤ 31906 ETHIOPIC SEMICOLON 0x1364
99
+ ၏ 21519 MYANMAR SYMBOL GENITIVE 0x104f
100
+ ។ 20834 KHMER SIGN KHAN 0x17d4
101
+ ꓾ 15773 LISU PUNCTUATION COMMA 0xa4fe
102
+ ᙮ 13473 CANADIAN SYLLABICS FULL STOP 0x166e
103
+ ꤯ 12892 KAYAH LI SIGN SHYA 0xa92f
104
+ ⵰ 11478 TIFINAGH SEPARATOR MARK 0x2d70
105
+ ꓿ 11118 LISU PUNCTUATION FULL STOP 0xa4ff
106
+ ॥ 10763 DEVANAGARI DOUBLE DANDA 0x965
107
+ ؞ 10403 ARABIC TRIPLE DOT PUNCTUATION MARK 0x61e
108
+ ၍ 8936 MYANMAR SYMBOL COMPLETED 0x104d
109
+ · 8431 GREEK ANO TELEIA 0x387
110
+ † 7477 DAGGER 0x2020
111
+ ၌ 6632 MYANMAR SYMBOL LOCATIVE 0x104c
112
+ ፣ 5719 ETHIOPIC COMMA 0x1363
113
+ ៖ 5528 KHMER SIGN CAMNUC PII KUUH 0x17d6
114
+ ꤮ 4791 KAYAH LI SIGN CWI 0xa92e
115
+ ※ 3439 REFERENCE MARK 0x203b
116
+ ፦ 2727 ETHIOPIC PREFACE COLON 0x1366
117
+ • 1749 BULLET 0x2022
118
+ ¶ 1507 PILCROW SIGN 0xb6
119
+ ၎ 1386 MYANMAR SYMBOL AFOREMENTIONED 0x104e
120
+ ﹖ 1224 SMALL QUESTION MARK 0xfe56
121
+ ; 975 GREEK QUESTION MARK 0x37e
122
+ … 827 HORIZONTAL ELLIPSIS 0x2026
123
+ % 617 PERCENT SIGN 0x25
124
+ ・ 468 KATAKANA MIDDLE DOT 0x30fb
125
+ ༎ 306 TIBETAN MARK NYIS SHAD 0xf0e
126
+ ‡ 140 DOUBLE DAGGER 0x2021
127
+ # 137 NUMBER SIGN 0x23
128
+ @ 125 COMMERCIAL AT 0x40
129
+ ፡ 121 ETHIOPIC WORDSPACE 0x1361
130
+ ៚ 55 KHMER SIGN KOOMUUT 0x17da
131
+ ៕ 49 KHMER SIGN BARIYOOSAN 0x17d5
132
+ ﹐ 10 SMALL COMMA 0xfe50
133
+ ༅ 6 TIBETAN MARK CLOSING YIG MGO SGAB MA 0xf05
134
+ ༄ 6 TIBETAN MARK INITIAL YIG MGO MDUN MA 0xf04
135
+ . 2 FULLWIDTH FULL STOP 0xff0e
136
+ ﹗ 2 SMALL EXCLAMATION MARK 0xfe57
137
+ ﹕ 2 SMALL COLON 0xfe55
138
+ ‰ 2 PER MILLE SIGN 0x2030
139
+ ・ 1 HALFWIDTH KATAKANA MIDDLE DOT 0xff65
140
+ ( 98504 LEFT PARENTHESIS 0x28
141
+ [ 27245 LEFT SQUARE BRACKET 0x5b
142
+ ⌊ 1567 LEFT FLOOR 0x230a
143
+ 〔 95 LEFT TORTOISE SHELL BRACKET 0x3014
144
+ 【 36 LEFT BLACK LENTICULAR BRACKET 0x3010
145
+ ﴿ 14 ORNATE RIGHT PARENTHESIS 0xfd3f
146
+ _ 4851 LOW LINE 0x5f
147
+ $ 72 DOLLAR SIGN 0x24
148
+ € 14 EURO SIGN 0x20ac
149
+ £ 2 POUND SIGN 0xa3
150
+ ~ 27462 TILDE 0x7e
151
+ = 11450 EQUALS SIGN 0x3d
152
+ | 8430 VERTICAL LINE 0x7c
153
+ − 3971 MINUS SIGN 0x2212
154
+ ≫ 1904 MUCH GREATER-THAN 0x226b
155
+ ≪ 1903 MUCH LESS-THAN 0x226a
156
+ + 1450 PLUS SIGN 0x2b
157
+ < 345 FULLWIDTH LESS-THAN SIGN 0xff1c
158
+ > 344 FULLWIDTH GREATER-THAN SIGN 0xff1e
159
+ ¬ 5 NOT SIGN 0xac
160
+ × 4 MULTIPLICATION SIGN 0xd7
161
+ → 2 RIGHTWARDS ARROW 0x2192
162
+ ᙭ 537 CANADIAN SYLLABICS CHI SIGN 0x166d
163
+ ° 499 DEGREE SIGN 0xb0
164
+ ႟ 421 MYANMAR SYMBOL SHAN EXCLAMATION 0x109f
165
+ � 192 REPLACEMENT CHARACTER 0xfffd
166
+ ⌟ 54 BOTTOM RIGHT CORNER 0x231f
167
+ ⌞ 54 BOTTOM LEFT CORNER 0x231e
168
+ © 2 COPYRIGHT SIGN 0xa9
169
+   40 NARROW NO-BREAK SPACE 0x202f
170
+   1 SIX-PER-EM SPACE 0x2006
171
+ ˜ 40261 SMALL TILDE 0x2dc
172
+ ^ 6469 CIRCUMFLEX ACCENT 0x5e
173
+ ¯ 20 MACRON 0xaf
174
+ ˇ 191442 CARON 0x2c7
175
+ ⁿ 38144 SUPERSCRIPT LATIN SMALL LETTER N 0x207f
176
+ ـ 9440 ARABIC TATWEEL 0x640
177
+ ๆ 6766 THAI CHARACTER MAIYAMOK 0xe46
178
+ ៗ 3310 KHMER SIGN LEK TOO 0x17d7
179
+ 々 678 IDEOGRAPHIC ITERATION MARK 0x3005
180
+ ໆ 430 LAO KO LA 0xec6
181
+ ー 319 KATAKANA-HIRAGANA PROLONGED SOUND MARK 0x30fc
182
+ ⁱ 137 SUPERSCRIPT LATIN SMALL LETTER I 0x2071
183
+ ৷ 11056 BENGALI CURRENCY NUMERATOR FOUR 0x9f7
184
+ ⅓ 26 VULGAR FRACTION ONE THIRD 0x2153
185
+ ½ 26 VULGAR FRACTION ONE HALF 0xbd
186
+ ¼ 4 VULGAR FRACTION ONE QUARTER 0xbc
187
+ ⅟ 1 FRACTION NUMERATOR ONE 0x215f
188
+ ⁄ 57 FRACTION SLASH 0x2044
runtime/omnivoice-python/omnivoice-source/omnivoice/scripts/extract_audio_tokens.py ADDED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright 2026 Xiaomi Corp. (authors: Han Zhu)
3
+ #
4
+ # See ../../LICENSE for clarification regarding multiple authors
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
+ """
19
+ Extract audio tokens from audio data and pack them into WebDataset shards.
20
+
21
+ Supports two input modes:
22
+
23
+ 1. WebDataset manifest (data.lst):
24
+ python extract_audio_tokens.py \
25
+ --input_manifest data.lst \
26
+ --tar_output_pattern output/audios/shard-%06d.tar \
27
+ --jsonl_output_pattern output/txts/shard-%06d.jsonl
28
+
29
+ 2. Raw JSONL (each line: {"id": "...", "audio_path": "...", "text": "...", ...}):
30
+ python extract_audio_tokens.py \
31
+ --input_jsonl data.jsonl \
32
+ --tar_output_pattern output/audios/shard-%06d.tar \
33
+ --jsonl_output_pattern output/txts/shard-%06d.jsonl
34
+
35
+ Output structure:
36
+ output_dir/
37
+ ├── audios/ # WebDataset tar shards (.npy audio tokens + .json metadata)
38
+ │ ├── shard_000000.tar
39
+ │ └── ...
40
+ ├── txts/ # Per-shard JSONL metadata
41
+ │ ├── shard_000000.jsonl
42
+ │ └── ...
43
+ ├── data.lst # Manifest: <tar_path> <jsonl_path> <sample_count> <total_duration>
44
+ └── errors.jsonl # Failed samples with error details
45
+ """
46
+
47
+ import argparse
48
+ import io
49
+ import json
50
+ import logging
51
+ import multiprocessing as mp
52
+ import os
53
+ import warnings
54
+ from concurrent.futures import FIRST_COMPLETED, ProcessPoolExecutor, wait
55
+ from pathlib import Path
56
+ from typing import Any
57
+
58
+ import numpy as np
59
+ import torch
60
+ import webdataset as wds
61
+ from torch.utils.data import DataLoader, IterableDataset
62
+ from tqdm.auto import tqdm
63
+ from transformers import AutoFeatureExtractor, HiggsAudioV2TokenizerModel
64
+
65
+ from omnivoice.data.dataset import JsonlDatasetReader, WebDatasetReader
66
+ from omnivoice.utils.common import str2bool
67
+
68
+ warnings.filterwarnings(
69
+ "ignore", category=FutureWarning, module="torch.nn.utils.weight_norm"
70
+ )
71
+
72
+ HIGGS_INPUT_SAMPLE_RATE = 24_000
73
+
74
+
75
+ # Global variables: Store tokenizer and device for each worker process
76
+ worker_tokenizer = None
77
+ worker_feature_extractor = None
78
+
79
+
80
+ def build_parser() -> argparse.ArgumentParser:
81
+ parser = argparse.ArgumentParser(description=__doc__)
82
+ parser.add_argument(
83
+ "--input_manifest",
84
+ default=None,
85
+ help="Path to input dataset manifest (data.lst).",
86
+ )
87
+ parser.add_argument(
88
+ "--input_jsonl",
89
+ default=None,
90
+ help="Path to raw JSONL file (alternative to --input_manifest).",
91
+ )
92
+ parser.add_argument(
93
+ "--tar_output_pattern",
94
+ required=True,
95
+ help="Tar shard pattern passed to WebDataset",
96
+ )
97
+ parser.add_argument(
98
+ "--jsonl_output_pattern",
99
+ required=True,
100
+ help="Jsonl shard pattern passed to WebDataset",
101
+ )
102
+ parser.add_argument(
103
+ "--samples_per_shard",
104
+ type=int,
105
+ default=1000,
106
+ help="Maximum records per shard",
107
+ )
108
+ parser.add_argument(
109
+ "--min_num_shards",
110
+ type=int,
111
+ default=32,
112
+ help="Minimum number of output shards (use to ensure "
113
+ "shard count >= num_gpu * num_workers)",
114
+ )
115
+ parser.add_argument(
116
+ "--tokenizer_path",
117
+ type=str,
118
+ default="eustlb/higgs-audio-v2-tokenizer",
119
+ help="Path to audio tokenizer.",
120
+ )
121
+ parser.add_argument(
122
+ "--skip_errors", action="store_true", help="Skip items that fail to process"
123
+ )
124
+ parser.add_argument(
125
+ "--min_length",
126
+ type=float,
127
+ default=0.0,
128
+ help="Minimum audio duration in seconds (e.g. 2.0)",
129
+ )
130
+ parser.add_argument(
131
+ "--max_length",
132
+ type=float,
133
+ default=float("inf"),
134
+ help="Maximum audio duration in seconds (e.g. 15.0)",
135
+ )
136
+ parser.add_argument(
137
+ "--num_machines",
138
+ type=int,
139
+ default=1,
140
+ help="Total number of machines for distributed runs",
141
+ )
142
+ parser.add_argument(
143
+ "--machine_index",
144
+ type=int,
145
+ default=0,
146
+ help="Zero-based machine index when distributing across multiple "
147
+ "machines (e.g. 0, 1, ... num_machines-1)",
148
+ )
149
+ parser.add_argument(
150
+ "--nj_per_gpu",
151
+ type=int,
152
+ default=3,
153
+ help="Number of worker processes to spawn per GPU.",
154
+ )
155
+ parser.add_argument(
156
+ "--loader_workers",
157
+ type=int,
158
+ default=24,
159
+ help="Number of DataLoader workers for streaming IterableDataset.",
160
+ )
161
+ parser.add_argument(
162
+ "--shuffle",
163
+ type=str2bool,
164
+ default=True,
165
+ help="Shuffle data by default.",
166
+ )
167
+ parser.add_argument(
168
+ "--shuffle-seed",
169
+ type=int,
170
+ default=42,
171
+ help="Random seed for shuffle (default: 42).",
172
+ )
173
+ return parser
174
+
175
+
176
+ def count_lines(path):
177
+ with open(path, "rb") as f:
178
+ return sum(buf.count(b"\n") for buf in iter(lambda: f.read(1 << 20), b""))
179
+
180
+
181
+ def serialise_numpy(key: str, tokens: np.ndarray) -> dict:
182
+ buffer = io.BytesIO()
183
+ np.save(buffer, tokens)
184
+ return {"__key__": key, "npy": buffer.getvalue()}
185
+
186
+
187
+ def process_init(rank_queue, tokenizer_path):
188
+ """
189
+ Initialization function for each worker process.
190
+ Assigns a specific GPU to the process and loads the tokenizer.
191
+ """
192
+ global worker_tokenizer, worker_feature_extractor
193
+
194
+ # Configure worker process logging
195
+ formatter = (
196
+ "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d]"
197
+ " [Worker %(process)d] %(message)s"
198
+ )
199
+ logging.basicConfig(format=formatter, level=logging.INFO, force=True)
200
+
201
+ # Get assigned GPU rank
202
+ rank = rank_queue.get()
203
+ # Determine device
204
+ if rank != -1 and torch.cuda.is_available():
205
+ worker_device = torch.device(f"cuda:{rank}")
206
+ else:
207
+ worker_device = torch.device("cpu")
208
+
209
+ logging.debug(f"Worker process initialized with device: {worker_device}")
210
+ # Load tokenizer onto the specified device
211
+ worker_feature_extractor = AutoFeatureExtractor.from_pretrained(tokenizer_path)
212
+ worker_tokenizer = HiggsAudioV2TokenizerModel.from_pretrained(
213
+ tokenizer_path, device_map=worker_device
214
+ )
215
+ logging.debug(f"Tokenizer loaded successfully on device {worker_device}")
216
+
217
+
218
+ def process_single_sample(sample: dict[str, Any]) -> dict[str, Any]:
219
+ """
220
+ Single-sample processing function executed in worker processes.
221
+ Skips invalid samples during streaming processing.
222
+ """
223
+ try:
224
+ audio_tensor = sample.get("audio", None) # shape (1, T)
225
+ if audio_tensor is None:
226
+ raise ValueError("Sample missing 'audio' field")
227
+
228
+ with torch.inference_mode():
229
+ key = sample["label"]["id"]
230
+ inputs = worker_feature_extractor(
231
+ raw_audio=audio_tensor.squeeze(0).numpy(),
232
+ sampling_rate=HIGGS_INPUT_SAMPLE_RATE,
233
+ return_tensors="pt",
234
+ ).to(worker_tokenizer.device)
235
+ audio_tokens = worker_tokenizer.encode(
236
+ inputs["input_values"],
237
+ ).audio_codes.squeeze(0)
238
+
239
+ assert len(audio_tokens.shape) == 2
240
+ assert audio_tokens.size(0) == 8
241
+
242
+ num_tokens = audio_tokens.size(1)
243
+ metadata = sample["label"]
244
+ metadata["num_tokens"] = num_tokens
245
+
246
+ # Convert to numpy format for subsequent serialization (int16 to save space)
247
+ audio_tokens_np = audio_tokens.to(torch.int16).cpu().numpy()
248
+
249
+ return {
250
+ "status": "success",
251
+ "key": key,
252
+ "audio_tokens": audio_tokens_np,
253
+ "metadata": metadata,
254
+ "error_msg": None,
255
+ }
256
+ except Exception as e:
257
+ sample_id = sample.get("label", {}).get("id", "unknown")
258
+ logging.error(f"Failed to process sample {sample_id}: {e}")
259
+ return {
260
+ "status": "error",
261
+ "key": sample_id,
262
+ "audio_tokens": None,
263
+ "metadata": None,
264
+ "error_msg": str(e),
265
+ }
266
+
267
+
268
+ def _normalise_value(value: Any) -> Any:
269
+ """Convert tensors and NumPy scalars to serialisable Python objects."""
270
+ if isinstance(value, torch.Tensor):
271
+ if value.ndim == 0:
272
+ return value.item()
273
+ return value.cpu().tolist()
274
+ if isinstance(value, np.generic):
275
+ return value.item()
276
+ if isinstance(value, np.ndarray):
277
+ return value.tolist()
278
+ return value
279
+
280
+
281
+ def _encode_metadata(metadata: dict[str, Any]) -> bytes:
282
+ cleaned: dict[str, Any] = {}
283
+ for key, value in metadata.items():
284
+ if value is None:
285
+ continue
286
+ cleaned[key] = _normalise_value(value)
287
+ return json.dumps(cleaned, ensure_ascii=False).encode("utf-8")
288
+
289
+
290
+ class StreamingLengthFilteredDataset(IterableDataset):
291
+ def __init__(
292
+ self,
293
+ base_iterable,
294
+ min_len: float,
295
+ max_len: float,
296
+ sr: int,
297
+ ):
298
+ self.base_iterable = base_iterable
299
+ self.min_len = min_len
300
+ self.max_len = max_len
301
+ self.sr = sr
302
+ self.filtered_count = 0
303
+
304
+ def __iter__(self):
305
+ """Stream samples one by one and filter on the fly."""
306
+ for sample in self.base_iterable:
307
+ try:
308
+ duration = sample["audio"].size(-1) / self.sr
309
+ if self.min_len <= duration <= self.max_len:
310
+ yield sample
311
+ else:
312
+ self.filtered_count += 1
313
+ logging.warning(
314
+ f"Filtered sample (duration out of range): "
315
+ f"{sample['label']['id']} ({duration:.2f}s)"
316
+ )
317
+ except Exception as e:
318
+ logging.warning(f"Skipped invalid sample during streaming: {e}")
319
+ continue
320
+
321
+
322
+ def main() -> None:
323
+ formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
324
+ logging.basicConfig(format=formatter, level=logging.INFO, force=True)
325
+ parser = build_parser()
326
+ args = parser.parse_args()
327
+ mp.set_start_method("spawn", force=True)
328
+
329
+ # Validate input arguments
330
+ assert bool(args.input_manifest) != bool(
331
+ args.input_jsonl
332
+ ), "Exactly one of --input_manifest or --input_jsonl must be provided."
333
+
334
+ if args.num_machines > 1:
335
+ assert (
336
+ 0 <= args.machine_index < args.num_machines
337
+ ), f"machine_index {args.machine_index} must be in [0, {args.num_machines})"
338
+
339
+ # Build base dataset and count total samples based on input mode
340
+ if args.input_jsonl:
341
+ logging.info(f"Input mode: raw JSONL ({args.input_jsonl})")
342
+ total_samples = count_lines(args.input_jsonl)
343
+ base_dataset = JsonlDatasetReader(
344
+ args.input_jsonl,
345
+ sample_rate=HIGGS_INPUT_SAMPLE_RATE,
346
+ shuffle=args.shuffle,
347
+ shuffle_seed=args.shuffle_seed,
348
+ )
349
+ loader_workers = args.loader_workers
350
+ else:
351
+ logging.info(f"Input mode: WebDataset manifest ({args.input_manifest})")
352
+ manifest_num_lines = count_lines(args.input_manifest)
353
+ loader_workers = min(args.loader_workers, manifest_num_lines)
354
+ total_samples = 0
355
+ manifests = []
356
+ with open(args.input_manifest, "r", encoding="utf-8") as f:
357
+ for line_id, line in tqdm(
358
+ enumerate(f),
359
+ total=manifest_num_lines,
360
+ desc="Calculating dataset length",
361
+ ):
362
+ items = line.strip().split(" ")
363
+ tar_path, jsonl_path, num_items, duration = (
364
+ items[0],
365
+ items[1],
366
+ int(items[2]),
367
+ float(items[3]),
368
+ )
369
+ assert os.path.exists(tar_path), f"File {tar_path} does not exist."
370
+ assert os.path.exists(jsonl_path), f"File {jsonl_path} does not exist."
371
+ assert jsonl_path.endswith(
372
+ ".jsonl"
373
+ ), f"File {jsonl_path} is not a .jsonl file."
374
+ if (
375
+ args.num_machines > 1
376
+ and line_id % args.num_machines != args.machine_index
377
+ ):
378
+ continue
379
+ total_samples += num_items
380
+ manifests.append((tar_path, jsonl_path, num_items, duration))
381
+ logging.info(
382
+ f"Total shards: {manifest_num_lines}, "
383
+ f"Shards for current index: {len(manifests)}"
384
+ )
385
+ base_dataset = WebDatasetReader(
386
+ manifests=manifests,
387
+ sample_rate=HIGGS_INPUT_SAMPLE_RATE,
388
+ evaluation=True,
389
+ )
390
+
391
+ # Adjust samples_per_shard if min_num_shards would be violated
392
+ samples_per_shard = args.samples_per_shard
393
+ if total_samples > 0:
394
+ estimated_shards = max(
395
+ 1, (total_samples + samples_per_shard - 1) // samples_per_shard
396
+ )
397
+ if estimated_shards < args.min_num_shards:
398
+ samples_per_shard = max(1, total_samples // args.min_num_shards)
399
+ logging.info(
400
+ f"Adjusted samples_per_shard from {args.samples_per_shard} to "
401
+ f"{samples_per_shard} to meet min_num_shards={args.min_num_shards} "
402
+ f"(total_samples={total_samples})"
403
+ )
404
+
405
+ # Apply length filter and create DataLoader
406
+ filtered_dataset = StreamingLengthFilteredDataset(
407
+ base_iterable=base_dataset,
408
+ min_len=args.min_length,
409
+ max_len=args.max_length,
410
+ sr=HIGGS_INPUT_SAMPLE_RATE,
411
+ )
412
+ dataloader = DataLoader(
413
+ dataset=filtered_dataset,
414
+ batch_size=None,
415
+ num_workers=loader_workers,
416
+ persistent_workers=loader_workers > 0,
417
+ pin_memory=False,
418
+ )
419
+
420
+ # Configure multi-GPU multi-process setup
421
+ num_devices = torch.cuda.device_count()
422
+ if num_devices == 0:
423
+ logging.warning("No GPUs detected - using CPU for processing")
424
+ num_processes = args.nj_per_gpu
425
+ else:
426
+ num_processes = num_devices * args.nj_per_gpu
427
+ logging.info(
428
+ f"GPU count: {num_devices}, Processes per GPU: {args.nj_per_gpu}, "
429
+ f"Total processes: {num_processes}"
430
+ )
431
+
432
+ # Shared GPU rank queue for process assignment
433
+ manager = mp.Manager()
434
+ rank_queue = manager.Queue()
435
+ for rank in list(range(num_devices)) * args.nj_per_gpu:
436
+ rank_queue.put(rank)
437
+ if num_devices == 0:
438
+ for _ in range(num_processes):
439
+ rank_queue.put(-1)
440
+
441
+ # Prepare output paths
442
+ tar_output_pattern = str(Path(args.tar_output_pattern).expanduser())
443
+ jsonl_output_pattern = str(Path(args.jsonl_output_pattern).expanduser())
444
+ Path(tar_output_pattern).parent.mkdir(parents=True, exist_ok=True)
445
+ Path(jsonl_output_pattern).parent.mkdir(parents=True, exist_ok=True)
446
+
447
+ # Determine output directory from tar_output_pattern
448
+ output_dir = Path(tar_output_pattern).parent.parent
449
+ error_log_path = str(output_dir / "errors.jsonl")
450
+ manifest_path = str(output_dir / "data.lst")
451
+
452
+ # Setup error logger (writes to errors.jsonl)
453
+ error_logger = logging.getLogger("error_log")
454
+ error_logger.setLevel(logging.ERROR)
455
+ error_logger.handlers.clear()
456
+ error_fh = logging.FileHandler(error_log_path, mode="w", encoding="utf-8")
457
+ error_fh.setFormatter(logging.Formatter("%(message)s"))
458
+ error_logger.addHandler(error_fh)
459
+
460
+ # Progress and error tracking
461
+ processed_count = 0
462
+ error_count = 0
463
+ write_error_count = 0
464
+ failed_ids = []
465
+ shard_idx = 0
466
+ shard_sample_count = 0
467
+ shard_duration = 0.0
468
+ shard_manifest = {} # shard_idx -> (tar_path, jsonl_path, count, duration)
469
+
470
+ tar_writer = None
471
+ jsonl_file = None
472
+
473
+ def open_new_shard():
474
+ nonlocal tar_writer, jsonl_file, shard_idx, shard_sample_count, shard_duration
475
+ if tar_writer is not None:
476
+ tar_writer.close()
477
+ if jsonl_file is not None:
478
+ jsonl_file.close()
479
+ # Record manifest for the previous shard
480
+ if shard_idx > 0 and shard_sample_count > 0:
481
+ prev_idx = shard_idx - 1
482
+ shard_manifest[prev_idx] = (
483
+ os.path.abspath(tar_output_pattern % prev_idx),
484
+ os.path.abspath(jsonl_output_pattern % prev_idx),
485
+ shard_sample_count,
486
+ shard_duration,
487
+ )
488
+ tar_fname = tar_output_pattern % shard_idx
489
+ jsonl_fname = jsonl_output_pattern % shard_idx
490
+ tar_writer = wds.TarWriter(tar_fname)
491
+ jsonl_file = open(jsonl_fname, "w", encoding="utf-8")
492
+ shard_idx += 1
493
+ shard_sample_count = 0
494
+ shard_duration = 0.0
495
+
496
+ def write_sample(key, audio_tokens_np, metadata):
497
+ nonlocal shard_sample_count, write_error_count, shard_duration
498
+ assert tar_writer is not None and jsonl_file is not None
499
+ try:
500
+ token_record = serialise_numpy(key, audio_tokens_np)
501
+ json_record = _encode_metadata(metadata)
502
+ tar_writer.write(token_record)
503
+ jsonl_file.write(json_record.decode("utf-8") + "\n")
504
+ shard_sample_count += 1
505
+ shard_duration += metadata.get("audio_duration", 0.0)
506
+ except Exception as exc:
507
+ write_error_count += 1
508
+ failed_ids.append(key)
509
+ error_logger.error(
510
+ json.dumps({"id": key, "reason": str(exc)}, ensure_ascii=False)
511
+ )
512
+ logging.error(f"Write failed for sample {key}: {exc}")
513
+
514
+ def handle_result(result):
515
+ nonlocal processed_count, error_count
516
+ if result["status"] == "success":
517
+ # Rotate shard if needed
518
+ if tar_writer is None or shard_sample_count >= samples_per_shard:
519
+ open_new_shard()
520
+ write_sample(result["key"], result["audio_tokens"], result["metadata"])
521
+ processed_count += 1
522
+ else:
523
+ error_count += 1
524
+ failed_ids.append(result["key"])
525
+ error_logger.error(
526
+ json.dumps(
527
+ {"id": result["key"], "reason": result["error_msg"]},
528
+ ensure_ascii=False,
529
+ )
530
+ )
531
+ if not args.skip_errors:
532
+ raise RuntimeError(
533
+ f"Sample {result['key']} processing failed due "
534
+ f"to {result['error_msg']} - terminating"
535
+ )
536
+ logging.warning(
537
+ f"Skipping failed sample {result['key']}: {result['error_msg']}"
538
+ )
539
+
540
+ main_progress = tqdm(total=total_samples, desc="Extracting Audio Tokens")
541
+
542
+ try:
543
+ with ProcessPoolExecutor(
544
+ max_workers=num_processes,
545
+ initializer=process_init,
546
+ initargs=(rank_queue, args.tokenizer_path),
547
+ ) as executor:
548
+ logging.info(f"Submitting tasks... ({num_processes} workers)")
549
+ futures = set()
550
+ max_pending = num_processes * 10
551
+
552
+ def drain_completed():
553
+ """Wait for at least one future to complete, process all done."""
554
+ nonlocal futures
555
+ done, _ = wait(futures, return_when=FIRST_COMPLETED)
556
+ for f in done:
557
+ futures.discard(f)
558
+ result = f.result()
559
+ main_progress.update(1)
560
+ handle_result(result)
561
+ main_progress.set_postfix(
562
+ Samples=processed_count,
563
+ Errors=error_count,
564
+ )
565
+
566
+ # Stream samples from DataLoader
567
+ for sample in dataloader:
568
+ if len(futures) >= max_pending:
569
+ drain_completed()
570
+
571
+ future = executor.submit(process_single_sample, sample)
572
+ futures.add(future)
573
+
574
+ # Process remaining futures
575
+ logging.info("Processing remaining pending samples...")
576
+ while futures:
577
+ drain_completed()
578
+
579
+ except Exception:
580
+ logging.error("Critical error during processing", exc_info=True)
581
+ raise
582
+ finally:
583
+ main_progress.close()
584
+ if tar_writer is not None:
585
+ tar_writer.close()
586
+ if jsonl_file is not None:
587
+ jsonl_file.close()
588
+ # Record the last shard in the manifest
589
+ if shard_idx > 0 and shard_sample_count > 0:
590
+ last_idx = shard_idx - 1
591
+ shard_manifest[last_idx] = (
592
+ os.path.abspath(tar_output_pattern % last_idx),
593
+ os.path.abspath(jsonl_output_pattern % last_idx),
594
+ shard_sample_count,
595
+ shard_duration,
596
+ )
597
+
598
+ # Write manifest file (data.lst)
599
+ with open(manifest_path, "w", encoding="utf-8") as mf:
600
+ for idx in sorted(shard_manifest.keys()):
601
+ tar_path, jsonl_path, count, duration = shard_manifest[idx]
602
+ mf.write(f"{tar_path} {jsonl_path} {count} {duration:.3f}\n")
603
+
604
+ # Output final statistics
605
+ total_failed = error_count + write_error_count
606
+ filtered_and_skipped = total_samples - processed_count - total_failed
607
+ logging.info(
608
+ f"Processing Complete - Successful: {processed_count}, Failed: {total_failed}, "
609
+ f"Filtered/Skipped: {filtered_and_skipped}, Shards written: {shard_idx}"
610
+ )
611
+ logging.info(f"Manifest written to: {manifest_path} ({len(shard_manifest)} shards)")
612
+ if total_failed > 0:
613
+ logging.info(f"Error details: {error_log_path}")
614
+ if failed_ids and args.skip_errors:
615
+ logging.warning(
616
+ f"Failed sample IDs (count: {len(failed_ids)}): {failed_ids[:100]}..."
617
+ )
618
+ if write_error_count > 0 and not args.skip_errors:
619
+ raise RuntimeError(
620
+ f"{write_error_count} samples failed to write - check logs for details"
621
+ )
622
+
623
+
624
+ if __name__ == "__main__":
625
+ main()
runtime/omnivoice-python/omnivoice-source/omnivoice/scripts/extract_audio_tokens_add_noise.py ADDED
@@ -0,0 +1,819 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # Copyright 2026 Xiaomi Corp. (authors: Han Zhu)
3
+ #
4
+ # See ../../LICENSE for clarification regarding multiple authors
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
+ """
19
+ Extract audio tokens from audio data and pack them into WebDataset shards.
20
+
21
+ Extends ``extract_audio_tokens.py`` with optional noise and reverberation
22
+ augmentation on the prompt (reference) portion of the audio. Requires a
23
+ noise manifest and/or RIR manifest.
24
+
25
+ Supports two input modes:
26
+
27
+ 1. WebDataset manifest (data.lst):
28
+ python extract_audio_tokens_add_noise.py \\
29
+ --input_manifest data.lst \\
30
+ --noise_manifest noise.lst \\
31
+ --tar_output_pattern output/audios/shard-%06d.tar \\
32
+ --jsonl_output_pattern output/txts/shard-%06d.jsonl
33
+
34
+ 2. Raw JSONL (each line: {"id": "...", "audio_path": "...", "text": "...", ...}):
35
+ python extract_audio_tokens_add_noise.py \\
36
+ --input_jsonl data.jsonl \\
37
+ --noise_manifest noise.lst \\
38
+ --tar_output_pattern output/audios/shard-%06d.tar \\
39
+ --jsonl_output_pattern output/txts/shard-%06d.jsonl
40
+
41
+ Output structure:
42
+ output_dir/
43
+ ├── audios/ # WebDataset tar shards (.npy audio tokens + .json metadata)
44
+ │ ├── shard_000000.tar
45
+ │ └── ...
46
+ ├── txts/ # Per-shard JSONL metadata
47
+ │ ├── shard_000000.jsonl
48
+ │ └── ...
49
+ ├── data.lst # Manifest: <tar_path> <jsonl_path> <sample_count> <total_duration>
50
+ └── errors.jsonl # Failed samples with error details
51
+ """
52
+
53
+ import argparse
54
+ import io
55
+ import json
56
+ import logging
57
+ import math
58
+ import multiprocessing as mp
59
+ import os
60
+ import random
61
+ import warnings
62
+ from concurrent.futures import FIRST_COMPLETED, ProcessPoolExecutor, wait
63
+ from pathlib import Path
64
+ from typing import Any
65
+
66
+ import numpy as np
67
+ import torch
68
+ import torch.nn.functional as F
69
+ import webdataset as wds
70
+ from torch.utils.data import DataLoader, IterableDataset
71
+ from tqdm.auto import tqdm
72
+ from transformers import AutoFeatureExtractor, HiggsAudioV2TokenizerModel
73
+
74
+ from omnivoice.data.dataset import JsonlDatasetReader, WebDatasetReader
75
+ from omnivoice.utils.audio import load_audio_bytes
76
+ from omnivoice.utils.common import str2bool
77
+
78
+ warnings.filterwarnings(
79
+ "ignore", category=FutureWarning, module="torch.nn.utils.weight_norm"
80
+ )
81
+
82
+ HIGGS_INPUT_SAMPLE_RATE = 24_000
83
+
84
+ # Global variables: Store tokenizer and device for each worker process
85
+ worker_tokenizer = None
86
+ worker_feature_extractor = None
87
+ worker_noise_sampler = None
88
+ worker_rir_sampler = None
89
+
90
+
91
+ def build_parser() -> argparse.ArgumentParser:
92
+ parser = argparse.ArgumentParser(description=__doc__)
93
+ parser.add_argument(
94
+ "--input_manifest",
95
+ default=None,
96
+ help="Path to input dataset manifest (data.lst).",
97
+ )
98
+ parser.add_argument(
99
+ "--input_jsonl",
100
+ default=None,
101
+ help="Path to raw JSONL file (alternative to --input_manifest).",
102
+ )
103
+ parser.add_argument(
104
+ "--tar_output_pattern",
105
+ required=True,
106
+ help="Tar shard pattern passed to WebDataset",
107
+ )
108
+ parser.add_argument(
109
+ "--jsonl_output_pattern",
110
+ required=True,
111
+ help="Jsonl shard pattern passed to WebDataset",
112
+ )
113
+ parser.add_argument(
114
+ "--samples_per_shard",
115
+ type=int,
116
+ default=1000,
117
+ help="Maximum records per shard",
118
+ )
119
+ parser.add_argument(
120
+ "--min_num_shards",
121
+ type=int,
122
+ default=32,
123
+ help="Minimum number of output shards (use to ensure "
124
+ "shard count >= num_gpu * num_workers)",
125
+ )
126
+ parser.add_argument(
127
+ "--tokenizer_path",
128
+ type=str,
129
+ default="eustlb/higgs-audio-v2-tokenizer",
130
+ help="Path to audio tokenizer.",
131
+ )
132
+ parser.add_argument(
133
+ "--skip_errors", action="store_true", help="Skip items that fail to process"
134
+ )
135
+ parser.add_argument(
136
+ "--min_length",
137
+ type=float,
138
+ default=0.0,
139
+ help="Minimum audio duration in seconds (e.g. 2.0)",
140
+ )
141
+ parser.add_argument(
142
+ "--max_length",
143
+ type=float,
144
+ default=float("inf"),
145
+ help="Maximum audio duration in seconds (e.g. 15.0)",
146
+ )
147
+ parser.add_argument(
148
+ "--num_machines",
149
+ type=int,
150
+ default=1,
151
+ help="Total number of machines for distributed runs",
152
+ )
153
+ parser.add_argument(
154
+ "--machine_index",
155
+ type=int,
156
+ default=0,
157
+ help="Zero-based machine index when distributing across multiple "
158
+ "machines (e.g. 0, 1, ... num_machines-1)",
159
+ )
160
+ parser.add_argument(
161
+ "--nj_per_gpu",
162
+ type=int,
163
+ default=3,
164
+ help="Number of worker processes to spawn per GPU.",
165
+ )
166
+ parser.add_argument(
167
+ "--loader_workers",
168
+ type=int,
169
+ default=24,
170
+ help="Number of DataLoader workers for streaming IterableDataset.",
171
+ )
172
+ parser.add_argument(
173
+ "--shuffle",
174
+ type=str2bool,
175
+ default=True,
176
+ help="Shuffle data by default.",
177
+ )
178
+ parser.add_argument(
179
+ "--shuffle-seed",
180
+ type=int,
181
+ default=42,
182
+ help="Random seed for shuffle (default: 42).",
183
+ )
184
+ parser.add_argument(
185
+ "--noise_manifest",
186
+ default=None,
187
+ help="Path to noise manifest (list of tar files). Enables prompt noise augmentation.",
188
+ )
189
+ parser.add_argument(
190
+ "--rir_manifest",
191
+ default=None,
192
+ help="Path to RIR manifest (list of tar files). Enables prompt reverb augmentation.",
193
+ )
194
+ return parser
195
+
196
+
197
+ def count_lines(path):
198
+ with open(path, "rb") as f:
199
+ return sum(buf.count(b"\n") for buf in iter(lambda: f.read(1 << 20), b""))
200
+
201
+
202
+ def serialise_numpy(key: str, tokens: np.ndarray) -> dict:
203
+ buffer = io.BytesIO()
204
+ np.save(buffer, tokens)
205
+ return {"__key__": key, "npy": buffer.getvalue()}
206
+
207
+
208
+ def _load_aug_audio(data, sample_rate=24000):
209
+ """Simple audio loader for augmentation files."""
210
+ return torch.from_numpy(load_audio_bytes(data, sample_rate))
211
+
212
+
213
+ class SimpleWorkerSampler:
214
+ """A lightweight infinite sampler for noise/RIR within a worker process."""
215
+
216
+ def __init__(self, tar_paths, sample_rate=24000):
217
+ self.dataset = (
218
+ wds.WebDataset(
219
+ tar_paths, shardshuffle=True, nodesplitter=None, workersplitter=None
220
+ )
221
+ .decode()
222
+ .map(lambda s: self._decode(s, sample_rate))
223
+ .select(lambda x: x is not None)
224
+ .shuffle(100)
225
+ .repeat()
226
+ )
227
+ self.iterator = iter(self.dataset)
228
+
229
+ def _decode(self, sample, sample_rate):
230
+ for ext in ["wav", "flac", "mp3"]:
231
+ if ext in sample:
232
+ return _load_aug_audio(sample[ext], sample_rate)
233
+ return None
234
+
235
+ def sample_segment(self, target_len, allow_repeat=True):
236
+ """Get a random segment of noise matching the target length."""
237
+ try:
238
+ audio = next(self.iterator)
239
+ except StopIteration:
240
+ self.iterator = iter(self.dataset)
241
+ audio = next(self.iterator)
242
+
243
+ cur_len = audio.size(-1)
244
+ if cur_len < target_len and allow_repeat:
245
+ if cur_len > 0:
246
+ num_repeats = math.ceil(target_len / cur_len)
247
+ audio = audio.repeat(1, num_repeats)
248
+ else:
249
+ audio = F.pad(audio, (0, target_len), mode="constant")
250
+ cur_len = audio.size(-1)
251
+
252
+ if cur_len > target_len:
253
+ start = random.randint(0, cur_len - target_len)
254
+ audio = audio[..., start : start + target_len]
255
+
256
+ return audio
257
+
258
+
259
+ def _convolve1d(signal: torch.Tensor, kernel: torch.Tensor) -> torch.Tensor:
260
+ m = signal.size(-1)
261
+ n = kernel.size(-1)
262
+ padded_size = m + n - 1
263
+ f_signal = torch.fft.rfft(signal, n=padded_size)
264
+ f_kernel = torch.fft.rfft(kernel, n=padded_size)
265
+ f_result = f_signal * f_kernel
266
+ result = torch.fft.irfft(f_result, n=padded_size)
267
+ return result[:padded_size]
268
+
269
+
270
+ def _apply_rir(audio, rir, mix_ratio=0.5):
271
+ rir_scaling_factor = 0.5**15
272
+ N_in = audio.shape[-1]
273
+ rir_d = rir[0, :] * rir_scaling_factor
274
+ aug_d = _convolve1d(audio[0], rir_d)
275
+ shift_index = torch.argmax(torch.abs(rir_d))
276
+ end_index = shift_index + N_in
277
+ if end_index > aug_d.shape[0]:
278
+ augmented = F.pad(aug_d[shift_index:], (0, end_index - aug_d.shape[0]))
279
+ else:
280
+ augmented = aug_d[shift_index:end_index]
281
+ power_before = torch.sum(audio[0] ** 2)
282
+ power_after = torch.sum(augmented**2)
283
+ if power_after > 0:
284
+ augmented *= torch.sqrt(power_before / power_after)
285
+ mixed = (1 - mix_ratio) * audio[0] + mix_ratio * augmented
286
+ return mixed.unsqueeze(0)
287
+
288
+
289
+ def process_init(rank_queue, tokenizer_path, noise_manifest=None, rir_manifest=None):
290
+ """
291
+ Initialization function for each worker process.
292
+ Assigns a specific GPU to the process and loads the tokenizer.
293
+ """
294
+ global worker_tokenizer, worker_feature_extractor, worker_noise_sampler, worker_rir_sampler
295
+
296
+ # Configure worker process logging
297
+ formatter = (
298
+ "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d]"
299
+ " [Worker %(process)d] %(message)s"
300
+ )
301
+ logging.basicConfig(format=formatter, level=logging.INFO, force=True)
302
+
303
+ # Get assigned GPU rank
304
+ rank = rank_queue.get()
305
+ # Determine device
306
+ if rank != -1 and torch.cuda.is_available():
307
+ worker_device = torch.device(f"cuda:{rank}")
308
+ else:
309
+ worker_device = torch.device("cpu")
310
+
311
+ logging.debug(f"Worker process initialized with device: {worker_device}")
312
+ # Load tokenizer onto the specified device
313
+ worker_feature_extractor = AutoFeatureExtractor.from_pretrained(tokenizer_path)
314
+ worker_tokenizer = HiggsAudioV2TokenizerModel.from_pretrained(
315
+ tokenizer_path, device_map=worker_device
316
+ )
317
+ logging.debug(f"Tokenizer loaded successfully on device {worker_device}")
318
+
319
+ # Initialize augmentation samplers (optional)
320
+ if noise_manifest:
321
+ try:
322
+ with open(noise_manifest, "r") as f:
323
+ tars = [l.strip().split()[0] for l in f if l.strip()]
324
+ worker_noise_sampler = SimpleWorkerSampler(
325
+ tars, sample_rate=HIGGS_INPUT_SAMPLE_RATE
326
+ )
327
+ logging.debug("Noise sampler initialized.")
328
+ except Exception as e:
329
+ logging.warning(f"Failed to load noise manifest: {e}")
330
+
331
+ if rir_manifest:
332
+ try:
333
+ with open(rir_manifest, "r") as f:
334
+ tars = [l.strip().split()[0] for l in f if l.strip()]
335
+ worker_rir_sampler = SimpleWorkerSampler(
336
+ tars, sample_rate=HIGGS_INPUT_SAMPLE_RATE
337
+ )
338
+ logging.debug("RIR sampler initialized.")
339
+ except Exception as e:
340
+ logging.warning(f"Failed to load RIR manifest: {e}")
341
+
342
+
343
+ def _augment_prompt(audio_tensor: torch.Tensor) -> tuple[torch.Tensor, int]:
344
+ """Apply noise/reverb augmentation to the front portion of audio.
345
+
346
+ Returns the augmented audio and the sample index where clean audio starts.
347
+ """
348
+ # Pre-normalization
349
+ max_val = audio_tensor.abs().max() + 1e-7
350
+ audio_tensor = (audio_tensor / max_val) * 0.6
351
+
352
+ total_len = audio_tensor.size(-1)
353
+ ratio = random.uniform(0.1, 0.3)
354
+ split_idx = int(total_len * ratio)
355
+ front_part = audio_tensor[:, :split_idx].clone()
356
+
357
+ # Apply noise
358
+ if worker_noise_sampler is not None:
359
+ noise = worker_noise_sampler.sample_segment(split_idx)
360
+ snr_db = random.uniform(5, 15)
361
+ sig_rms = front_part.norm(p=2) / (split_idx**0.5)
362
+ noise_rms = noise.norm(p=2) / (split_idx**0.5)
363
+ if noise_rms > 1e-9:
364
+ snr = 10 ** (snr_db / 20)
365
+ scale = sig_rms / (snr * noise_rms + 1e-8)
366
+ front_part = front_part + noise * scale
367
+
368
+ # Apply RIR (30% probability)
369
+ if worker_rir_sampler is not None and random.random() < 0.3:
370
+ rir = worker_rir_sampler.sample_segment(split_idx, allow_repeat=False)
371
+ reverb_amt = random.uniform(0.3, 1.0)
372
+ try:
373
+ front_part = _apply_rir(front_part, rir, reverb_amt)
374
+ except Exception as e:
375
+ logging.warning(f"RIR failed: {e}")
376
+
377
+ # Merge back
378
+ if front_part.device != audio_tensor.device:
379
+ front_part = front_part.to(audio_tensor.device)
380
+ audio_tensor[:, :split_idx] = front_part
381
+
382
+ # Post-normalization
383
+ max_val = audio_tensor.abs().max() + 1e-7
384
+ audio_tensor = (audio_tensor / max_val) * 0.9
385
+
386
+ return audio_tensor, split_idx
387
+
388
+
389
+ def process_single_sample(sample: dict[str, Any]) -> dict[str, Any]:
390
+ """
391
+ Single-sample processing function executed in worker processes.
392
+ Skips invalid samples during streaming processing.
393
+ """
394
+ try:
395
+ audio_tensor = sample.get("audio", None) # shape (1, T)
396
+ if audio_tensor is None:
397
+ raise ValueError("Sample missing 'audio' field")
398
+
399
+ # Apply prompt augmentation if noise/rir samplers are available
400
+ enable_aug = worker_noise_sampler is not None or worker_rir_sampler is not None
401
+ clean_sample_idx = 0
402
+ if enable_aug:
403
+ audio_tensor, clean_sample_idx = _augment_prompt(audio_tensor)
404
+
405
+ with torch.inference_mode():
406
+ key = sample["label"]["id"]
407
+
408
+ inputs = worker_feature_extractor(
409
+ raw_audio=audio_tensor.squeeze(0).numpy(),
410
+ sampling_rate=HIGGS_INPUT_SAMPLE_RATE,
411
+ return_tensors="pt",
412
+ ).to(worker_tokenizer.device)
413
+ audio_tokens = worker_tokenizer.encode(
414
+ inputs["input_values"],
415
+ ).audio_codes.squeeze(0)
416
+
417
+ assert len(audio_tokens.shape) == 2
418
+ assert audio_tokens.size(0) == 8
419
+
420
+ num_tokens = audio_tokens.size(1)
421
+ metadata = sample["label"]
422
+ metadata["num_tokens"] = num_tokens
423
+
424
+ if enable_aug:
425
+ clean_token_idx = math.ceil(
426
+ clean_sample_idx / worker_tokenizer.config.hop_length
427
+ )
428
+ metadata["clean_start_token_idx"] = clean_token_idx
429
+
430
+ # Convert to numpy format for subsequent serialization (int16 to save space)
431
+ audio_tokens_np = audio_tokens.to(torch.int16).cpu().numpy()
432
+
433
+ return {
434
+ "status": "success",
435
+ "key": key,
436
+ "audio_tokens": audio_tokens_np,
437
+ "metadata": metadata,
438
+ "error_msg": None,
439
+ }
440
+ except Exception as e:
441
+ sample_id = sample.get("label", {}).get("id", "unknown")
442
+ logging.error(f"Failed to process sample {sample_id}: {e}")
443
+ return {
444
+ "status": "error",
445
+ "key": sample_id,
446
+ "audio_tokens": None,
447
+ "metadata": None,
448
+ "error_msg": str(e),
449
+ }
450
+
451
+
452
+ def _normalise_value(value: Any) -> Any:
453
+ """Convert tensors and NumPy scalars to serialisable Python objects."""
454
+ if isinstance(value, torch.Tensor):
455
+ if value.ndim == 0:
456
+ return value.item()
457
+ return value.cpu().tolist()
458
+ if isinstance(value, np.generic):
459
+ return value.item()
460
+ if isinstance(value, np.ndarray):
461
+ return value.tolist()
462
+ return value
463
+
464
+
465
+ def _encode_metadata(metadata: dict[str, Any]) -> bytes:
466
+ cleaned: dict[str, Any] = {}
467
+ for key, value in metadata.items():
468
+ if value is None:
469
+ continue
470
+ cleaned[key] = _normalise_value(value)
471
+ return json.dumps(cleaned, ensure_ascii=False).encode("utf-8")
472
+
473
+
474
+ class StreamingLengthFilteredDataset(IterableDataset):
475
+ def __init__(
476
+ self,
477
+ base_iterable,
478
+ min_len: float,
479
+ max_len: float,
480
+ sr: int,
481
+ ):
482
+ self.base_iterable = base_iterable
483
+ self.min_len = min_len
484
+ self.max_len = max_len
485
+ self.sr = sr
486
+ self.filtered_count = 0
487
+
488
+ def __iter__(self):
489
+ """Stream samples one by one and filter on the fly."""
490
+ for sample in self.base_iterable:
491
+ try:
492
+ duration = sample["audio"].size(-1) / self.sr
493
+ if self.min_len <= duration <= self.max_len:
494
+ yield sample
495
+ else:
496
+ self.filtered_count += 1
497
+ logging.warning(
498
+ f"Filtered sample (duration out of range): "
499
+ f"{sample['label']['id']} ({duration:.2f}s)"
500
+ )
501
+ except Exception as e:
502
+ logging.warning(f"Skipped invalid sample during streaming: {e}")
503
+ continue
504
+
505
+
506
+ def main() -> None:
507
+ formatter = "%(asctime)s %(levelname)s [%(filename)s:%(lineno)d] %(message)s"
508
+ logging.basicConfig(format=formatter, level=logging.INFO, force=True)
509
+ parser = build_parser()
510
+ args = parser.parse_args()
511
+ mp.set_start_method("spawn", force=True)
512
+
513
+ # Validate input arguments
514
+ assert bool(args.input_manifest) != bool(
515
+ args.input_jsonl
516
+ ), "Exactly one of --input_manifest or --input_jsonl must be provided."
517
+
518
+ if args.num_machines > 1:
519
+ assert (
520
+ 0 <= args.machine_index < args.num_machines
521
+ ), f"machine_index {args.machine_index} must be in [0, {args.num_machines})"
522
+
523
+ # Build base dataset and count total samples based on input mode
524
+ if args.input_jsonl:
525
+ logging.info(f"Input mode: raw JSONL ({args.input_jsonl})")
526
+ total_samples = count_lines(args.input_jsonl)
527
+ base_dataset = JsonlDatasetReader(
528
+ args.input_jsonl,
529
+ sample_rate=HIGGS_INPUT_SAMPLE_RATE,
530
+ shuffle=args.shuffle,
531
+ shuffle_seed=args.shuffle_seed,
532
+ )
533
+ loader_workers = args.loader_workers
534
+ else:
535
+ logging.info(f"Input mode: WebDataset manifest ({args.input_manifest})")
536
+ manifest_num_lines = count_lines(args.input_manifest)
537
+ loader_workers = min(args.loader_workers, manifest_num_lines)
538
+ total_samples = 0
539
+ manifests = []
540
+ with open(args.input_manifest, "r", encoding="utf-8") as f:
541
+ for line_id, line in tqdm(
542
+ enumerate(f),
543
+ total=manifest_num_lines,
544
+ desc="Calculating dataset length",
545
+ ):
546
+ items = line.strip().split(" ")
547
+ tar_path, jsonl_path, num_items, duration = (
548
+ items[0],
549
+ items[1],
550
+ int(items[2]),
551
+ float(items[3]),
552
+ )
553
+ assert os.path.exists(tar_path), f"File {tar_path} does not exist."
554
+ assert os.path.exists(jsonl_path), f"File {jsonl_path} does not exist."
555
+ assert jsonl_path.endswith(
556
+ ".jsonl"
557
+ ), f"File {jsonl_path} is not a .jsonl file."
558
+ if (
559
+ args.num_machines > 1
560
+ and line_id % args.num_machines != args.machine_index
561
+ ):
562
+ continue
563
+ total_samples += num_items
564
+ manifests.append((tar_path, jsonl_path, num_items, duration))
565
+ logging.info(
566
+ f"Total shards: {manifest_num_lines}, "
567
+ f"Shards for current index: {len(manifests)}"
568
+ )
569
+ base_dataset = WebDatasetReader(
570
+ manifests=manifests,
571
+ sample_rate=HIGGS_INPUT_SAMPLE_RATE,
572
+ evaluation=True,
573
+ )
574
+
575
+ # Apply length filter and create DataLoader
576
+ filtered_dataset = StreamingLengthFilteredDataset(
577
+ base_iterable=base_dataset,
578
+ min_len=args.min_length,
579
+ max_len=args.max_length,
580
+ sr=HIGGS_INPUT_SAMPLE_RATE,
581
+ )
582
+ dataloader = DataLoader(
583
+ dataset=filtered_dataset,
584
+ batch_size=None,
585
+ num_workers=loader_workers,
586
+ persistent_workers=loader_workers > 0,
587
+ pin_memory=False,
588
+ )
589
+
590
+ # Adjust samples_per_shard if min_num_shards would be violated
591
+ samples_per_shard = args.samples_per_shard
592
+ if total_samples > 0:
593
+ estimated_shards = max(
594
+ 1, (total_samples + samples_per_shard - 1) // samples_per_shard
595
+ )
596
+ if estimated_shards < args.min_num_shards:
597
+ samples_per_shard = max(1, total_samples // args.min_num_shards)
598
+ logging.info(
599
+ f"Adjusted samples_per_shard from {args.samples_per_shard} to "
600
+ f"{samples_per_shard} to meet min_num_shards={args.min_num_shards} "
601
+ f"(total_samples={total_samples})"
602
+ )
603
+
604
+ # Configure multi-GPU multi-process setup
605
+ num_devices = torch.cuda.device_count()
606
+ if num_devices == 0:
607
+ logging.warning("No GPUs detected - using CPU for processing")
608
+ num_processes = args.nj_per_gpu
609
+ else:
610
+ num_processes = num_devices * args.nj_per_gpu
611
+ logging.info(
612
+ f"GPU count: {num_devices}, Processes per GPU: {args.nj_per_gpu}, "
613
+ f"Total processes: {num_processes}"
614
+ )
615
+ if args.noise_manifest or args.rir_manifest:
616
+ logging.info(
617
+ f"Prompt augmentation enabled - "
618
+ f"noise: {args.noise_manifest or 'off'}, rir: {args.rir_manifest or 'off'}"
619
+ )
620
+
621
+ # Shared GPU rank queue for process assignment
622
+ manager = mp.Manager()
623
+ rank_queue = manager.Queue()
624
+ for rank in list(range(num_devices)) * args.nj_per_gpu:
625
+ rank_queue.put(rank)
626
+ if num_devices == 0:
627
+ for _ in range(num_processes):
628
+ rank_queue.put(-1)
629
+
630
+ # Prepare output paths
631
+ tar_output_pattern = str(Path(args.tar_output_pattern).expanduser())
632
+ jsonl_output_pattern = str(Path(args.jsonl_output_pattern).expanduser())
633
+ Path(tar_output_pattern).parent.mkdir(parents=True, exist_ok=True)
634
+ Path(jsonl_output_pattern).parent.mkdir(parents=True, exist_ok=True)
635
+
636
+ # Determine output directory from tar_output_pattern
637
+ output_dir = Path(tar_output_pattern).parent.parent
638
+ error_log_path = str(output_dir / "errors.jsonl")
639
+ manifest_path = str(output_dir / "data.lst")
640
+
641
+ # Setup error logger (writes to errors.jsonl)
642
+ error_logger = logging.getLogger("error_log")
643
+ error_logger.setLevel(logging.ERROR)
644
+ error_logger.handlers.clear()
645
+ error_fh = logging.FileHandler(error_log_path, mode="w", encoding="utf-8")
646
+ error_fh.setFormatter(logging.Formatter("%(message)s"))
647
+ error_logger.addHandler(error_fh)
648
+
649
+ # Progress and error tracking
650
+ processed_count = 0
651
+ error_count = 0
652
+ write_error_count = 0
653
+ failed_ids = []
654
+ shard_idx = 0
655
+ shard_sample_count = 0
656
+ shard_duration = 0.0
657
+ shard_manifest = {} # shard_idx -> (tar_path, jsonl_path, count, duration)
658
+
659
+ tar_writer = None
660
+ jsonl_file = None
661
+
662
+ def open_new_shard():
663
+ nonlocal tar_writer, jsonl_file, shard_idx, shard_sample_count, shard_duration
664
+ if tar_writer is not None:
665
+ tar_writer.close()
666
+ if jsonl_file is not None:
667
+ jsonl_file.close()
668
+ # Record manifest for the previous shard
669
+ if shard_idx > 0 and shard_sample_count > 0:
670
+ prev_idx = shard_idx - 1
671
+ shard_manifest[prev_idx] = (
672
+ os.path.abspath(tar_output_pattern % prev_idx),
673
+ os.path.abspath(jsonl_output_pattern % prev_idx),
674
+ shard_sample_count,
675
+ shard_duration,
676
+ )
677
+ tar_fname = tar_output_pattern % shard_idx
678
+ jsonl_fname = jsonl_output_pattern % shard_idx
679
+ tar_writer = wds.TarWriter(tar_fname)
680
+ jsonl_file = open(jsonl_fname, "w", encoding="utf-8")
681
+ shard_idx += 1
682
+ shard_sample_count = 0
683
+ shard_duration = 0.0
684
+
685
+ def write_sample(key, audio_tokens_np, metadata):
686
+ nonlocal shard_sample_count, write_error_count, shard_duration
687
+ assert tar_writer is not None and jsonl_file is not None
688
+ try:
689
+ token_record = serialise_numpy(key, audio_tokens_np)
690
+ json_record = _encode_metadata(metadata)
691
+ tar_writer.write(token_record)
692
+ jsonl_file.write(json_record.decode("utf-8") + "\n")
693
+ shard_sample_count += 1
694
+ shard_duration += metadata.get("audio_duration", 0.0)
695
+ except Exception as exc:
696
+ write_error_count += 1
697
+ failed_ids.append(key)
698
+ error_logger.error(
699
+ json.dumps({"id": key, "reason": str(exc)}, ensure_ascii=False)
700
+ )
701
+ logging.error(f"Write failed for sample {key}: {exc}")
702
+
703
+ def handle_result(result):
704
+ nonlocal processed_count, error_count
705
+ if result["status"] == "success":
706
+ # Rotate shard if needed
707
+ if tar_writer is None or shard_sample_count >= samples_per_shard:
708
+ open_new_shard()
709
+ write_sample(result["key"], result["audio_tokens"], result["metadata"])
710
+ processed_count += 1
711
+ else:
712
+ error_count += 1
713
+ failed_ids.append(result["key"])
714
+ error_logger.error(
715
+ json.dumps(
716
+ {"id": result["key"], "reason": result["error_msg"]},
717
+ ensure_ascii=False,
718
+ )
719
+ )
720
+ if not args.skip_errors:
721
+ raise RuntimeError(
722
+ f"Sample {result['key']} processing failed due "
723
+ f"to {result['error_msg']} - terminating"
724
+ )
725
+ logging.warning(
726
+ f"Skipping failed sample {result['key']}: {result['error_msg']}"
727
+ )
728
+
729
+ main_progress = tqdm(total=total_samples, desc="Extracting Audio Tokens")
730
+
731
+ try:
732
+ with ProcessPoolExecutor(
733
+ max_workers=num_processes,
734
+ initializer=process_init,
735
+ initargs=(
736
+ rank_queue,
737
+ args.tokenizer_path,
738
+ args.noise_manifest,
739
+ args.rir_manifest,
740
+ ),
741
+ ) as executor:
742
+ logging.info(f"Submitting tasks... ({num_processes} workers)")
743
+ futures = set()
744
+ max_pending = num_processes * 10
745
+
746
+ def drain_completed():
747
+ """Wait for at least one future to complete, process all done."""
748
+ nonlocal futures
749
+ done, _ = wait(futures, return_when=FIRST_COMPLETED)
750
+ for f in done:
751
+ futures.discard(f)
752
+ result = f.result()
753
+ main_progress.update(1)
754
+ handle_result(result)
755
+ main_progress.set_postfix(
756
+ Samples=processed_count,
757
+ Errors=error_count,
758
+ )
759
+
760
+ # Stream samples from DataLoader
761
+ for sample in dataloader:
762
+ if len(futures) >= max_pending:
763
+ drain_completed()
764
+
765
+ future = executor.submit(process_single_sample, sample)
766
+ futures.add(future)
767
+
768
+ # Process remaining futures
769
+ logging.info("Processing remaining pending samples...")
770
+ while futures:
771
+ drain_completed()
772
+
773
+ except Exception:
774
+ logging.error("Critical error during processing", exc_info=True)
775
+ raise
776
+ finally:
777
+ main_progress.close()
778
+ if tar_writer is not None:
779
+ tar_writer.close()
780
+ if jsonl_file is not None:
781
+ jsonl_file.close()
782
+ # Record the last shard in the manifest
783
+ if shard_idx > 0 and shard_sample_count > 0:
784
+ last_idx = shard_idx - 1
785
+ shard_manifest[last_idx] = (
786
+ os.path.abspath(tar_output_pattern % last_idx),
787
+ os.path.abspath(jsonl_output_pattern % last_idx),
788
+ shard_sample_count,
789
+ shard_duration,
790
+ )
791
+
792
+ # Write manifest file (data.lst)
793
+ with open(manifest_path, "w", encoding="utf-8") as mf:
794
+ for idx in sorted(shard_manifest.keys()):
795
+ tar_path, jsonl_path, count, duration = shard_manifest[idx]
796
+ mf.write(f"{tar_path} {jsonl_path} {count} {duration:.3f}\n")
797
+
798
+ # Output final statistics
799
+ total_failed = error_count + write_error_count
800
+ filtered_and_skipped = total_samples - processed_count - total_failed
801
+ logging.info(
802
+ f"Processing Complete - Successful: {processed_count}, Failed: {total_failed}, "
803
+ f"Filtered/Skipped: {filtered_and_skipped}, Shards written: {shard_idx}"
804
+ )
805
+ logging.info(f"Manifest written to: {manifest_path} ({len(shard_manifest)} shards)")
806
+ if total_failed > 0:
807
+ logging.info(f"Error details: {error_log_path}")
808
+ if failed_ids and args.skip_errors:
809
+ logging.warning(
810
+ f"Failed sample IDs (count: {len(failed_ids)}): {failed_ids[:100]}..."
811
+ )
812
+ if write_error_count > 0 and not args.skip_errors:
813
+ raise RuntimeError(
814
+ f"{write_error_count} samples failed to write - check logs for details"
815
+ )
816
+
817
+
818
+ if __name__ == "__main__":
819
+ main()
runtime/omnivoice-python/omnivoice-source/uv.lock ADDED
The diff for this file is too large to render. See raw diff