Nikatun commited on
Commit
bbb4b25
·
verified ·
1 Parent(s): 403918c

Silencio v1.0: zero-shot TTS and voice conversion in one model

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.gitattributes CHANGED
@@ -33,3 +33,32 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ ref_1320.wav filter=lfs diff=lfs merge=lfs -text
37
+ ref_2094.wav filter=lfs diff=lfs merge=lfs -text
38
+ ref_260.wav filter=lfs diff=lfs merge=lfs -text
39
+ ref_3575.wav filter=lfs diff=lfs merge=lfs -text
40
+ ref_5639.wav filter=lfs diff=lfs merge=lfs -text
41
+ ref_6930.wav filter=lfs diff=lfs merge=lfs -text
42
+ ref_7127.wav filter=lfs diff=lfs merge=lfs -text
43
+ ref_7729.wav filter=lfs diff=lfs merge=lfs -text
44
+ samples/1_librispeech_1320.wav filter=lfs diff=lfs merge=lfs -text
45
+ samples/1_librispeech_2094.wav filter=lfs diff=lfs merge=lfs -text
46
+ samples/1_librispeech_260.wav filter=lfs diff=lfs merge=lfs -text
47
+ samples/1_paradee.wav filter=lfs diff=lfs merge=lfs -text
48
+ samples/2_librispeech_1320.wav filter=lfs diff=lfs merge=lfs -text
49
+ samples/2_librispeech_2094.wav filter=lfs diff=lfs merge=lfs -text
50
+ samples/2_librispeech_260.wav filter=lfs diff=lfs merge=lfs -text
51
+ samples/2_paradee.wav filter=lfs diff=lfs merge=lfs -text
52
+ samples/3_librispeech_1320.wav filter=lfs diff=lfs merge=lfs -text
53
+ samples/3_librispeech_2094.wav filter=lfs diff=lfs merge=lfs -text
54
+ samples/3_librispeech_260.wav filter=lfs diff=lfs merge=lfs -text
55
+ samples/3_paradee.wav filter=lfs diff=lfs merge=lfs -text
56
+ samples/4_librispeech_1320.wav filter=lfs diff=lfs merge=lfs -text
57
+ samples/4_librispeech_2094.wav filter=lfs diff=lfs merge=lfs -text
58
+ samples/4_librispeech_260.wav filter=lfs diff=lfs merge=lfs -text
59
+ samples/4_paradee.wav filter=lfs diff=lfs merge=lfs -text
60
+ samples/5_librispeech_1320.wav filter=lfs diff=lfs merge=lfs -text
61
+ samples/5_librispeech_2094.wav filter=lfs diff=lfs merge=lfs -text
62
+ samples/5_librispeech_260.wav filter=lfs diff=lfs merge=lfs -text
63
+ samples/5_paradee.wav filter=lfs diff=lfs merge=lfs -text
64
+ samples/vc_1320_to_2094.wav filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ pipeline_tag: text-to-speech
6
+ library_name: pytorch
7
+ base_model:
8
+ - sahilmahendrakar/Paradee-8M-v1.1
9
+ - ASLP-lab/MeanVC2
10
+ tags:
11
+ - text-to-speech
12
+ - tts
13
+ - zero-shot
14
+ - voice-cloning
15
+ - voice-conversion
16
+ - streaming
17
+ - flow-matching
18
+ - on-device
19
+ ---
20
+
21
+ # Silencio
22
+
23
+ **Small zero-shot text-to-speech that speaks in any voice.**
24
+
25
+ Silencio turns English text into 16 kHz speech in a voice it has never heard before. A few seconds
26
+ of reference audio are enough. All 33.6M parameters fit in one 134 MB `model.safetensors`. It renders
27
+ audio in 120 ms chunks and runs about 9x faster than real time on one CPU thread of an Apple M4.
28
+
29
+ The same weights also do voice conversion: give it speech instead of text and it says the same words in
30
+ another voice.
31
+
32
+ Silencio is built on two open models. The text front-end comes from
33
+ [Paradee-8M v1.1](https://huggingface.co/sahilmahendrakar/Paradee-8M-v1.1) by Sahil Mahendrakar,
34
+ itself distilled from [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) by hexgrad. The
35
+ voice renderer and vocoder come from [MeanVC2](https://huggingface.co/ASLP-lab/MeanVC2) by ASLP-lab.
36
+ A new 3.4M-parameter content adapter connects them, so text goes straight into the renderer with no
37
+ intermediate audio.
38
+
39
+ ## Model details
40
+
41
+ - **Model type:** zero-shot text-to-speech. A text encoder predicts phoneme durations, pitch and
42
+ phonetic features. A ConvNeXt adapter turns them into content tokens at 25 Hz. A mean-flow DiT
43
+ renders a mel spectrogram from those tokens and a 256-d voice embedding in 3 steps. Vocos then
44
+ makes the waveform.
45
+ - **Parameters:** 33.6M (text encoder 4.2M, adapter 3.4M, renderer 17.7M, vocoder 8.3M)
46
+ - **Weights file:** `model.safetensors`, 134 MB (fp32)
47
+ - **Language:** English (American pronunciation)
48
+ - **Voices:** zero-shot, from a 5–15 s reference clip. 8 ready-made voices are included.
49
+ - **Output:** mono, 16 kHz
50
+ - **License:** [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
51
+
52
+ ### How it fits together
53
+
54
+ ```
55
+ text ─► misaki G2P ─► text encoder ─► content adapter ─► DiT renderer ─► Vocos ─► 16 kHz audio
56
+ (phonemes) durations, F0, content tokens mean flow, ▲
57
+ features @40 Hz @25 Hz 3 steps │
58
+ ▲ │
59
+ voice embedding ──┘ (256-d) │
60
+ speech ─► Fast-U2++ streaming ASR encoder ─► content tokens ─┘ (voice conversion)
61
+ ```
62
+
63
+ The text encoder predicts how long each phoneme lasts and how the pitch moves. The renderer only
64
+ decides how it sounds in the chosen voice. So timing and intonation come from the text, and timbre
65
+ comes from the voice embedding.
66
+
67
+ ## Quickstart
68
+
69
+ ```sh
70
+ hf download BlueRoseLabs/Silencio --local-dir Silencio
71
+ cd Silencio
72
+ pip install -r requirements.txt
73
+ pip install --no-deps --ignore-requires-python misaki==0.9.4 # G2P; declares python<3.13 but runs on 3.13
74
+ ```
75
+
76
+ ### Python
77
+
78
+ ```python
79
+ from silencio import SilencioTTS
80
+
81
+ tts = SilencioTTS.from_pretrained("BlueRoseLabs/Silencio") # or the local folder
82
+ tts.save("speech.wav", "Hello from Silencio.", voice="librispeech_1320")
83
+
84
+ # Streaming: one chunk per sentence, float32 at 16 kHz
85
+ for pcm in tts.stream("Speech can start playing right away. The next sentence follows.", voice="librispeech_2094"):
86
+ ...
87
+ ```
88
+
89
+ `generate` returns a numpy array, and `speed` (default 1.0) changes the speaking rate. On first use misaki downloads spaCy's small English model (about 13 MB).
90
+
91
+ ### Any voice from a short clip
92
+
93
+ ```python
94
+ import soundfile as sf
95
+ from silencio.enroll import enroll # pip install s3prl
96
+
97
+ voice = enroll(*sf.read("reference.wav")) # 256-d embedding; save it with torch.save and reuse it
98
+ tts.save("cloned.wav", "This is my voice, read by Silencio.", voice=voice)
99
+ ```
100
+
101
+ The voice encoder is WavLM-Large with ECAPA-TDNN, the same one MeanVC2 was trained with. It is large
102
+ (about 1.3 GB) and only needed to add new voices, so it is not part of `model.safetensors`. It is
103
+ downloaded on first use. A saved embedding needs nothing extra.
104
+
105
+ ### Voice conversion
106
+
107
+ ```python
108
+ wav, sr = sf.read("source.wav")
109
+ sf.write("converted.wav", tts.convert(wav, sr, voice="librispeech_2094"), 16000)
110
+ ```
111
+
112
+ For speech input, the content tokens come from MeanVC2's streaming Fast-U2++ encoder. It is
113
+ downloaded from [ASLP-lab/MeanVC2](https://huggingface.co/ASLP-lab/MeanVC2) on first use and runs on CPU.
114
+
115
+ ## Voices
116
+
117
+ `voices.safetensors` holds 8 voices enrolled from LibriSpeech test-clean readers. A reader's voice
118
+ is called `librispeech_<speaker id>`. The reference clip used for each one is included as `ref_<id>.wav`.
119
+
120
+ | Voice | Reference clip |
121
+ |---|---|
122
+ | `librispeech_1320` (default) | [ref_1320.wav](ref_1320.wav) |
123
+ | `librispeech_2094` | [ref_2094.wav](ref_2094.wav) |
124
+ | `librispeech_260` | [ref_260.wav](ref_260.wav) |
125
+ | `librispeech_3575` | [ref_3575.wav](ref_3575.wav) |
126
+ | `librispeech_5639` | [ref_5639.wav](ref_5639.wav) |
127
+ | `librispeech_6930` | [ref_6930.wav](ref_6930.wav) |
128
+ | `librispeech_7127` | [ref_7127.wav](ref_7127.wav) |
129
+ | `librispeech_7729` | [ref_7729.wav](ref_7729.wav) |
130
+
131
+ ## Samples
132
+
133
+ Five held-out sentences, read by Silencio in three voices. The last column is
134
+ Paradee-8M v1.1, whose text encoder Silencio uses, in its single voice, for comparison. The sentences are
135
+ in [`samples/sentences.txt`](samples/sentences.txt).
136
+
137
+ | # | `librispeech_1320` | `librispeech_2094` | `librispeech_260` | Paradee-8M v1.1 |
138
+ |---|---|---|---|---|
139
+ | 1 | [listen](samples/1_librispeech_1320.wav) | [listen](samples/1_librispeech_2094.wav) | [listen](samples/1_librispeech_260.wav) | [listen](samples/1_paradee.wav) |
140
+ | 2 | [listen](samples/2_librispeech_1320.wav) | [listen](samples/2_librispeech_2094.wav) | [listen](samples/2_librispeech_260.wav) | [listen](samples/2_paradee.wav) |
141
+ | 3 | [listen](samples/3_librispeech_1320.wav) | [listen](samples/3_librispeech_2094.wav) | [listen](samples/3_librispeech_260.wav) | [listen](samples/3_paradee.wav) |
142
+ | 4 | [listen](samples/4_librispeech_1320.wav) | [listen](samples/4_librispeech_2094.wav) | [listen](samples/4_librispeech_260.wav) | [listen](samples/4_paradee.wav) |
143
+ | 5 | [listen](samples/5_librispeech_1320.wav) | [listen](samples/5_librispeech_2094.wav) | [listen](samples/5_librispeech_260.wav) | [listen](samples/5_paradee.wav) |
144
+
145
+ Voice conversion: [source](ref_1320.wav) (LibriSpeech 1320) → [converted to `librispeech_2094`](samples/vc_1320_to_2094.wav). Whisper transcribes both identically, and the speaker similarity to the target voice is 0.83.
146
+
147
+ ## Results
148
+
149
+ All numbers are on Paradee's 200 held-out WikiText sentences, scored the way the Paradee card scores
150
+ them. Silencio speaks them in its 8 voices in turn.
151
+
152
+ | Model (voice) | Params | Weights | UTMOS | WER | Speaker similarity | Speed, T4 | Speed, 1 CPU thread |
153
+ |---|---:|---:|---:|---:|---:|---:|---:|
154
+ | **Silencio (8 zero-shot voices)** | **33.6M** | **134 MB** | 4.15 | 7.0% | 0.72 | 3.7x | 8.8x (M4) · 1.6x (Colab) |
155
+ | Paradee-8M v1.1 (af_heart, single voice), PyTorch | 7.9M | 8.5 MB (int8) | 4.41 | 6.3% | – | – | – |
156
+
157
+ - **UTMOS** is a neural network trained on human ratings. It predicts how natural a clip sounds,
158
+ on a scale from 1 to 5 (SpeechMOS `utmos22_strong`).
159
+ - **WER** is the share of words that Whisper (base) transcribes wrongly.
160
+ - **Speaker similarity** is the cosine similarity between the voice of the output and the voice of the
161
+ reference clip, measured with MeanVC2's WavLM/ECAPA encoder. 1.0 is the same embedding.
162
+ - **Speed** is how many times faster than real time the model runs, counting text to waveform, in PyTorch
163
+ fp32. CPU speed is measured with one thread, on an Apple M4 (one two-sentence paragraph) and on the Colab host (10 held-out sentences).
164
+
165
+ Paradee was scored by the same script, in PyTorch, which is why its WER differs a little from the 5.9% on its own card.
166
+
167
+ Paradee speaks one studio voice at 24 kHz. Silencio speaks voices taken from audiobook recordings, and
168
+ it does so at 16 kHz. Its UTMOS therefore also reflects the recording quality of the reference voices,
169
+ not only the model.
170
+
171
+ ## How it was made
172
+
173
+ Only the adapter was trained. Everything else is reused frozen from the two base models.
174
+
175
+ 1. **Content space.** MeanVC2's renderer takes content tokens from a streaming ASR encoder (256-d at
176
+ 25 Hz) and a speaker embedding. Content tokens carry what is said and how, but not who says it.
177
+ 2. **Targets.** Paradee read 6,000 WikiText sentences at speeds 0.9, 1.0 and 1.1 (about 6 hours). For
178
+ each one, Paradee's internal text-side features were kept, and MeanVC2's ASR encoder was run on
179
+ Paradee's audio to get the content tokens that audio would produce.
180
+ 3. **Adapter.** A ConvNeXt network (4 blocks at 40 Hz, resampling to 25 Hz, then 4 more blocks) learns to
181
+ map the text features, pitch and loudness to those content tokens. It uses an L1 plus cosine loss.
182
+ Training took 15,000 steps at batch 32, about 22 minutes on one T4.
183
+ 4. **One model.** The text encoder, adapter, renderer (EMA weights) and vocoder were put into a single
184
+ `model.safetensors`. Weight norm was folded into the weights, and the Vocos TorchScript was converted
185
+ to plain PyTorch. The output of the merged model matches the original chained pipeline to within
186
+ 7e-4.
187
+
188
+ In speech-to-speech mode the original ASR encoder takes the adapter's place, so voice conversion works
189
+ exactly as in MeanVC2.
190
+
191
+ ## Files
192
+
193
+ | File | What it is |
194
+ |---|---|
195
+ | `model.safetensors` | The whole model: text encoder, content adapter, DiT renderer, Vocos vocoder (134 MB, fp32) |
196
+ | `config.json` | Phoneme vocabulary, module sizes, chunking and the default voice |
197
+ | `voices.safetensors` | 8 ready-made voice embeddings (256-d each) |
198
+ | `ref_*.wav` | The LibriSpeech clips those voices were enrolled from |
199
+ | `silencio/` | The inference code. Upstream modules are vendored under their Apache-2.0 licenses, with only import paths changed |
200
+ | `requirements.txt` | Runtime dependencies |
201
+ | `samples/` | The audio above |
202
+ | `eval.json` | The numbers in Results |
203
+
204
+ ## Limitations
205
+
206
+ - English only, with American pronunciation. Numbers, abbreviations and rare words are pronounced only
207
+ as well as misaki handles them.
208
+ - Output is 16 kHz, which is the rate of MeanVC2's vocoder. That is enough for voice assistants
209
+ and audiobooks, but not for full-band audio.
210
+ - Prosody comes from Paradee's single training voice, so every voice speaks with similar timing and
211
+ intonation. Speaker similarity is lower than in voice conversion, where timing comes from the
212
+ source speaker.
213
+ - Long text is split into sentences that are spoken one after another, so prosody does not carry
214
+ across sentences.
215
+
216
+ ## Responsible use
217
+
218
+ Do not use Silencio to impersonate a real person, to deceive listeners about who is speaking, or
219
+ to clone or convert a voice without the speaker's consent. Tell listeners when speech is synthetic where
220
+ that matters to them.
221
+
222
+ ## License
223
+
224
+ The weights and code in this repository are released under
225
+ [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0), the license of every model they are built
226
+ from:
227
+
228
+ - [Paradee-8M v1.1](https://huggingface.co/sahilmahendrakar/Paradee-8M-v1.1) by Sahil Mahendrakar
229
+ (Apache 2.0): text encoder.
230
+ - [Kokoro-82M](https://huggingface.co/hexgrad/Kokoro-82M) by hexgrad (Apache 2.0): the architecture and
231
+ teacher behind Paradee, and the [misaki](https://github.com/hexgrad/misaki) G2P.
232
+ - [MeanVC2](https://huggingface.co/ASLP-lab/MeanVC2) by ASLP-lab (Apache 2.0): DiT renderer, Vocos
233
+ vocoder, Fast-U2++ encoder and the speaker-encoder code.
234
+ - [Vocos](https://github.com/gemelo-ai/vocos) by Gemelo AI (MIT): vocoder architecture.
235
+
236
+ The voice encoder used by `enroll` (WavLM-Large from
237
+ [UniSpeech](https://github.com/microsoft/UniSpeech), with its ECAPA-TDNN speaker-verification
238
+ fine-tune) is not redistributed here. It is downloaded at runtime under its own license. The bundled
239
+ voices come from [LibriSpeech](https://www.openslr.org/12) (CC BY 4.0) readers, whose recordings are from
240
+ the public-domain LibriVox project.
241
+
242
+ ## Citation
243
+
244
+ ```bibtex
245
+ @misc{silencio2026,
246
+ title = {Silencio: Zero-Shot Text-to-Speech by Connecting a Distilled Text Encoder to a Streaming Voice Renderer},
247
+ author = {{BlueRose Labs}},
248
+ year = {2026},
249
+ url = {https://huggingface.co/BlueRoseLabs/Silencio}
250
+ }
251
+
252
+ @misc{mahendrakar2026paradee,
253
+ title = {Paradee: Distilling Kokoro-82M into an 8M-Parameter Single-Voice Text-to-Speech Model},
254
+ author = {Mahendrakar, Sahil},
255
+ year = {2026},
256
+ url = {https://huggingface.co/sahilmahendrakar/Paradee-8M-v1.0}
257
+ }
258
+ ```
259
+
260
+ MeanVC2: [github.com/ASLP-lab/MeanVC2](https://github.com/ASLP-lab/MeanVC2).
config.json ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "silencio-tts",
3
+ "version": "1.0",
4
+ "sample_rate": 16000,
5
+ "vocab": {
6
+ ";": 1,
7
+ ":": 2,
8
+ ",": 3,
9
+ ".": 4,
10
+ "!": 5,
11
+ "?": 6,
12
+ "—": 9,
13
+ "…": 10,
14
+ "\"": 11,
15
+ "(": 12,
16
+ ")": 13,
17
+ "“": 14,
18
+ "”": 15,
19
+ " ": 16,
20
+ "̃": 17,
21
+ "ʣ": 18,
22
+ "ʥ": 19,
23
+ "ʦ": 20,
24
+ "ʨ": 21,
25
+ "ᵝ": 22,
26
+ "ꭧ": 23,
27
+ "A": 24,
28
+ "I": 25,
29
+ "O": 31,
30
+ "Q": 33,
31
+ "S": 35,
32
+ "T": 36,
33
+ "W": 39,
34
+ "Y": 41,
35
+ "ᵊ": 42,
36
+ "a": 43,
37
+ "b": 44,
38
+ "c": 45,
39
+ "d": 46,
40
+ "e": 47,
41
+ "f": 48,
42
+ "h": 50,
43
+ "i": 51,
44
+ "j": 52,
45
+ "k": 53,
46
+ "l": 54,
47
+ "m": 55,
48
+ "n": 56,
49
+ "o": 57,
50
+ "p": 58,
51
+ "q": 59,
52
+ "r": 60,
53
+ "s": 61,
54
+ "t": 62,
55
+ "u": 63,
56
+ "v": 64,
57
+ "w": 65,
58
+ "x": 66,
59
+ "y": 67,
60
+ "z": 68,
61
+ "ɑ": 69,
62
+ "ɐ": 70,
63
+ "ɒ": 71,
64
+ "æ": 72,
65
+ "β": 75,
66
+ "ɔ": 76,
67
+ "ɕ": 77,
68
+ "ç": 78,
69
+ "ɖ": 80,
70
+ "ð": 81,
71
+ "ʤ": 82,
72
+ "ə": 83,
73
+ "ɚ": 85,
74
+ "ɛ": 86,
75
+ "ɜ": 87,
76
+ "ɟ": 90,
77
+ "ɡ": 92,
78
+ "ɥ": 99,
79
+ "ɨ": 101,
80
+ "ɪ": 102,
81
+ "ʝ": 103,
82
+ "ɯ": 110,
83
+ "ɰ": 111,
84
+ "ŋ": 112,
85
+ "ɳ": 113,
86
+ "ɲ": 114,
87
+ "ɴ": 115,
88
+ "ø": 116,
89
+ "ɸ": 118,
90
+ "θ": 119,
91
+ "œ": 120,
92
+ "ɹ": 123,
93
+ "ɾ": 125,
94
+ "ɻ": 126,
95
+ "ʁ": 128,
96
+ "ɽ": 129,
97
+ "ʂ": 130,
98
+ "ʃ": 131,
99
+ "ʈ": 132,
100
+ "ʧ": 133,
101
+ "ʊ": 135,
102
+ "ʋ": 136,
103
+ "ʌ": 138,
104
+ "ɣ": 139,
105
+ "ɤ": 140,
106
+ "χ": 142,
107
+ "ʎ": 143,
108
+ "ʒ": 147,
109
+ "ʔ": 148,
110
+ "ˈ": 156,
111
+ "ˌ": 157,
112
+ "ː": 158,
113
+ "ʰ": 162,
114
+ "ʲ": 164,
115
+ "↓": 169,
116
+ "→": 171,
117
+ "↗": 172,
118
+ "↘": 173,
119
+ "ᵻ": 177
120
+ },
121
+ "adapter": {
122
+ "dim": 256
123
+ },
124
+ "dit": {
125
+ "dim": 512,
126
+ "depth": 4,
127
+ "heads": 2,
128
+ "ff_mult": 2,
129
+ "bn_dim": 256,
130
+ "conv_layers": 4,
131
+ "chunk_size": 12,
132
+ "block_size": 4,
133
+ "dropout": 0.0,
134
+ "qk_norm": "rms_norm"
135
+ },
136
+ "vocos": {
137
+ "input_channels": 80,
138
+ "dim": 320,
139
+ "intermediate_dim": 1536,
140
+ "num_layers": 8,
141
+ "n_fft": 640,
142
+ "hop_length": 160,
143
+ "padding": "center"
144
+ },
145
+ "chunk_size": 12,
146
+ "block_size": 4,
147
+ "steps": 3,
148
+ "default_voice": "librispeech_6930"
149
+ }
eval.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "n": 200,
3
+ "voices": [
4
+ "librispeech_1320",
5
+ "librispeech_2094",
6
+ "librispeech_260",
7
+ "librispeech_3575",
8
+ "librispeech_5639",
9
+ "librispeech_6930",
10
+ "librispeech_7127",
11
+ "librispeech_7729"
12
+ ],
13
+ "params_m": 33.61,
14
+ "silencio": {
15
+ "wer": 0.06953260242354299,
16
+ "utmos": 4.151572382450103,
17
+ "spk_sim": 0.7226772201061249,
18
+ "rtf_t4": 0.2737586687655075,
19
+ "rtf_cpu_1thread": 0.6323270854857574
20
+ },
21
+ "paradee_v1_1": {
22
+ "wer": 0.06318522792844777,
23
+ "utmos": 4.407845231294632
24
+ }
25
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:19f6c837e93857af3d91415e88c08a89f4535f4e0e56e9b313bc87fa5cc24276
3
+ size 134476848
ref_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:27e4cf2998ad8b13279721d2d5e21c6a2f9c232b4882a75f7857f760a6899453
3
+ size 250764
ref_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:72fab69f58aa47a6cb82c13c8fcb23979098329e52c42db69ac3a8a5649c123d
3
+ size 722284
ref_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:69df92e49522c52d424c18c1a560295523ca46ddc5e43e11829fed38118d4692
3
+ size 470924
ref_3575.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6aaf21d7747b079cb4f860823e2a9f4b09809474dd7f32754c9bf91a2313d72b
3
+ size 263404
ref_5639.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8069fd8ee2a120f435d682a8e27a9435fa6b80122b5d8843779178d19a3c1c2a
3
+ size 504684
ref_6930.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a0c28ad2cca523cd7e7d72e360a3f314eeb905688358dbf6f150c0a87b87f76
3
+ size 455244
ref_7127.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5b5d5625b3cac60e98bddec49206b053d64d7c0d0d871090801b26f8a2755bce
3
+ size 575084
ref_7729.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f8e9574a4743df5adca62c99b13aeee6cd48082aed075708545a898435aabde3
3
+ size 265644
requirements.txt ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Silencio runtime. misaki (the G2P) declares python<3.13 but runs on 3.13; install it with
2
+ # pip install --no-deps --ignore-requires-python misaki==0.9.4
3
+ torch>=2.5
4
+ torchaudio>=2.5
5
+ numpy
6
+ huggingface-hub
7
+ safetensors
8
+ transformers
9
+ x-transformers==2.2.11
10
+ einops
11
+ librosa
12
+ soundfile
13
+ # misaki's own dependencies
14
+ addict
15
+ regex
16
+ spacy
17
+ num2words
18
+ phonemizer-fork
19
+ espeakng-loader
20
+ # only for enrolling new voices (silencio.enroll)
21
+ s3prl
samples/1_librispeech_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:86acc5e690d395b78d16d99cc8381af523e8a061036a89f44b7258883ce020da
3
+ size 306924
samples/1_librispeech_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ef3d327c72599c3862c772a75b9143c7976079ba830a3b9bba5835bc665713c7
3
+ size 306924
samples/1_librispeech_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeed717d7e2d0f6bd98bf628429151d6687aa668a2cb023eaa4bdec72118b1d1
3
+ size 306924
samples/1_paradee.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:879b44188d8e1d043b6f85f07d89ebc91047f4b40c1143991d5dc778aa7ea5f9
3
+ size 466844
samples/2_librispeech_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4399e04c86c1e83ad22e258fbdc0161f71a56ebe5ad1786d393fb5f141dd63f2
3
+ size 184044
samples/2_librispeech_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:769d8d22e3a93a5ed42be25f0ebd7629273aaee1bf837eca73217895a0d7c425
3
+ size 184044
samples/2_librispeech_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c275d5246288a9d71bcca0d0d90c87e2722d2f415d2579d76eca3c71006dcca8
3
+ size 184044
samples/2_paradee.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3e166f7a5e7d332e3fcc3b7977d4ee7bdca18591772432165d69d2f01e9cd98a
3
+ size 282044
samples/3_librispeech_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0a76e623642c7d3c1c42ab612335c2c7f4debab2d6e4a1a1e1c98358529877ff
3
+ size 235244
samples/3_librispeech_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cc3841776e11eeb75b0472e59480646888a4300d1ed7a7cb3e394762ca500f2f
3
+ size 235244
samples/3_librispeech_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:16f17e71a6ac27d1ca93731212ebc8490f4c97b5c89a7d92912b68deaca08876
3
+ size 235244
samples/3_paradee.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba7d8300a36b960ed4dd95a36fa185d483e18e7cdae175737c807b92ad347e5a
3
+ size 361244
samples/4_librispeech_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3c32ae2b277b885cd31e5799a77c4a51ee64c5502f9f4296d9382cceb89890a5
3
+ size 168684
samples/4_librispeech_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bff1793e67eb2ed58e3b0a0429d85ed5e8551d711ff53c4f258a623ff1c15fa4
3
+ size 168684
samples/4_librispeech_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:041da728500f660fc23cdd7eb2a9f2db217e0066728f0c65d3e12576474fad4f
3
+ size 168684
samples/4_paradee.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:3a0c5dc7a5797da25eb03af806759c22e8e1e6cdec521ebf7151d2cab4b0aaa7
3
+ size 261644
samples/5_librispeech_1320.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e5738455475737c27bb67eebae87c6233065e87e3f035249510878e19985b53a
3
+ size 235244
samples/5_librispeech_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:597f15e3edefa292ce3489574f884398236d69f042d73d8165159aad1cdbbd31
3
+ size 235244
samples/5_librispeech_260.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fffda725e02ce7721b3b41937e57c00782e35909145cb50d034b60db25f31f4f
3
+ size 235244
samples/5_paradee.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:63135b92c297c2634d21277aa08e00de99aad84bdd5abcaff64b5b89eb322121
3
+ size 358844
samples/sentences.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ 1. As of August 2015, there were 169 proposed targets for these goals and 304 proposed indicators to show compliance.
2
+ 2. White dwarf stars, if they have a near companion, may then become Type Ia supernovae.
3
+ 3. According to Herodotus, this was because they had decided in council that they could not beat the Allies in a naval battle.
4
+ 4. The ashes were later taken to Veluvana, where they were enshrined in a silver stupa.
5
+ 5. Ten priests of the Evangelical Lutheran Church of Finland and 90 (moderate) socialists were executed also.
samples/vc_1320_to_2094.wav ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:683d8c80da6838c05a20f90591a859af647b6fbf2a4463ee56fe31e1c4e3870d
3
+ size 245484
silencio/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Silencio: zero-shot English text-to-speech and voice conversion from one weights file."""
2
+ from .adapter import Adapter, bnf_frames
3
+
4
+ __all__ = ["Adapter", "SilencioTTS", "bnf_frames"]
5
+
6
+
7
+ def __getattr__(name): # torchaudio, misaki and x-transformers load only when the model is asked for
8
+ if name == "SilencioTTS":
9
+ from .tts import SilencioTTS
10
+ return SilencioTTS
11
+ raise AttributeError(name)
silencio/adapter.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Trainable bridge between Paradee's text side and MeanVC2's content space.
2
+
3
+ Paradee's text side produces, per 40 Hz frame, a 512-d phonetic feature (`asr`) plus pitch
4
+ and loudness curves at 80 Hz. MeanVC2's DiT consumes 256-d Fast-U2++ bottleneck features
5
+ (BNF) at 25 Hz. The adapter maps the first onto the second, so the TTS path skips both
6
+ Paradee's vocoder and MeanVC2's ASR encoder.
7
+ """
8
+ import math
9
+ import torch
10
+ import torch.nn as nn
11
+ import torch.nn.functional as F
12
+
13
+ PARADEE_FPS = 40 # one aligned frame = 600 samples at 24 kHz
14
+ BNF_FPS = 25 # one BNF frame = 4 fbank frames of 10 ms
15
+ ASR_DIM, BNF_DIM = 512, 256
16
+ PROSODY_CH = 6 # log-F0, voicing and loudness for the two 80 Hz sub-frames
17
+
18
+
19
+ def bnf_frames(seconds, window=19, stride=16):
20
+ """Number of BNF frames the streaming Fast-U2++ (160 ms preset) yields for `seconds` of 16 kHz audio."""
21
+ samples = int(round(seconds * 16000))
22
+ fbank = 1 + (samples - 400) // 160 if samples >= 400 else 0
23
+ return 0 if fbank < window else ((fbank - window) // stride + 1) * (stride // 4)
24
+
25
+
26
+ class ConvNeXt(nn.Module):
27
+ def __init__(self, dim, mult=3, kernel=7, dropout=0.0):
28
+ super().__init__()
29
+ self.dw = nn.Conv1d(dim, dim, kernel, padding=kernel // 2, groups=dim)
30
+ self.norm = nn.LayerNorm(dim)
31
+ self.pw1, self.pw2 = nn.Linear(dim, dim * mult), nn.Linear(dim * mult, dim)
32
+ self.drop = nn.Dropout(dropout)
33
+ self.gamma = nn.Parameter(torch.full((dim,), 1e-2))
34
+
35
+ def forward(self, x, mask): # x [B,C,T], mask [B,1,T]
36
+ h = self.norm(self.dw(x * mask).transpose(1, 2))
37
+ return x + (self.gamma * self.drop(self.pw2(F.gelu(self.pw1(h))))).transpose(1, 2) * mask
38
+
39
+
40
+ class Adapter(nn.Module):
41
+ """asr [B,512,F] @40 Hz, F0 [B,2F], N [B,2F] -> BNF [B,T,256] @25 Hz."""
42
+
43
+ def __init__(self, dim=256, pre_blocks=4, post_blocks=4, dropout=0.1):
44
+ super().__init__()
45
+ self.inp = nn.Conv1d(ASR_DIM + PROSODY_CH, dim, 1)
46
+ self.pre = nn.ModuleList(ConvNeXt(dim, dropout=dropout) for _ in range(pre_blocks))
47
+ self.post = nn.ModuleList(ConvNeXt(dim, dropout=dropout) for _ in range(post_blocks))
48
+ self.norm = nn.LayerNorm(dim)
49
+ self.out = nn.Linear(dim, BNF_DIM)
50
+ # BNF statistics from the training targets: the network predicts normalised BNF
51
+ self.register_buffer("bnf_mean", torch.zeros(BNF_DIM))
52
+ self.register_buffer("bnf_std", torch.ones(BNF_DIM))
53
+
54
+ @staticmethod
55
+ def prosody(F0, N):
56
+ B, F2 = F0.shape
57
+ f0 = F0.view(B, F2 // 2, 2).transpose(1, 2)
58
+ n = N.view(B, F2 // 2, 2).transpose(1, 2)
59
+ voiced = (f0 > 10).float()
60
+ logf0 = torch.where(f0 > 10, (torch.log(f0.clamp_min(1.0)) - math.log(200.0)) / 0.3, torch.zeros_like(f0))
61
+ return torch.cat([logf0, voiced, n / 10.0], 1)
62
+
63
+ def forward(self, asr, F0, N, out_len, in_len=None):
64
+ """out_len: [B] long, target BNF lengths. in_len: [B] long, valid 40 Hz frames (None = all)."""
65
+ B, _, Fr = asr.shape
66
+ in_len = torch.full((B,), Fr, device=asr.device) if in_len is None else in_len
67
+ m_in = (torch.arange(Fr, device=asr.device)[None] < in_len[:, None]).float()[:, None]
68
+ x = self.inp(torch.cat([asr, self.prosody(F0, N)], 1)) * m_in
69
+ for b in self.pre:
70
+ x = b(x, m_in)
71
+ T = int(out_len.max())
72
+ # each item is resampled on its own valid span so padding never leaks into the content
73
+ x = torch.stack([F.pad(F.interpolate(x[i:i + 1, :, :int(in_len[i])], size=int(out_len[i]),
74
+ mode="linear", align_corners=False)[0], (0, T - int(out_len[i])))
75
+ for i in range(B)])
76
+ m_out = (torch.arange(T, device=asr.device)[None] < out_len[:, None]).float()[:, None]
77
+ for b in self.post:
78
+ x = b(x, m_out)
79
+ y = self.out(self.norm(x.transpose(1, 2))) * self.bnf_std + self.bnf_mean
80
+ return y * m_out.transpose(1, 2)
silencio/asr.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MeanVC2's streaming Fast-U2++ ASR encoder: speech -> content tokens (BNF) for voice conversion,
2
+ and the training targets for the adapter. The TorchScript model is downloaded from ASLP-lab/MeanVC2."""
3
+ import torch
4
+ import torchaudio
5
+ import torchaudio.compliance.kaldi as kaldi
6
+ from huggingface_hub import hf_hub_download
7
+
8
+ REPO = "ASLP-lab/MeanVC2"
9
+ SR = 16000
10
+ # the 160 ms Fast-U2++ that feeds MeanVC2's 120 ms + 40 ms model (see MeanVC2 infer_e2e.py)
11
+ ASR_PRESET = dict(asr="fastu2pp_160ms.pt", window=19, stride=16, offset_init=8, offset_step=4, req_cache=8)
12
+
13
+
14
+ def to_16k(wav, sr):
15
+ wav = torch.as_tensor(wav, dtype=torch.float32)
16
+ return wav if sr == SR else torchaudio.functional.resample(wav, sr, SR)
17
+
18
+
19
+ class ContentEncoder:
20
+ """Streaming Fast-U2++ ASR encoder (TorchScript): 16 kHz wave -> BNF [T,256] at 25 Hz.
21
+ The trace has CPU constants baked in, so it always runs on CPU (as in MeanVC2)."""
22
+
23
+ def __init__(self):
24
+ self.device = "cpu"
25
+ self.model = torch.jit.load(hf_hub_download(REPO, ASR_PRESET["asr"]), map_location="cpu").eval()
26
+
27
+ @torch.no_grad()
28
+ def __call__(self, wav16):
29
+ P = ASR_PRESET
30
+ x = torch.as_tensor(wav16, dtype=torch.float32).detach().cpu().reshape(1, -1) * (1 << 15)
31
+ fb = kaldi.fbank(x, frame_length=25, frame_shift=10, snip_edges=True, num_mel_bins=80,
32
+ energy_floor=0.0, dither=0.0, sample_frequency=SR)[None].to(self.device)
33
+ att = torch.zeros(6, 4, P["req_cache"], 128, device=self.device)
34
+ cnn = torch.zeros(6, 1, 256, 8, device=self.device)
35
+ offset, outs, i = P["offset_init"], [], 0
36
+ req = torch.tensor(P["req_cache"], dtype=torch.int64)
37
+ while i + P["window"] <= fb.shape[1]:
38
+ out, att, cnn = self.model(fb[:, i:i + P["window"]], torch.tensor(offset, dtype=torch.int64), req, att, cnn)
39
+ outs.append(out[0])
40
+ offset += P["offset_step"]; i += P["stride"]
41
+ return torch.cat(outs) if outs else torch.zeros(0, 256, device=self.device)
silencio/ecapa_tdnn.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/ASLP-lab/MeanVC2 (preprocess/models/ecapa_tdnn.py), Apache-2.0;
2
+ # the model code follows https://github.com/lawlict/ECAPA-TDNN (MIT). UpstreamExpert is imported at the top
3
+ # so the WavLM checkpoint path can be passed in as config_path.
4
+ # part of the code is borrowed from https://github.com/lawlict/ECAPA-TDNN
5
+
6
+ import os
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+ import torchaudio.transforms as trans
11
+ from s3prl.upstream.wavlm.expert import UpstreamExpert
12
+ # from .utils import UpstreamExpert
13
+
14
+
15
+ ''' Res2Conv1d + BatchNorm1d + ReLU
16
+ '''
17
+
18
+
19
+ class Res2Conv1dReluBn(nn.Module):
20
+ '''
21
+ in_channels == out_channels == channels
22
+ '''
23
+
24
+ def __init__(self, channels, kernel_size=1, stride=1, padding=0, dilation=1, bias=True, scale=4):
25
+ super().__init__()
26
+ assert channels % scale == 0, "{} % {} != 0".format(channels, scale)
27
+ self.scale = scale
28
+ self.width = channels // scale
29
+ self.nums = scale if scale == 1 else scale - 1
30
+
31
+ self.convs = []
32
+ self.bns = []
33
+ for i in range(self.nums):
34
+ self.convs.append(nn.Conv1d(self.width, self.width, kernel_size, stride, padding, dilation, bias=bias))
35
+ self.bns.append(nn.BatchNorm1d(self.width))
36
+ self.convs = nn.ModuleList(self.convs)
37
+ self.bns = nn.ModuleList(self.bns)
38
+
39
+ def forward(self, x):
40
+ out = []
41
+ spx = torch.split(x, self.width, 1)
42
+ for i in range(self.nums):
43
+ if i == 0:
44
+ sp = spx[i]
45
+ else:
46
+ sp = sp + spx[i]
47
+ # Order: conv -> relu -> bn
48
+ sp = self.convs[i](sp)
49
+ sp = self.bns[i](F.relu(sp))
50
+ out.append(sp)
51
+ if self.scale != 1:
52
+ out.append(spx[self.nums])
53
+ out = torch.cat(out, dim=1)
54
+
55
+ return out
56
+
57
+
58
+ ''' Conv1d + BatchNorm1d + ReLU
59
+ '''
60
+
61
+
62
+ class Conv1dReluBn(nn.Module):
63
+ def __init__(self, in_channels, out_channels, kernel_size=1, stride=1, padding=0, dilation=1, bias=True):
64
+ super().__init__()
65
+ self.conv = nn.Conv1d(in_channels, out_channels, kernel_size, stride, padding, dilation, bias=bias)
66
+ self.bn = nn.BatchNorm1d(out_channels)
67
+
68
+ def forward(self, x):
69
+ return self.bn(F.relu(self.conv(x)))
70
+
71
+
72
+ ''' The SE connection of 1D case.
73
+ '''
74
+
75
+
76
+ class SE_Connect(nn.Module):
77
+ def __init__(self, channels, se_bottleneck_dim=128):
78
+ super().__init__()
79
+ self.linear1 = nn.Linear(channels, se_bottleneck_dim)
80
+ self.linear2 = nn.Linear(se_bottleneck_dim, channels)
81
+
82
+ def forward(self, x):
83
+ out = x.mean(dim=2)
84
+ out = F.relu(self.linear1(out))
85
+ out = torch.sigmoid(self.linear2(out))
86
+ out = x * out.unsqueeze(2)
87
+
88
+ return out
89
+
90
+
91
+ ''' SE-Res2Block of the ECAPA-TDNN architecture.
92
+ '''
93
+
94
+
95
+ # def SE_Res2Block(channels, kernel_size, stride, padding, dilation, scale):
96
+ # return nn.Sequential(
97
+ # Conv1dReluBn(channels, 512, kernel_size=1, stride=1, padding=0),
98
+ # Res2Conv1dReluBn(512, kernel_size, stride, padding, dilation, scale=scale),
99
+ # Conv1dReluBn(512, channels, kernel_size=1, stride=1, padding=0),
100
+ # SE_Connect(channels)
101
+ # )
102
+
103
+
104
+ class SE_Res2Block(nn.Module):
105
+ def __init__(self, in_channels, out_channels, kernel_size, stride, padding, dilation, scale, se_bottleneck_dim):
106
+ super().__init__()
107
+ self.Conv1dReluBn1 = Conv1dReluBn(in_channels, out_channels, kernel_size=1, stride=1, padding=0)
108
+ self.Res2Conv1dReluBn = Res2Conv1dReluBn(out_channels, kernel_size, stride, padding, dilation, scale=scale)
109
+ self.Conv1dReluBn2 = Conv1dReluBn(out_channels, out_channels, kernel_size=1, stride=1, padding=0)
110
+ self.SE_Connect = SE_Connect(out_channels, se_bottleneck_dim)
111
+
112
+ self.shortcut = None
113
+ if in_channels != out_channels:
114
+ self.shortcut = nn.Conv1d(
115
+ in_channels=in_channels,
116
+ out_channels=out_channels,
117
+ kernel_size=1,
118
+ )
119
+
120
+ def forward(self, x):
121
+ residual = x
122
+ if self.shortcut:
123
+ residual = self.shortcut(x)
124
+
125
+ x = self.Conv1dReluBn1(x)
126
+ x = self.Res2Conv1dReluBn(x)
127
+ x = self.Conv1dReluBn2(x)
128
+ x = self.SE_Connect(x)
129
+
130
+ return x + residual
131
+
132
+
133
+ ''' Attentive weighted mean and standard deviation pooling.
134
+ '''
135
+
136
+
137
+ class AttentiveStatsPool(nn.Module):
138
+ def __init__(self, in_dim, attention_channels=128, global_context_att=False):
139
+ super().__init__()
140
+ self.global_context_att = global_context_att
141
+
142
+ # Use Conv1d with stride == 1 rather than Linear, then we don't need to transpose inputs.
143
+ if global_context_att:
144
+ self.linear1 = nn.Conv1d(in_dim * 3, attention_channels, kernel_size=1) # equals W and b in the paper
145
+ else:
146
+ self.linear1 = nn.Conv1d(in_dim, attention_channels, kernel_size=1) # equals W and b in the paper
147
+ self.linear2 = nn.Conv1d(attention_channels, in_dim, kernel_size=1) # equals V and k in the paper
148
+
149
+ def forward(self, x):
150
+
151
+ if self.global_context_att:
152
+ context_mean = torch.mean(x, dim=-1, keepdim=True).expand_as(x)
153
+ context_std = torch.sqrt(torch.var(x, dim=-1, keepdim=True) + 1e-10).expand_as(x)
154
+ x_in = torch.cat((x, context_mean, context_std), dim=1)
155
+ else:
156
+ x_in = x
157
+
158
+ # DON'T use ReLU here! In experiments, I find ReLU hard to converge.
159
+ alpha = torch.tanh(self.linear1(x_in))
160
+ # alpha = F.relu(self.linear1(x_in))
161
+ alpha = torch.softmax(self.linear2(alpha), dim=2)
162
+ mean = torch.sum(alpha * x, dim=2)
163
+ residuals = torch.sum(alpha * (x ** 2), dim=2) - mean ** 2
164
+ std = torch.sqrt(residuals.clamp(min=1e-9))
165
+ return torch.cat([mean, std], dim=1)
166
+
167
+
168
+ class ECAPA_TDNN(nn.Module):
169
+ def __init__(self, feat_dim=80, channels=512, emb_dim=192, global_context_att=False,
170
+ feat_type='fbank', sr=16000, feature_selection="hidden_states", update_extract=False, config_path=None):
171
+ super().__init__()
172
+
173
+ self.feat_type = feat_type
174
+ self.feature_selection = feature_selection
175
+ self.update_extract = update_extract
176
+ self.sr = sr
177
+
178
+ if feat_type == "fbank" or feat_type == "mfcc":
179
+ self.update_extract = False
180
+
181
+ win_len = int(sr * 0.025)
182
+ hop_len = int(sr * 0.01)
183
+
184
+ if feat_type == 'fbank':
185
+ self.feature_extract = trans.MelSpectrogram(sample_rate=sr, n_fft=512, win_length=win_len,
186
+ hop_length=hop_len, f_min=0.0, f_max=sr // 2,
187
+ pad=0, n_mels=feat_dim)
188
+ elif feat_type == 'mfcc':
189
+ melkwargs = {
190
+ 'n_fft': 512,
191
+ 'win_length': win_len,
192
+ 'hop_length': hop_len,
193
+ 'f_min': 0.0,
194
+ 'f_max': sr // 2,
195
+ 'pad': 0
196
+ }
197
+ self.feature_extract = trans.MFCC(sample_rate=sr, n_mfcc=feat_dim, log_mels=False,
198
+ melkwargs=melkwargs)
199
+ else:
200
+ if config_path is None:
201
+ # TODO: meanvc2 root directory (two levels above preprocess/models/)
202
+ _ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
203
+ self.feature_extract = UpstreamExpert(ckpt=os.path.join(_ROOT, 'preprocess/ckpts/wavlm_large.pt'))
204
+ else:
205
+ self.feature_extract = UpstreamExpert(config_path)
206
+ if len(self.feature_extract.model.encoder.layers) == 24 and hasattr(self.feature_extract.model.encoder.layers[23].self_attn, "fp32_attention"):
207
+ self.feature_extract.model.encoder.layers[23].self_attn.fp32_attention = False
208
+ if len(self.feature_extract.model.encoder.layers) == 24 and hasattr(self.feature_extract.model.encoder.layers[11].self_attn, "fp32_attention"):
209
+ self.feature_extract.model.encoder.layers[11].self_attn.fp32_attention = False
210
+
211
+ self.feat_num = self.get_feat_num()
212
+ self.feature_weight = nn.Parameter(torch.zeros(self.feat_num))
213
+
214
+ if feat_type != 'fbank' and feat_type != 'mfcc':
215
+ freeze_list = ['final_proj', 'label_embs_concat', 'mask_emb', 'project_q', 'quantizer']
216
+ for name, param in self.feature_extract.named_parameters():
217
+ for freeze_val in freeze_list:
218
+ if freeze_val in name:
219
+ param.requires_grad = False
220
+ break
221
+
222
+ if not self.update_extract:
223
+ for param in self.feature_extract.parameters():
224
+ param.requires_grad = False
225
+
226
+ self.instance_norm = nn.InstanceNorm1d(feat_dim)
227
+ # self.channels = [channels] * 4 + [channels * 3]
228
+ self.channels = [channels] * 4 + [1536]
229
+
230
+ self.layer1 = Conv1dReluBn(feat_dim, self.channels[0], kernel_size=5, padding=2)
231
+ self.layer2 = SE_Res2Block(self.channels[0], self.channels[1], kernel_size=3, stride=1, padding=2, dilation=2, scale=8, se_bottleneck_dim=128)
232
+ self.layer3 = SE_Res2Block(self.channels[1], self.channels[2], kernel_size=3, stride=1, padding=3, dilation=3, scale=8, se_bottleneck_dim=128)
233
+ self.layer4 = SE_Res2Block(self.channels[2], self.channels[3], kernel_size=3, stride=1, padding=4, dilation=4, scale=8, se_bottleneck_dim=128)
234
+
235
+ # self.conv = nn.Conv1d(self.channels[-1], self.channels[-1], kernel_size=1)
236
+ cat_channels = channels * 3
237
+ self.conv = nn.Conv1d(cat_channels, self.channels[-1], kernel_size=1)
238
+ self.pooling = AttentiveStatsPool(self.channels[-1], attention_channels=128, global_context_att=global_context_att)
239
+ self.bn = nn.BatchNorm1d(self.channels[-1] * 2)
240
+ self.linear = nn.Linear(self.channels[-1] * 2, emb_dim)
241
+
242
+
243
+ def get_feat_num(self):
244
+ self.feature_extract.eval()
245
+ wav = [torch.randn(self.sr).to(next(self.feature_extract.parameters()).device)]
246
+ with torch.no_grad():
247
+ features = self.feature_extract(wav)
248
+ select_feature = features[self.feature_selection]
249
+ if isinstance(select_feature, (list, tuple)):
250
+ return len(select_feature)
251
+ else:
252
+ return 1
253
+
254
+ def get_feat(self, x):
255
+ if self.update_extract:
256
+ x = self.feature_extract([sample for sample in x])
257
+ else:
258
+ with torch.no_grad():
259
+ if self.feat_type == 'fbank' or self.feat_type == 'mfcc':
260
+ x = self.feature_extract(x) + 1e-6 # B x feat_dim x time_len
261
+ else:
262
+ x = self.feature_extract([sample for sample in x])
263
+
264
+ if self.feat_type == 'fbank':
265
+ x = x.log()
266
+
267
+ if self.feat_type != "fbank" and self.feat_type != "mfcc":
268
+ x = x[self.feature_selection]
269
+ if isinstance(x, (list, tuple)):
270
+ x = torch.stack(x, dim=0)
271
+ else:
272
+ x = x.unsqueeze(0)
273
+ norm_weights = F.softmax(self.feature_weight, dim=-1).unsqueeze(-1).unsqueeze(-1).unsqueeze(-1)
274
+ x = (norm_weights * x).sum(dim=0)
275
+ x = torch.transpose(x, 1, 2) + 1e-6
276
+
277
+ x = self.instance_norm(x)
278
+ return x
279
+
280
+ def forward(self, x):
281
+ x = self.get_feat(x)
282
+
283
+ out1 = self.layer1(x)
284
+ out2 = self.layer2(out1)
285
+ out3 = self.layer3(out2)
286
+ out4 = self.layer4(out3)
287
+
288
+ out = torch.cat([out2, out3, out4], dim=1)
289
+ out = F.relu(self.conv(out))
290
+ out = self.bn(self.pooling(out))
291
+ out = self.linear(out)
292
+
293
+ return out
294
+
295
+
296
+ def ECAPA_TDNN_SMALL(feat_dim, emb_dim=256, feat_type='fbank', sr=16000, feature_selection="hidden_states", update_extract=False, config_path=None):
297
+ return ECAPA_TDNN(feat_dim=feat_dim, channels=512, emb_dim=emb_dim,
298
+ feat_type=feat_type, sr=sr, feature_selection=feature_selection, update_extract=update_extract, config_path=config_path)
299
+
300
+ if __name__ == '__main__':
301
+ x = torch.zeros(2, 32000)
302
+ model = ECAPA_TDNN_SMALL(feat_dim=768, emb_dim=256, feat_type='hubert_base', feature_selection="hidden_states",
303
+ update_extract=False)
304
+
305
+ out = model(x)
306
+ # print(model)
307
+ print(out.shape)
308
+
silencio/enroll.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Voice enrollment: a reference clip -> the 256-d voice embedding Silencio speaks with.
2
+
3
+ The encoder is WavLM-Large + ECAPA-TDNN, the speaker model MeanVC2 was trained against. It is large
4
+ (~1.3 GB, CC BY-SA 3.0 weights) and only needed to add new voices, so it is not part of model.safetensors;
5
+ its weights are downloaded on first use. Needs `pip install s3prl`.
6
+ """
7
+ import numpy as np
8
+ import torch
9
+ import torchaudio
10
+ from huggingface_hub import hf_hub_download
11
+
12
+ # s3prl-format WavLM-Large, and the UniSpeech speaker-verification fine-tune (sha256 51f07e3b...)
13
+ WAVLM = ("s3prl/converted_ckpts", "wavlm_large.pt")
14
+ ECAPA = ("bezzam/wavlm_large_finetune_seed_tts_eval", "wavlm_large_finetune.pth")
15
+
16
+
17
+ class VoiceEncoder:
18
+ def __init__(self, device=None):
19
+ from .ecapa_tdnn import ECAPA_TDNN_SMALL
20
+ self.device = device or ("cuda" if torch.cuda.is_available() else "cpu")
21
+ self.model = ECAPA_TDNN_SMALL(feat_dim=1024, feat_type="wavlm_large", config_path=hf_hub_download(*WAVLM))
22
+ self.model.load_state_dict(torch.load(hf_hub_download(*ECAPA), map_location="cpu")["model"], strict=False)
23
+ self.model.to(self.device).eval()
24
+
25
+ @torch.no_grad()
26
+ def __call__(self, wav, sr):
27
+ """wav: mono float array (5-15 s of clean speech works best) -> voice [256]."""
28
+ wav = torch.as_tensor(np.asarray(wav, dtype=np.float32))
29
+ if wav.ndim > 1:
30
+ wav = wav.mean(-1)
31
+ if sr != 16000:
32
+ wav = torchaudio.functional.resample(wav, sr, 16000)
33
+ return self.model(wav[None].to(self.device))[0].float().cpu()
34
+
35
+
36
+ _encoder = None
37
+
38
+
39
+ def enroll(wav, sr, device=None):
40
+ """Voice embedding for `wav` at sample rate `sr`; the encoder is loaded once and reused."""
41
+ global _encoder
42
+ if _encoder is None:
43
+ _encoder = VoiceEncoder(device)
44
+ return _encoder(wav, sr)
silencio/meanvc2/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """MeanVC2 DiT renderer (ASLP-lab, Apache-2.0), vendored for inference."""
2
+ from .dit import DiT
silencio/meanvc2/dit.py ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/ASLP-lab/MeanVC2 (src/infer/dit_kvcache.py), Apache-2.0.
2
+ # Kept verbatim apart from import paths so Silencio runs without the original repo.
3
+ """
4
+ ein notation:
5
+ b - batch
6
+ n - sequence
7
+ nt - text sequence
8
+ nw - raw wave length
9
+ d - dimension
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import torch
15
+ from torch import nn
16
+ import torch.nn.functional as F
17
+ from einops import rearrange
18
+
19
+ from x_transformers.x_transformers import RotaryEmbedding
20
+
21
+ # from src.model.prompt_vp import MRTE
22
+ from .modules import (
23
+ TimestepEmbedding,
24
+ ConvNeXtV2Block,
25
+ ConvPositionEmbedding,
26
+ AdaLayerNorm_Final,
27
+ precompute_freqs_cis,
28
+ get_pos_embed_indices,
29
+ )
30
+
31
+ from .kv_modules import (
32
+ DiTBlock,
33
+ ChunkDiTBlock,
34
+ )
35
+
36
+ class GlobalTimbreMemory(nn.Module):
37
+ """Global Timbre Memory (GTM)
38
+ Decomposes the global speaker embedding into K reusable timbre prototype slots.
39
+ A speaker-specific 2-layer MLP generates speaker-specific KV, fused with a universal prior.
40
+ """
41
+ def __init__(self, spk_dim=256, memory_slots=8, hidden_dim=256):
42
+ super().__init__()
43
+ self.memory_slots = memory_slots
44
+ self.hidden_dim = hidden_dim
45
+
46
+ # Speaker-specific mapping -- 2-layer MLP for increased expressiveness
47
+ self.mlp_k = nn.Sequential(
48
+ nn.Linear(spk_dim, spk_dim),
49
+ nn.SiLU(),
50
+ nn.Linear(spk_dim, memory_slots * hidden_dim),
51
+ )
52
+ self.mlp_v = nn.Sequential(
53
+ nn.Linear(spk_dim, spk_dim),
54
+ nn.SiLU(),
55
+ nn.Linear(spk_dim, memory_slots * hidden_dim),
56
+ )
57
+
58
+ # Universal speaker-agnostic prototypes (learns common pronunciation patterns), small-variance init
59
+ self.k_prior = nn.Parameter(torch.zeros(memory_slots, hidden_dim))
60
+ self.v_prior = nn.Parameter(torch.zeros(memory_slots, hidden_dim))
61
+ nn.init.normal_(self.k_prior, std=0.02)
62
+ nn.init.normal_(self.v_prior, std=0.02)
63
+
64
+ # LayerNorm after fusion for stable training
65
+ self.norm_k = nn.LayerNorm(hidden_dim)
66
+ self.norm_v = nn.LayerNorm(hidden_dim)
67
+
68
+ def forward(self, spks):
69
+ # spks: [B, spk_dim] static global speaker embedding
70
+ B = spks.shape[0]
71
+ # Generate speaker-specific key-value pairs
72
+ k_spk = self.mlp_k(spks).reshape(B, self.memory_slots, self.hidden_dim)
73
+ v_spk = self.mlp_v(spks).reshape(B, self.memory_slots, self.hidden_dim)
74
+ # Fuse with universal prototypes + LayerNorm
75
+ k = self.norm_k(k_spk + torch.tanh(self.k_prior).unsqueeze(0)) # [B, K, D]
76
+ v = self.norm_v(v_spk + torch.tanh(self.v_prior).unsqueeze(0)) # [B, K, D]
77
+ return k, v
78
+
79
+
80
+ class TemporalTimbreEncoder(nn.Module):
81
+ """Temporal Timbre Encoder (TVT processing block)
82
+ Frame-level content vectors serve as Query in Multi-Head Cross-Attention with GTM.
83
+ The resulting time-varying timbre features act as frame-level conditioning, combined
84
+ with the global speaker embedding at the input.
85
+ """
86
+ def __init__(self, content_dim=256, hidden_dim=256, attn_dim=128, num_heads=4):
87
+ super().__init__()
88
+ self.num_heads = num_heads
89
+ self.head_dim = attn_dim // num_heads
90
+ assert attn_dim % num_heads == 0
91
+
92
+ # Multi-Head Cross-Attention projections
93
+ self.q_proj = nn.Linear(content_dim, attn_dim)
94
+ self.k_proj = nn.Linear(hidden_dim, attn_dim) # Input from GTM hidden_dim
95
+ self.v_proj = nn.Linear(hidden_dim, attn_dim) # Independent V projection
96
+ self.out_proj = nn.Linear(attn_dim, content_dim) # Output projection back to content_dim
97
+
98
+ self.attn_scale = self.head_dim ** -0.5
99
+ self.attn_norm = nn.LayerNorm(content_dim)
100
+
101
+
102
+ def forward(self, bn, k_mem, v_mem):
103
+ """Args:
104
+ bn: [B, T, content_dim] frame-level content features
105
+ k_mem: [B, K, hidden_dim] GTM keys
106
+ v_mem: [B, K, hidden_dim] GTM values
107
+ Returns:
108
+ timbre_cond: [B, T, content_dim] time-varying timbre conditioning
109
+ """
110
+ B, T, _ = bn.shape
111
+ K = k_mem.shape[1]
112
+
113
+ # ---- 1. Multi-Head Cross-Attention: content queries × GTM ----
114
+ q = self.q_proj(bn) # [B, T, attn_dim]
115
+ k = self.k_proj(k_mem) # [B, K, attn_dim]
116
+ v = self.v_proj(v_mem) # [B, K, attn_dim]
117
+
118
+ # reshape → [B, num_heads, seq_len, head_dim]
119
+ q = q.view(B, T, self.num_heads, self.head_dim).transpose(1, 2)
120
+ k = k.view(B, K, self.num_heads, self.head_dim).transpose(1, 2)
121
+ v = v.view(B, K, self.num_heads, self.head_dim).transpose(1, 2)
122
+
123
+ attn = torch.matmul(q, k.transpose(-2, -1)) * self.attn_scale # [B, H, T, K]
124
+ attn = torch.softmax(attn, dim=-1)
125
+ v_t = torch.matmul(attn, v) # [B, H, T, head_dim]
126
+
127
+ # merge heads -> output projection
128
+ v_t = v_t.transpose(1, 2).contiguous().view(B, T, -1) # [B, T, attn_dim]
129
+ timbre_cond = self.attn_norm(self.out_proj(v_t)) # [B, T, content_dim]
130
+
131
+ return timbre_cond
132
+
133
+ class InputEmbedding(nn.Module):
134
+ def __init__(self, mel_dim, cond_dim, out_dim):
135
+ super().__init__()
136
+ self.proj = nn.Linear(mel_dim + cond_dim * 2, out_dim)
137
+ # self.conv_pos_embed = ConvPositionEmbedding(dim=out_dim)
138
+
139
+ def forward(self, x: float["b n d"], cond: float["b n d"], spks: float["b n d"], drop_audio_cond=False): # noqa: F722
140
+ # def forward(self, x: float["b n d"], cond: float["b n d"], timbre_cond: float["b n d"], drop_audio_cond=False): # noqa: F722
141
+ if drop_audio_cond: # cfg for cond audio
142
+ cond = torch.zeros_like(cond)
143
+ spks = torch.zeros_like(spks)
144
+
145
+ x = self.proj(torch.cat((x, cond, spks), dim=-1))
146
+ # x = self.conv_pos_embed(x) + x
147
+ return x
148
+
149
+
150
+
151
+ # Transformer backbone using DiT blocks
152
+
153
+
154
+ class DiT(nn.Module):
155
+ def __init__(
156
+ self,
157
+ *,
158
+ dim,
159
+ depth=8,
160
+ heads=8,
161
+ dim_head=64,
162
+ dropout=0.1,
163
+ ff_mult=4,
164
+ mel_dim=80,
165
+ bn_dim=256,
166
+ qk_norm=None,
167
+ conv_layers=0,
168
+ chunk_size=8,
169
+ block_size=4,
170
+ pe_attn_head=None,
171
+ long_skip_connection=False,
172
+ checkpoint_activations=False,
173
+ forward_layers=[0], # Layer 0 allowed to look ahead
174
+ backward_layers=[0,1,2,3], # Layer 3 allowed to look behind
175
+ t_f_num=[1,0,0,0],
176
+ t_p_num=[2,2,1,1],
177
+ ):
178
+ super().__init__()
179
+
180
+ self.t_time_embed = TimestepEmbedding(dim)
181
+ self.r_time_embed = TimestepEmbedding(dim)
182
+ self.input_embed = InputEmbedding(mel_dim, bn_dim, dim)
183
+ self.rotary_embed = RotaryEmbedding(dim_head)
184
+
185
+ self.dim = dim
186
+ self.depth = depth
187
+
188
+ # GTM + TVT time-varying timbre module (replaces MRTE)
189
+ self.gtm = GlobalTimbreMemory(spk_dim=bn_dim, memory_slots=32, hidden_dim=bn_dim)
190
+ self.temporal_timbre = TemporalTimbreEncoder(
191
+ content_dim=bn_dim, hidden_dim=bn_dim,
192
+ attn_dim=128, num_heads=4,
193
+ )
194
+
195
+ forward_layers = set(forward_layers) if forward_layers else set()
196
+ backward_layers = set(backward_layers) if backward_layers else set()
197
+
198
+ self.transformer_blocks = nn.ModuleList(
199
+ [
200
+ ChunkDiTBlock(
201
+ dim=dim,
202
+ heads=heads,
203
+ dim_head=dim_head,
204
+ ff_mult=ff_mult,
205
+ dropout=dropout,
206
+ qk_norm=qk_norm,
207
+ chunk_size=chunk_size,
208
+ block_size=block_size,
209
+ pe_attn_head=pe_attn_head,
210
+ t_p=t_p_num[i] if i in backward_layers else 0, # backward
211
+ t_f=t_f_num[i] if i in forward_layers else 0, # forward
212
+ )
213
+ for i in range(depth)
214
+ ]
215
+ )
216
+ self.long_skip_connection = nn.Linear(dim * 2, dim, bias=False) if long_skip_connection else None
217
+
218
+ self.norm_out = AdaLayerNorm_Final(dim) # final modulation
219
+ self.proj_out = nn.Linear(dim, mel_dim)
220
+
221
+ self.checkpoint_activations = checkpoint_activations
222
+
223
+ self.initialize_weights()
224
+
225
+ def initialize_weights(self):
226
+ # Zero-out AdaLN layers in DiT blocks:
227
+ for block in self.transformer_blocks:
228
+ nn.init.constant_(block.attn_norm.linear.weight, 0)
229
+ nn.init.constant_(block.attn_norm.linear.bias, 0)
230
+
231
+ # Zero-out output layers:
232
+ nn.init.constant_(self.norm_out.linear.weight, 0)
233
+ nn.init.constant_(self.norm_out.linear.bias, 0)
234
+ nn.init.constant_(self.proj_out.weight, 0)
235
+ nn.init.constant_(self.proj_out.bias, 0)
236
+
237
+ def ckpt_wrapper(self, module):
238
+ # https://github.com/chuanyangjin/fast-DiT/blob/main/models.py
239
+ def ckpt_forward(*inputs):
240
+ outputs = module(*inputs)
241
+ return outputs
242
+
243
+ return ckpt_forward
244
+
245
+
246
+ def forward(
247
+ self,
248
+ x: float["b n d"], # nosied input audio # noqa: F722 B, T, 80
249
+ t: float["b"] | float[""], # time step # noqa: F821 F722
250
+ r: float["b"] | float[""], # time step # noqa: F821 F722
251
+ cache: float["b n d"],
252
+ cond: float["b n d"], # bn # noqa: F722 B, T, 256
253
+ spks: float["b d"], # spks # noqa: F722 B, 256
254
+ offset=0,
255
+ mask: bool["b n"] | None = None, # noqa: F722
256
+ is_inference: bool = False,
257
+ is_uncondition: bool = False,
258
+ cfg_mask: bool["b"] | None = None, # noqa: F722
259
+ kv_cache=None,
260
+ ):
261
+
262
+ batch, seq_len = x.shape[0], x.shape[1]
263
+
264
+ # ---- timestep embedding ----
265
+ t = self.t_time_embed(t)
266
+ r = self.r_time_embed(r)
267
+ t = t + r
268
+
269
+ # ---- GTM: global speaker embedding -> timbre memory key-value pairs ----
270
+ k_mem, v_mem = self.gtm(spks) # spks: [B, 256] -> k,v: [B, K, 256]
271
+
272
+ # ---- TVT: frame-level BN x GTM -> timbre-enhanced timbre_cond ----
273
+ timbre_cond = self.temporal_timbre(cond, k_mem, v_mem) # [B, T, bn_dim]
274
+
275
+ # Expand spks_global to frame level, as pure global identity condition (independent of timbre_cond)
276
+ spks_expanded = spks.unsqueeze(1).expand(-1, cond.shape[1], -1) # [B, T, spk_dim]
277
+
278
+ # ---- CFG masking ----
279
+ if cfg_mask is not None:
280
+ cfg_mask_ = rearrange(cfg_mask, "b -> b 1 1")
281
+ timbre_cond = torch.where(cfg_mask_, torch.zeros_like(timbre_cond), timbre_cond)
282
+ spks_expanded = torch.where(cfg_mask_, torch.zeros_like(spks_expanded), spks_expanded)
283
+
284
+ # Dual-path input: timbre_cond (content+timbre) + spks_expanded (pure global identity)
285
+ x = self.input_embed(x, timbre_cond, spks_expanded, drop_audio_cond=is_uncondition)
286
+
287
+ # train
288
+ if not is_inference:
289
+
290
+ rope = self.rotary_embed.forward_from_seq_len(seq_len)
291
+ # infer
292
+ else:
293
+ if cache != None:
294
+ cache = self.cache_embed(cache)
295
+ x = torch.concat((cache, x), dim=1) # [b, cache_len + seq_len, dim]
296
+
297
+ # inference does not need to consider mask
298
+ cache_len = cache.shape[1]
299
+ rope_cache = self.rotary_embed.forward_from_seq_len(cache_len)
300
+ rope_x = self.rotary_embed.forward_from_seq_len(offset + seq_len)
301
+ rope = (torch.concat((rope_cache[0], rope_x[0][:, -seq_len:, :]), dim=1), rope_cache[1])
302
+ else:
303
+ rope = self.rotary_embed.forward_from_seq_len(offset + seq_len)
304
+
305
+
306
+ if self.long_skip_connection is not None:
307
+ residual = x
308
+
309
+ new_kv_cache = []
310
+ # inner_hidden_states = []
311
+ for index_block, block in enumerate(self.transformer_blocks):
312
+ if kv_cache is not None:
313
+ block_kv_cache = kv_cache[index_block]
314
+ else:
315
+ block_kv_cache = None
316
+ if self.checkpoint_activations:
317
+ # https://pytorch.org/docs/stable/checkpoint.html#torch.utils.checkpoint.checkpoint
318
+ x, new_block_kv_cache = torch.utils.checkpoint.checkpoint(self.ckpt_wrapper(block), x, t, mask, rope, block_kv_cache, use_reentrant=False)
319
+ else:
320
+ x, new_block_kv_cache = block(x, t, mask=mask, rope=rope, is_inference=is_inference, kv_cache=block_kv_cache)
321
+ new_kv_cache.append(new_block_kv_cache)
322
+ if self.long_skip_connection is not None:
323
+ x = self.long_skip_connection(torch.cat((x, residual), dim=-1))
324
+
325
+
326
+ # x = x[:, -seq_len:, :]
327
+ x = self.norm_out(x, t)
328
+
329
+ output = self.proj_out(x)
330
+
331
+ return output, new_kv_cache
332
+
333
+
334
+
silencio/meanvc2/kv_modules.py ADDED
@@ -0,0 +1,1060 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/ASLP-lab/MeanVC2 (src/infer/modules_kvcahe.py), Apache-2.0.
2
+ # Kept verbatim apart from import paths so Silencio runs without the original repo.
3
+ """
4
+ ein notation:
5
+ b - batch
6
+ n - sequence
7
+ nt - text sequence
8
+ nw - raw wave length
9
+ d - dimension
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+ from typing import Optional
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ import torchaudio
20
+ from librosa.filters import mel as librosa_mel_fn
21
+ from torch import nn
22
+ from x_transformers.x_transformers import apply_rotary_pos_emb
23
+
24
+
25
+ # raw wav to mel spec
26
+
27
+
28
+ mel_basis_cache = {}
29
+ hann_window_cache = {}
30
+
31
+
32
+ def get_bigvgan_mel_spectrogram(
33
+ waveform,
34
+ n_fft=1024,
35
+ n_mel_channels=100,
36
+ target_sample_rate=24000,
37
+ hop_length=256,
38
+ win_length=1024,
39
+ fmin=0,
40
+ fmax=None,
41
+ center=False,
42
+ ): # Copy from https://github.com/NVIDIA/BigVGAN/tree/main
43
+ device = waveform.device
44
+ key = f"{n_fft}_{n_mel_channels}_{target_sample_rate}_{hop_length}_{win_length}_{fmin}_{fmax}_{device}"
45
+
46
+ if key not in mel_basis_cache:
47
+ mel = librosa_mel_fn(sr=target_sample_rate, n_fft=n_fft, n_mels=n_mel_channels, fmin=fmin, fmax=fmax)
48
+ mel_basis_cache[key] = torch.from_numpy(mel).float().to(device) # TODO: why they need .float()?
49
+ hann_window_cache[key] = torch.hann_window(win_length).to(device)
50
+
51
+ mel_basis = mel_basis_cache[key]
52
+ hann_window = hann_window_cache[key]
53
+
54
+ padding = (n_fft - hop_length) // 2
55
+ waveform = torch.nn.functional.pad(waveform.unsqueeze(1), (padding, padding), mode="reflect").squeeze(1)
56
+
57
+ spec = torch.stft(
58
+ waveform,
59
+ n_fft,
60
+ hop_length=hop_length,
61
+ win_length=win_length,
62
+ window=hann_window,
63
+ center=center,
64
+ pad_mode="reflect",
65
+ normalized=False,
66
+ onesided=True,
67
+ return_complex=True,
68
+ )
69
+ spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9)
70
+
71
+ mel_spec = torch.matmul(mel_basis, spec)
72
+ mel_spec = torch.log(torch.clamp(mel_spec, min=1e-5))
73
+
74
+ return mel_spec
75
+
76
+
77
+ def get_vocos_mel_spectrogram(
78
+ waveform,
79
+ n_fft=1024,
80
+ n_mel_channels=100,
81
+ target_sample_rate=24000,
82
+ hop_length=256,
83
+ win_length=1024,
84
+ ):
85
+ mel_stft = torchaudio.transforms.MelSpectrogram(
86
+ sample_rate=target_sample_rate,
87
+ n_fft=n_fft,
88
+ win_length=win_length,
89
+ hop_length=hop_length,
90
+ n_mels=n_mel_channels,
91
+ power=1,
92
+ center=True,
93
+ normalized=False,
94
+ norm=None,
95
+ ).to(waveform.device)
96
+ if len(waveform.shape) == 3:
97
+ waveform = waveform.squeeze(1) # 'b 1 nw -> b nw'
98
+
99
+ assert len(waveform.shape) == 2
100
+
101
+ mel = mel_stft(waveform)
102
+ mel = mel.clamp(min=1e-5).log()
103
+ return mel
104
+
105
+
106
+ class MelSpec(nn.Module):
107
+ def __init__(
108
+ self,
109
+ n_fft=1024,
110
+ hop_length=256,
111
+ win_length=1024,
112
+ n_mel_channels=100,
113
+ target_sample_rate=24_000,
114
+ mel_spec_type="vocos",
115
+ ):
116
+ super().__init__()
117
+ assert mel_spec_type in ["vocos", "bigvgan"], print("We only support two extract mel backend: vocos or bigvgan")
118
+
119
+ self.n_fft = n_fft
120
+ self.hop_length = hop_length
121
+ self.win_length = win_length
122
+ self.n_mel_channels = n_mel_channels
123
+ self.target_sample_rate = target_sample_rate
124
+
125
+ if mel_spec_type == "vocos":
126
+ self.extractor = get_vocos_mel_spectrogram
127
+ elif mel_spec_type == "bigvgan":
128
+ self.extractor = get_bigvgan_mel_spectrogram
129
+
130
+ self.register_buffer("dummy", torch.tensor(0), persistent=False)
131
+
132
+ def forward(self, wav):
133
+ if self.dummy.device != wav.device:
134
+ self.to(wav.device)
135
+
136
+ mel = self.extractor(
137
+ waveform=wav,
138
+ n_fft=self.n_fft,
139
+ n_mel_channels=self.n_mel_channels,
140
+ target_sample_rate=self.target_sample_rate,
141
+ hop_length=self.hop_length,
142
+ win_length=self.win_length,
143
+ )
144
+
145
+ return mel
146
+
147
+
148
+ # sinusoidal position embedding
149
+
150
+
151
+ class SinusPositionEmbedding(nn.Module):
152
+ def __init__(self, dim):
153
+ super().__init__()
154
+ self.dim = dim
155
+
156
+ def forward(self, x, scale=1000):
157
+ device = x.device
158
+ half_dim = self.dim // 2
159
+ emb = math.log(10000) / (half_dim - 1)
160
+ emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb)
161
+ emb = scale * x.unsqueeze(1) * emb.unsqueeze(0)
162
+ emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
163
+ return emb
164
+
165
+
166
+ # convolutional position embedding
167
+
168
+
169
+ class ConvPositionEmbedding(nn.Module):
170
+ def __init__(self, dim, kernel_size=31, groups=16):
171
+ super().__init__()
172
+ assert kernel_size % 2 != 0
173
+ self.conv1d = nn.Sequential(
174
+ nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
175
+ nn.Mish(),
176
+ nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
177
+ nn.Mish(),
178
+ )
179
+
180
+ def forward(self, x: float["b n d"], mask: bool["b n"] | None = None): # noqa: F722
181
+ if mask is not None:
182
+ mask = mask[..., None]
183
+ x = x.masked_fill(~mask, 0.0)
184
+
185
+ x = x.permute(0, 2, 1)
186
+ x = self.conv1d(x)
187
+ out = x.permute(0, 2, 1)
188
+
189
+ if mask is not None:
190
+ out = out.masked_fill(~mask, 0.0)
191
+
192
+ return out
193
+
194
+
195
+ # rotary positional embedding related
196
+
197
+
198
+ def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0, theta_rescale_factor=1.0):
199
+ # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
200
+ # has some connection to NTK literature
201
+ # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
202
+ # https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py
203
+ theta *= theta_rescale_factor ** (dim / (dim - 2))
204
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
205
+ t = torch.arange(end, device=freqs.device) # type: ignore
206
+ freqs = torch.outer(t, freqs).float() # type: ignore
207
+ freqs_cos = torch.cos(freqs) # real part
208
+ freqs_sin = torch.sin(freqs) # imaginary part
209
+ return torch.cat([freqs_cos, freqs_sin], dim=-1)
210
+
211
+
212
+ def get_pos_embed_indices(start, length, max_pos, scale=1.0):
213
+ # length = length if isinstance(length, int) else length.max()
214
+ scale = scale * torch.ones_like(start, dtype=torch.float32) # in case scale is a scalar
215
+ pos = (
216
+ start.unsqueeze(1)
217
+ + (torch.arange(length, device=start.device, dtype=torch.float32).unsqueeze(0) * scale.unsqueeze(1)).long()
218
+ )
219
+ # avoid extra long error.
220
+ pos = torch.where(pos < max_pos, pos, max_pos - 1)
221
+ return pos
222
+
223
+
224
+ # Global Response Normalization layer (Instance Normalization ?)
225
+
226
+
227
+ class GRN(nn.Module):
228
+ def __init__(self, dim):
229
+ super().__init__()
230
+ self.gamma = nn.Parameter(torch.zeros(1, 1, dim))
231
+ self.beta = nn.Parameter(torch.zeros(1, 1, dim))
232
+
233
+ def forward(self, x):
234
+ Gx = torch.norm(x, p=2, dim=1, keepdim=True)
235
+ Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6)
236
+ return self.gamma * (x * Nx) + self.beta + x
237
+
238
+
239
+ # ConvNeXt-V2 Block https://github.com/facebookresearch/ConvNeXt-V2/blob/main/models/convnextv2.py
240
+ # ref: https://github.com/bfs18/e2_tts/blob/main/rfwave/modules.py#L108
241
+
242
+
243
+ class ConvNeXtV2Block(nn.Module):
244
+ def __init__(
245
+ self,
246
+ dim: int,
247
+ intermediate_dim: int,
248
+ dilation: int = 1,
249
+ ):
250
+ super().__init__()
251
+ padding = (dilation * (7 - 1)) // 2
252
+ self.dwconv = nn.Conv1d(
253
+ dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation
254
+ ) # depthwise conv
255
+ self.norm = nn.LayerNorm(dim, eps=1e-6)
256
+ self.pwconv1 = nn.Linear(dim, intermediate_dim) # pointwise/1x1 convs, implemented with linear layers
257
+ self.act = nn.GELU()
258
+ self.grn = GRN(intermediate_dim)
259
+ self.pwconv2 = nn.Linear(intermediate_dim, dim)
260
+
261
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
262
+ residual = x
263
+ x = x.transpose(1, 2) # b n d -> b d n
264
+ x = self.dwconv(x)
265
+ x = x.transpose(1, 2) # b d n -> b n d
266
+ x = self.norm(x)
267
+ x = self.pwconv1(x)
268
+ x = self.act(x)
269
+ x = self.grn(x)
270
+ x = self.pwconv2(x)
271
+ return residual + x
272
+
273
+
274
+ # RMSNorm
275
+
276
+
277
+ class RMSNorm(nn.Module):
278
+ def __init__(self, dim: int, eps: float):
279
+ super().__init__()
280
+ self.eps = eps
281
+ self.weight = nn.Parameter(torch.ones(dim))
282
+ self.native_rms_norm = float(torch.__version__[:3]) >= 2.4
283
+
284
+ def forward(self, x):
285
+ if self.native_rms_norm:
286
+ if self.weight.dtype in [torch.float16, torch.bfloat16]:
287
+ x = x.to(self.weight.dtype)
288
+ x = F.rms_norm(x, normalized_shape=(x.shape[-1],), weight=self.weight, eps=self.eps)
289
+ else:
290
+ variance = x.to(torch.float32).pow(2).mean(-1, keepdim=True)
291
+ x = x * torch.rsqrt(variance + self.eps)
292
+ if self.weight.dtype in [torch.float16, torch.bfloat16]:
293
+ x = x.to(self.weight.dtype)
294
+ x = x * self.weight
295
+
296
+ return x
297
+
298
+
299
+ # AdaLayerNorm
300
+ # return with modulated x for attn input, and params for later mlp modulation
301
+
302
+
303
+ class AdaLayerNorm(nn.Module):
304
+ def __init__(self, dim):
305
+ super().__init__()
306
+
307
+ self.silu = nn.SiLU()
308
+ self.linear = nn.Linear(dim, dim * 6)
309
+
310
+ self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
311
+
312
+ def forward(self, x, emb=None):
313
+ emb = self.linear(self.silu(emb))
314
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = torch.chunk(emb, 6, dim=1)
315
+
316
+ x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
317
+ return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
318
+
319
+
320
+ # AdaLayerNorm for final layer
321
+ # return only with modulated x for attn input, cuz no more mlp modulation
322
+
323
+
324
+ class AdaLayerNorm_Final(nn.Module):
325
+ def __init__(self, dim):
326
+ super().__init__()
327
+
328
+ self.silu = nn.SiLU()
329
+ self.linear = nn.Linear(dim, dim * 2)
330
+
331
+ self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
332
+
333
+ def forward(self, x, emb):
334
+ emb = self.linear(self.silu(emb))
335
+ scale, shift = torch.chunk(emb, 2, dim=1)
336
+
337
+ x = self.norm(x) * (1 + scale)[:, None, :] + shift[:, None, :]
338
+ return x
339
+
340
+
341
+ # FeedForward
342
+
343
+
344
+ class FeedForward(nn.Module):
345
+ def __init__(self, dim, dim_out=None, mult=4, dropout=0.0, approximate: str = "none"):
346
+ super().__init__()
347
+ inner_dim = int(dim * mult)
348
+ dim_out = dim_out if dim_out is not None else dim
349
+
350
+ activation = nn.GELU(approximate=approximate)
351
+ project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation)
352
+ self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out))
353
+
354
+ def forward(self, x):
355
+ return self.ff(x)
356
+
357
+
358
+ # Attention with possible joint part
359
+ # modified from diffusers/src/diffusers/models/attention_processor.py
360
+
361
+
362
+ class Attention(nn.Module):
363
+ def __init__(
364
+ self,
365
+ processor: JointAttnProcessor | AttnProcessor | ChunkAttnProcessor | BlockAttnProcessor,
366
+ dim: int,
367
+ heads: int = 8,
368
+ dim_head: int = 64,
369
+ dropout: float = 0.0,
370
+ context_dim: Optional[int] = None, # if not None -> joint attention
371
+ context_pre_only: bool = False,
372
+ qk_norm: Optional[str] = None,
373
+ ):
374
+ super().__init__()
375
+
376
+ if not hasattr(F, "scaled_dot_product_attention"):
377
+ raise ImportError("Attention equires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
378
+
379
+ self.processor = processor
380
+
381
+ self.dim = dim
382
+ self.heads = heads
383
+ self.inner_dim = dim_head * heads
384
+ self.dropout = dropout
385
+
386
+ self.context_dim = context_dim
387
+ self.context_pre_only = context_pre_only
388
+
389
+ self.to_q = nn.Linear(dim, self.inner_dim)
390
+ self.to_k = nn.Linear(dim, self.inner_dim)
391
+ self.to_v = nn.Linear(dim, self.inner_dim)
392
+
393
+ if qk_norm is None:
394
+ self.q_norm = None
395
+ self.k_norm = None
396
+ elif qk_norm == "rms_norm":
397
+ self.q_norm = RMSNorm(dim_head, eps=1e-6)
398
+ self.k_norm = RMSNorm(dim_head, eps=1e-6)
399
+ else:
400
+ raise ValueError(f"Unimplemented qk_norm: {qk_norm}")
401
+
402
+ if self.context_dim is not None:
403
+ self.to_q_c = nn.Linear(context_dim, self.inner_dim)
404
+ self.to_k_c = nn.Linear(context_dim, self.inner_dim)
405
+ self.to_v_c = nn.Linear(context_dim, self.inner_dim)
406
+ if qk_norm is None:
407
+ self.c_q_norm = None
408
+ self.c_k_norm = None
409
+ elif qk_norm == "rms_norm":
410
+ self.c_q_norm = RMSNorm(dim_head, eps=1e-6)
411
+ self.c_k_norm = RMSNorm(dim_head, eps=1e-6)
412
+
413
+ self.to_out = nn.ModuleList([])
414
+ self.to_out.append(nn.Linear(self.inner_dim, dim))
415
+ self.to_out.append(nn.Dropout(dropout))
416
+
417
+ if self.context_dim is not None and not self.context_pre_only:
418
+ self.to_out_c = nn.Linear(self.inner_dim, context_dim)
419
+
420
+ def forward(
421
+ self,
422
+ x: float["b n d"], # noised input x # noqa: F722
423
+ c: float["b n d"] = None, # context c # noqa: F722
424
+ mask: bool["b n"] | None = None, # noqa: F722
425
+ rope=None, # rotary position embedding for x
426
+ c_rope=None, # rotary position embedding for c
427
+ is_inference=False,
428
+ kv_cache=None,
429
+ ) -> torch.Tensor:
430
+ if c is not None:
431
+ return self.processor(self, x, c=c, mask=mask, rope=rope, c_rope=c_rope, is_inference=is_inference, kv_cache=kv_cache)
432
+ else:
433
+ return self.processor(self, x, mask=mask, rope=rope, is_inference=is_inference, kv_cache=kv_cache)
434
+
435
+
436
+ # Attention processor
437
+
438
+
439
+ class AttnProcessor:
440
+ def __init__(
441
+ self,
442
+ pe_attn_head: int | None = None, # number of attention head to apply rope, None for all
443
+ ):
444
+
445
+ self.pe_attn_head = pe_attn_head
446
+
447
+ def __call__(
448
+ self,
449
+ attn: Attention,
450
+ x: float["b n d"], # noised input x # noqa: F722
451
+ mask: bool["b n"] | None = None, # noqa: F722
452
+ rope=None, # rotary position embedding
453
+ ) -> torch.FloatTensor:
454
+ batch_size = x.shape[0]
455
+
456
+ # `sample` projections
457
+ query = attn.to_q(x)
458
+ key = attn.to_k(x)
459
+ value = attn.to_v(x)
460
+
461
+ # attention
462
+ inner_dim = key.shape[-1]
463
+ head_dim = inner_dim // attn.heads
464
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
465
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
466
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
467
+
468
+ # qk norm
469
+ if attn.q_norm is not None:
470
+ query = attn.q_norm(query)
471
+ if attn.k_norm is not None:
472
+ key = attn.k_norm(key)
473
+
474
+ # apply rotary position embedding
475
+ if rope is not None:
476
+ freqs, xpos_scale = rope
477
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
478
+
479
+ if self.pe_attn_head is not None:
480
+ pn = self.pe_attn_head
481
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
482
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
483
+ else:
484
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
485
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
486
+
487
+ # mask. e.g. inference got a batch with different target durations, mask out the padding
488
+ if mask is not None:
489
+ attn_mask = mask
490
+ attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
491
+ attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
492
+ else:
493
+ attn_mask = None
494
+
495
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
496
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=True)
497
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
498
+ x = x.to(query.dtype)
499
+
500
+ # linear proj
501
+ x = attn.to_out[0](x)
502
+ # dropout
503
+ x = attn.to_out[1](x)
504
+
505
+ if mask is not None:
506
+ mask = mask.unsqueeze(-1)
507
+ x = x.masked_fill(~mask, 0.0)
508
+
509
+ return x
510
+
511
+ def scaled_dot_product_attention_only(query, key, value, attn_mask=None, dropout_p=0.0,
512
+ is_causal=False, scale=None, enable_gqa=False) -> torch.Tensor:
513
+
514
+ L, S = query.size(-2), key.size(-2)
515
+ B = query.size(0)
516
+ scale_factor = 1 / math.sqrt(query.size(-1)) if scale is None else scale
517
+ attn_bias = torch.zeros(B, 1, L, S, dtype=query.dtype, device=query.device)
518
+ if is_causal:
519
+ assert attn_mask is None
520
+ temp_mask = torch.ones(B, 1, L, S, dtype=torch.bool).tril(diagonal=0)
521
+ attn_bias.masked_fill_(temp_mask.logical_not(), float("-inf"))
522
+ attn_bias.to(query.dtype)
523
+
524
+ if attn_mask is not None:
525
+ if attn_mask.dtype == torch.bool:
526
+ attn_bias.masked_fill_(attn_mask[:,:,-attn_bias.shape[2]:,:].logical_not(), float("-inf"))
527
+ else:
528
+ attn_bias = attn_mask + attn_bias
529
+
530
+ if enable_gqa:
531
+ key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
532
+ value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
533
+
534
+ attn_weight = query @ key.transpose(-2, -1) * scale_factor
535
+ attn_weight += attn_bias
536
+ attn_weight = torch.softmax(attn_weight, dim=-1)
537
+ attn_weight = torch.dropout(attn_weight, dropout_p, train=True)
538
+ return attn_weight @ value
539
+
540
+ class BlockAttnProcessor:
541
+ def __init__(self, chunk_size: int, block_size: int, t_p: int, t_f: int, pe_attn_head: int | None = None,):
542
+ """
543
+ Args:
544
+ chunk_size (int): Number of tokens per chunk
545
+ block_size (int): Number of tokens per block.
546
+ t_p (int): Number of past chunks to attend to
547
+ t_f (int): Number of leading blocks in the future chunk to attend to
548
+ """
549
+ self.pe_attn_head = pe_attn_head
550
+ self.chunk_size = chunk_size
551
+ self.block_size = block_size
552
+ self.t_p = t_p
553
+ self.t_f = t_f
554
+
555
+ def try_cached_mask(self, seq_len, device):
556
+ idx = torch.arange(seq_len, device=device)
557
+ ci = idx // self.chunk_size
558
+
559
+ qi = ci[:, None]
560
+ kj = ci[None, :]
561
+
562
+ # Within the same chunk
563
+ same_chunk = (qi == kj)
564
+
565
+ # Previous t_p chunks
566
+ # prev_chunk = (kj == (qi - self.t_p))
567
+ prev_chunk = (kj >= (qi - self.t_p)) & (kj < qi)
568
+
569
+ # First t_f blocks of the next chunk
570
+ # First compute key(j)'s offset within its own chunk; this offset is bounded by block_size * t_f
571
+ offset_in_chunk = (idx % self.chunk_size)[None, :]
572
+ next_chunk_first_block = (kj == (qi + 1)) & (offset_in_chunk < self.block_size * self.t_f)
573
+
574
+ computed_mask = same_chunk | prev_chunk | next_chunk_first_block
575
+
576
+ return computed_mask
577
+
578
+ def __call__(
579
+ self,
580
+ attn: Attention,
581
+ x: float["b n d"], # noised input x # noqa: F722
582
+ mask: bool["b n"] | None = None, # noqa: F722
583
+ rope=None, # rotary position embedding
584
+ is_inference=False,
585
+ kv_cache=None,
586
+ ) -> torch.FloatTensor:
587
+
588
+ # batch_size, seq_len, _ = x.shape
589
+ batch_size = x.shape[0]
590
+ device = x.device
591
+
592
+ # 1. Compute query, key, value projections
593
+ query = attn.to_q(x) # Linear layer expands dims [b, n, d * heads]
594
+ key = attn.to_k(x)
595
+ value = attn.to_v(x) #torch.Size([batch, seq, 1024])
596
+
597
+ ## 3. Reshape query, key, value into multi-head format: [batch, heads, seq_len, head_dim] attention
598
+ inner_dim = key.shape[-1] # d * heads
599
+ head_dim = inner_dim // attn.heads
600
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) # [b, heads, n, d]
601
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
602
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
603
+
604
+ # qk norm
605
+ if attn.q_norm is not None:
606
+ query = attn.q_norm(query)
607
+ if attn.k_norm is not None:
608
+ key = attn.k_norm(key)
609
+
610
+ # kvcache
611
+ if kv_cache is None:
612
+ key_cache = None
613
+ value_cache = None
614
+ else:
615
+ key_cache, value_cache = kv_cache
616
+
617
+ if kv_cache is not None:
618
+ key = torch.cat([key_cache, key], dim=2)
619
+ if value_cache is not None:
620
+ value = torch.cat([value_cache, value], dim=2)
621
+
622
+ new_kv_cache = (key[:, :, :-self.block_size, :], value[:, :, :-self.block_size, :]) # Subsequent blocks are future info; do not cache them
623
+
624
+ batch_size, _, seq_len, _ = key.shape
625
+
626
+ # apply rotary position embedding
627
+ # Apply rotary position encoding to q and k
628
+ if rope is not None:
629
+ freqs, xpos_scale = rope
630
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
631
+
632
+ if self.pe_attn_head is not None:
633
+ pn = self.pe_attn_head
634
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
635
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
636
+ else:
637
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
638
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
639
+
640
+ # [b, i, j] = True means token i can attend to token j,
641
+ # [b, i, j] = False means token i cannot attend to token j,
642
+ computed_mask = self.try_cached_mask(seq_len,device).unsqueeze(0).expand(batch_size, -1, -1) # [b, seq_len, seq_len]
643
+
644
+ # 4.5 Expand the final mask to multi-head dimensions; shape becomes [batch, heads, seq_len, seq_len] #torch.Size([2, 16, 636, 636])
645
+ attn_mask = computed_mask.unsqueeze(1).expand(batch_size, 1, seq_len, seq_len)
646
+
647
+ # 5. Call PyTorch 2.0 scaled_dot_product_attention
648
+ attn_output = scaled_dot_product_attention_only(
649
+ query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False
650
+ )#attn_mask.to(query.dtype)
651
+ # attn_output shape: [batch, heads, seq_len, head_dim]
652
+
653
+ # 6. Restore shape; concatenate multi-head back to original dims [batch, seq_len, inner_dim]
654
+ attn_output = attn_output.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
655
+ attn_output = attn_output.to(query.dtype)
656
+
657
+ # 7. Pass through output projection and dropout
658
+ attn_output = attn.to_out[0](attn_output)
659
+ attn_output = attn.to_out[1](attn_output)
660
+
661
+ # Expand mask to [batch, seq_len, 1] and zero out the output accordingly
662
+ if mask is not None:
663
+ mask = mask.unsqueeze(-1)
664
+ attn_output = attn_output.masked_fill(~mask, 0.0)
665
+
666
+ return attn_output, new_kv_cache
667
+
668
+ class ChunkAttnProcessor:
669
+ def __init__(
670
+ self,
671
+ chunk_size: int,
672
+ pe_attn_head=None, # number of attention head to apply rope, None for all
673
+ ):
674
+ self.chunk_size = chunk_size
675
+ self.pe_attn_head = pe_attn_head
676
+
677
+ def __call__(
678
+ self,
679
+ attn: Attention,
680
+ x: float["b 2*N*chunk_size d"], # noised input x # noqa: F722
681
+ mask: bool["b n"] | None = None, # noqa: F722
682
+ rope=None, # rotary position embedding
683
+ is_inference=False,
684
+ ) -> torch.FloatTensor:
685
+ batch_size, seq_len, _ = x.shape
686
+
687
+ # `sample` projections
688
+ query = attn.to_q(x)
689
+ key = attn.to_k(x)
690
+ value = attn.to_v(x)
691
+
692
+ # attention
693
+ inner_dim = key.shape[-1]
694
+ head_dim = inner_dim // attn.heads
695
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
696
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
697
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
698
+
699
+ # qk norm
700
+ if attn.q_norm is not None:
701
+ query = attn.q_norm(query)
702
+ if attn.k_norm is not None:
703
+ key = attn.k_norm(key)
704
+
705
+ # apply rotary position embedding
706
+ if rope is not None:
707
+ freqs, xpos_scale = rope
708
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
709
+
710
+ if self.pe_attn_head is not None:
711
+ pn = self.pe_attn_head
712
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
713
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
714
+ else:
715
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
716
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
717
+
718
+ indices = torch.arange(seq_len, device=x.device)
719
+ chunk_indices = indices // self.chunk_size
720
+ N = int(seq_len / 2 / self.chunk_size)
721
+
722
+ # attn_mask_1 = chunk_indices.unsqueeze(0) <= chunk_indices.unsqueeze(1)
723
+ # attn_mask_2 = (chunk_indices.unsqueeze(0) + N < chunk_indices.unsqueeze(1)) | (chunk_indices.unsqueeze(0) == chunk_indices.unsqueeze(1))
724
+ # attn_mask = attn_mask_1 & attn_mask_2
725
+
726
+ # Generate left/right side identifiers (left side = first N*chunk_size frames)
727
+ is_right_side = indices >= (N * self.chunk_size)
728
+
729
+ # Left blocks (M_i) can attend to <= current block's left blocks
730
+ # left_mask = chunk_indices.unsqueeze(0) <= chunk_indices.unsqueeze(1)
731
+
732
+ # Right blocks (M'_i) can only attend to left clean blocks (all M_j, j < i) and itself
733
+ # right_mask = (
734
+ # (chunk_indices.unsqueeze(0) < (chunk_indices.unsqueeze(1) - N)) | # Access left clean blocks
735
+ # (chunk_indices.unsqueeze(0) == chunk_indices.unsqueeze(1)) # Access itself
736
+ # )
737
+
738
+ max_lookback = 5
739
+ num_cache_blocks = N # N
740
+
741
+ # 3. Expand dims for broadcasting
742
+ ci = chunk_indices.unsqueeze(0) # [L,1], row: chunk the query position belongs to ??? shouldn't it be [1, L]?
743
+ cj = chunk_indices.unsqueeze(1) # [1,L], col: chunk the key position belongs to ??? shouldn't it be [L, 1]?
744
+
745
+ # 4. Compute relative new block index: for block j, rel_j = cj - N; only rel_j >= 0 is a new block
746
+ rel_j = cj - num_cache_blocks # [1,L]
747
+
748
+ # 5. Self-attention: token can always attend to itself
749
+ mask_self = ci == cj # [L,L]
750
+
751
+ mask_cache = (
752
+ (rel_j >= 0) &
753
+ (ci < num_cache_blocks) &
754
+ (ci < rel_j) &
755
+ (ci >= rel_j - max_lookback)
756
+ )
757
+
758
+ right_mask = mask_self | mask_cache # [L,L] boolean matrix
759
+
760
+
761
+ lookback_k = 5 # Look back at most 5 previous blocks + self = 6 blocks total
762
+ block_diff = cj - ci
763
+ left_mask = (block_diff >= 0) & (block_diff <= lookback_k)
764
+
765
+ # Combine masks
766
+ if not is_inference:
767
+ # attn_mask = torch.where(
768
+ # is_right_side.unsqueeze(1), # Apply right_mask to right-side blocks
769
+ # right_mask,
770
+ # left_mask, # Apply left_mask to left-side blocks
771
+ # )
772
+ attn_mask = right_mask
773
+ else:
774
+ attn_mask = left_mask
775
+
776
+
777
+ if mask is not None:
778
+ pad_mask = mask.unsqueeze(1) & mask.unsqueeze(2) | torch.eye(seq_len, device=x.device).unsqueeze(0).bool()
779
+ attn_mask = attn_mask.unsqueeze(0).expand(batch_size, -1, -1) & pad_mask
780
+ else:
781
+ attn_mask = attn_mask.unsqueeze(0).expand(batch_size, -1, -1)
782
+
783
+ attn_mask = attn_mask.unsqueeze(1).expand(batch_size, 1, seq_len, seq_len)
784
+
785
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
786
+ x = scaled_dot_product_attention_only(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
787
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=True)
788
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
789
+ x = x.to(query.dtype)
790
+
791
+ # linear proj
792
+ x = attn.to_out[0](x)
793
+ # dropout
794
+ x = attn.to_out[1](x)
795
+
796
+ if mask is not None:
797
+ mask = mask.unsqueeze(-1)
798
+ x = x.masked_fill(~mask, 0.0)
799
+
800
+ return x
801
+
802
+ # Joint Attention processor for MM-DiT
803
+ # modified from diffusers/src/diffusers/models/attention_processor.py
804
+
805
+
806
+ class JointAttnProcessor:
807
+ def __init__(self):
808
+ pass
809
+
810
+ def __call__(
811
+ self,
812
+ attn: Attention,
813
+ x: float["b n d"], # noised input x # noqa: F722
814
+ c: float["b nt d"] = None, # context c, here text # noqa: F722
815
+ mask: bool["b n"] | None = None, # noqa: F722
816
+ rope=None, # rotary position embedding for x
817
+ c_rope=None, # rotary position embedding for c
818
+ ) -> torch.FloatTensor:
819
+ residual = x
820
+
821
+ batch_size = c.shape[0]
822
+
823
+ # `sample` projections
824
+ query = attn.to_q(x)
825
+ key = attn.to_k(x)
826
+ value = attn.to_v(x)
827
+
828
+ # `context` projections
829
+ c_query = attn.to_q_c(c)
830
+ c_key = attn.to_k_c(c)
831
+ c_value = attn.to_v_c(c)
832
+
833
+ # attention
834
+ inner_dim = key.shape[-1]
835
+ head_dim = inner_dim // attn.heads
836
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
837
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
838
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
839
+ c_query = c_query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
840
+ c_key = c_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
841
+ c_value = c_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
842
+
843
+ # qk norm
844
+ if attn.q_norm is not None:
845
+ query = attn.q_norm(query)
846
+ if attn.k_norm is not None:
847
+ key = attn.k_norm(key)
848
+ if attn.c_q_norm is not None:
849
+ c_query = attn.c_q_norm(c_query)
850
+ if attn.c_k_norm is not None:
851
+ c_key = attn.c_k_norm(c_key)
852
+
853
+ # apply rope for context and noised input independently
854
+ if rope is not None:
855
+ freqs, xpos_scale = rope
856
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
857
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
858
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
859
+ if c_rope is not None:
860
+ freqs, xpos_scale = c_rope
861
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
862
+ c_query = apply_rotary_pos_emb(c_query, freqs, q_xpos_scale)
863
+ c_key = apply_rotary_pos_emb(c_key, freqs, k_xpos_scale)
864
+
865
+ # joint attention
866
+ query = torch.cat([query, c_query], dim=2)
867
+ key = torch.cat([key, c_key], dim=2)
868
+ value = torch.cat([value, c_value], dim=2)
869
+
870
+ # mask. e.g. inference got a batch with different target durations, mask out the padding
871
+ if mask is not None:
872
+ attn_mask = F.pad(mask, (0, c.shape[1]), value=True) # no mask for c (text)
873
+ attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
874
+ attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
875
+ else:
876
+ attn_mask = None
877
+
878
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
879
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
880
+ x = x.to(query.dtype)
881
+
882
+ # Split the attention outputs.
883
+ x, c = (
884
+ x[:, : residual.shape[1]],
885
+ x[:, residual.shape[1] :],
886
+ )
887
+
888
+ # linear proj
889
+ x = attn.to_out[0](x)
890
+ # dropout
891
+ x = attn.to_out[1](x)
892
+ if not attn.context_pre_only:
893
+ c = attn.to_out_c(c)
894
+
895
+ if mask is not None:
896
+ mask = mask.unsqueeze(-1)
897
+ x = x.masked_fill(~mask, 0.0)
898
+ # c = c.masked_fill(~mask, 0.) # no mask for c (text)
899
+
900
+ return x, c
901
+
902
+
903
+ # DiT Block
904
+
905
+
906
+ class DiTBlock(nn.Module):
907
+ def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, qk_norm=None, pe_attn_head=None):
908
+ super().__init__()
909
+
910
+ self.attn_norm = AdaLayerNorm(dim)
911
+ self.attn = Attention(
912
+ processor=AttnProcessor(pe_attn_head=pe_attn_head),
913
+ dim=dim,
914
+ heads=heads,
915
+ dim_head=dim_head,
916
+ dropout=dropout,
917
+ qk_norm=qk_norm,
918
+ )
919
+
920
+ self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
921
+ self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
922
+
923
+ def forward(self, x, t, mask=None, rope=None): # x: noised input, t: time embedding
924
+ # pre-norm & modulation for attention input
925
+ norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t)
926
+
927
+ # attention
928
+ attn_output = self.attn(x=norm, mask=mask, rope=rope)
929
+
930
+ # process attention output for input x
931
+ x = x + gate_msa.unsqueeze(1) * attn_output
932
+
933
+ norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
934
+ ff_output = self.ff(norm)
935
+ x = x + gate_mlp.unsqueeze(1) * ff_output
936
+
937
+ return x
938
+
939
+
940
+ class ChunkDiTBlock(nn.Module):
941
+ def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, qk_norm=None, chunk_size=16, block_size=8, t_p=0, t_f=0, pe_attn_head=None):
942
+ super().__init__()
943
+
944
+ self.attn_norm = AdaLayerNorm(dim)
945
+ self.attn = Attention(
946
+ processor=BlockAttnProcessor(chunk_size=chunk_size, block_size=block_size, t_p=t_p, t_f=t_f, pe_attn_head=pe_attn_head),
947
+ dim=dim,
948
+ heads=heads,
949
+ dim_head=dim_head,
950
+ dropout=dropout,
951
+ qk_norm=qk_norm,
952
+ )
953
+
954
+ self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
955
+ self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
956
+
957
+ def forward(self, x, t, mask=None, rope=None, is_inference=False, kv_cache=None): # x: noised input, t: time embedding
958
+ # pre-norm & modulation for attention input
959
+ norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t)
960
+
961
+ # attention
962
+ attn_output, new_kv_cache = self.attn(x=norm, mask=mask, rope=rope, is_inference=is_inference, kv_cache=kv_cache)
963
+
964
+ # process attention output for input x
965
+ x = x + gate_msa.unsqueeze(1) * attn_output
966
+
967
+ norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
968
+ ff_output = self.ff(norm)
969
+ x = x + gate_mlp.unsqueeze(1) * ff_output
970
+
971
+ return x, new_kv_cache
972
+
973
+ # MMDiT Block https://arxiv.org/abs/2403.03206
974
+
975
+
976
+ class MMDiTBlock(nn.Module):
977
+ r"""
978
+ modified from diffusers/src/diffusers/models/attention.py
979
+
980
+ notes.
981
+ _c: context related. text, cond, etc. (left part in sd3 fig2.b)
982
+ _x: noised input related. (right part)
983
+ context_pre_only: last layer only do prenorm + modulation cuz no more ffn
984
+ """
985
+
986
+ def __init__(
987
+ self, dim, heads, dim_head, ff_mult=4, dropout=0.1, context_dim=None, context_pre_only=False, qk_norm=None
988
+ ):
989
+ super().__init__()
990
+ if context_dim is None:
991
+ context_dim = dim
992
+ self.context_pre_only = context_pre_only
993
+
994
+ self.attn_norm_c = AdaLayerNorm_Final(context_dim) if context_pre_only else AdaLayerNorm(context_dim)
995
+ self.attn_norm_x = AdaLayerNorm(dim)
996
+ self.attn = Attention(
997
+ processor=JointAttnProcessor(),
998
+ dim=dim,
999
+ heads=heads,
1000
+ dim_head=dim_head,
1001
+ dropout=dropout,
1002
+ context_dim=context_dim,
1003
+ context_pre_only=context_pre_only,
1004
+ qk_norm=qk_norm,
1005
+ )
1006
+
1007
+ if not context_pre_only:
1008
+ self.ff_norm_c = nn.LayerNorm(context_dim, elementwise_affine=False, eps=1e-6)
1009
+ self.ff_c = FeedForward(dim=context_dim, mult=ff_mult, dropout=dropout, approximate="tanh")
1010
+ else:
1011
+ self.ff_norm_c = None
1012
+ self.ff_c = None
1013
+ self.ff_norm_x = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
1014
+ self.ff_x = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
1015
+
1016
+ def forward(self, x, c, t, mask=None, rope=None, c_rope=None): # x: noised input, c: context, t: time embedding
1017
+ # pre-norm & modulation for attention input
1018
+ if self.context_pre_only:
1019
+ norm_c = self.attn_norm_c(c, t)
1020
+ else:
1021
+ norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, emb=t)
1022
+ norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, emb=t)
1023
+
1024
+ # attention
1025
+ x_attn_output, c_attn_output = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope)
1026
+
1027
+ # process attention output for context c
1028
+ if self.context_pre_only:
1029
+ c = None
1030
+ else: # if not last layer
1031
+ c = c + c_gate_msa.unsqueeze(1) * c_attn_output
1032
+
1033
+ norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
1034
+ c_ff_output = self.ff_c(norm_c)
1035
+ c = c + c_gate_mlp.unsqueeze(1) * c_ff_output
1036
+
1037
+ # process attention output for input x
1038
+ x = x + x_gate_msa.unsqueeze(1) * x_attn_output
1039
+
1040
+ norm_x = self.ff_norm_x(x) * (1 + x_scale_mlp[:, None]) + x_shift_mlp[:, None]
1041
+ x_ff_output = self.ff_x(norm_x)
1042
+ x = x + x_gate_mlp.unsqueeze(1) * x_ff_output
1043
+
1044
+ return c, x
1045
+
1046
+
1047
+ # time step conditioning embedding
1048
+
1049
+
1050
+ class TimestepEmbedding(nn.Module):
1051
+ def __init__(self, dim, freq_embed_dim=256):
1052
+ super().__init__()
1053
+ self.time_embed = SinusPositionEmbedding(freq_embed_dim)
1054
+ self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
1055
+
1056
+ def forward(self, timestep: float["b"]): # noqa: F821
1057
+ time_hidden = self.time_embed(timestep)
1058
+ time_hidden = time_hidden.to(timestep.dtype)
1059
+ time = self.time_mlp(time_hidden) # b d
1060
+ return time
silencio/meanvc2/modules.py ADDED
@@ -0,0 +1,1042 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/ASLP-lab/MeanVC2 (src/model/modules.py), Apache-2.0.
2
+ # Kept verbatim apart from import paths so Silencio runs without the original repo.
3
+ """
4
+ ein notation:
5
+ b - batch
6
+ n - sequence
7
+ nt - text sequence
8
+ nw - raw wave length
9
+ d - dimension
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import math
15
+ from typing import Optional
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+ import torchaudio
20
+ from librosa.filters import mel as librosa_mel_fn
21
+ from torch import nn
22
+ from x_transformers.x_transformers import apply_rotary_pos_emb
23
+
24
+
25
+ # raw wav to mel spec
26
+
27
+
28
+ mel_basis_cache = {}
29
+ hann_window_cache = {}
30
+
31
+
32
+ def get_bigvgan_mel_spectrogram(
33
+ waveform,
34
+ n_fft=1024,
35
+ n_mel_channels=100,
36
+ target_sample_rate=24000,
37
+ hop_length=256,
38
+ win_length=1024,
39
+ fmin=0,
40
+ fmax=None,
41
+ center=False,
42
+ ): # Copy from https://github.com/NVIDIA/BigVGAN/tree/main
43
+ device = waveform.device
44
+ key = f"{n_fft}_{n_mel_channels}_{target_sample_rate}_{hop_length}_{win_length}_{fmin}_{fmax}_{device}"
45
+
46
+ if key not in mel_basis_cache:
47
+ mel = librosa_mel_fn(sr=target_sample_rate, n_fft=n_fft, n_mels=n_mel_channels, fmin=fmin, fmax=fmax)
48
+ mel_basis_cache[key] = torch.from_numpy(mel).float().to(device) # TODO: why they need .float()?
49
+ hann_window_cache[key] = torch.hann_window(win_length).to(device)
50
+
51
+ mel_basis = mel_basis_cache[key]
52
+ hann_window = hann_window_cache[key]
53
+
54
+ padding = (n_fft - hop_length) // 2
55
+ waveform = torch.nn.functional.pad(waveform.unsqueeze(1), (padding, padding), mode="reflect").squeeze(1)
56
+
57
+ spec = torch.stft(
58
+ waveform,
59
+ n_fft,
60
+ hop_length=hop_length,
61
+ win_length=win_length,
62
+ window=hann_window,
63
+ center=center,
64
+ pad_mode="reflect",
65
+ normalized=False,
66
+ onesided=True,
67
+ return_complex=True,
68
+ )
69
+ spec = torch.sqrt(torch.view_as_real(spec).pow(2).sum(-1) + 1e-9)
70
+
71
+ mel_spec = torch.matmul(mel_basis, spec)
72
+ mel_spec = torch.log(torch.clamp(mel_spec, min=1e-5))
73
+
74
+ return mel_spec
75
+
76
+
77
+ def get_vocos_mel_spectrogram(
78
+ waveform,
79
+ n_fft=1024,
80
+ n_mel_channels=100,
81
+ target_sample_rate=24000,
82
+ hop_length=256,
83
+ win_length=1024,
84
+ ):
85
+ mel_stft = torchaudio.transforms.MelSpectrogram(
86
+ sample_rate=target_sample_rate,
87
+ n_fft=n_fft,
88
+ win_length=win_length,
89
+ hop_length=hop_length,
90
+ n_mels=n_mel_channels,
91
+ power=1,
92
+ center=True,
93
+ normalized=False,
94
+ norm=None,
95
+ ).to(waveform.device)
96
+ if len(waveform.shape) == 3:
97
+ waveform = waveform.squeeze(1) # 'b 1 nw -> b nw'
98
+
99
+ assert len(waveform.shape) == 2
100
+
101
+ mel = mel_stft(waveform)
102
+ mel = mel.clamp(min=1e-5).log()
103
+ return mel
104
+
105
+
106
+ class MelSpec(nn.Module):
107
+ def __init__(
108
+ self,
109
+ n_fft=1024,
110
+ hop_length=256,
111
+ win_length=1024,
112
+ n_mel_channels=100,
113
+ target_sample_rate=24_000,
114
+ mel_spec_type="vocos",
115
+ ):
116
+ super().__init__()
117
+ assert mel_spec_type in ["vocos", "bigvgan"], print("We only support two extract mel backend: vocos or bigvgan")
118
+
119
+ self.n_fft = n_fft
120
+ self.hop_length = hop_length
121
+ self.win_length = win_length
122
+ self.n_mel_channels = n_mel_channels
123
+ self.target_sample_rate = target_sample_rate
124
+
125
+ if mel_spec_type == "vocos":
126
+ self.extractor = get_vocos_mel_spectrogram
127
+ elif mel_spec_type == "bigvgan":
128
+ self.extractor = get_bigvgan_mel_spectrogram
129
+
130
+ self.register_buffer("dummy", torch.tensor(0), persistent=False)
131
+
132
+ def forward(self, wav):
133
+ if self.dummy.device != wav.device:
134
+ self.to(wav.device)
135
+
136
+ mel = self.extractor(
137
+ waveform=wav,
138
+ n_fft=self.n_fft,
139
+ n_mel_channels=self.n_mel_channels,
140
+ target_sample_rate=self.target_sample_rate,
141
+ hop_length=self.hop_length,
142
+ win_length=self.win_length,
143
+ )
144
+
145
+ return mel
146
+
147
+
148
+ # sinusoidal position embedding
149
+
150
+
151
+ class SinusPositionEmbedding(nn.Module):
152
+ def __init__(self, dim):
153
+ super().__init__()
154
+ self.dim = dim
155
+
156
+ def forward(self, x, scale=1000):
157
+ device = x.device
158
+ half_dim = self.dim // 2
159
+ emb = math.log(10000) / (half_dim - 1)
160
+ emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb)
161
+ emb = scale * x.unsqueeze(1) * emb.unsqueeze(0)
162
+ emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
163
+ return emb
164
+
165
+
166
+ # convolutional position embedding
167
+
168
+
169
+ class ConvPositionEmbedding(nn.Module):
170
+ def __init__(self, dim, kernel_size=31, groups=16):
171
+ super().__init__()
172
+ assert kernel_size % 2 != 0
173
+ self.conv1d = nn.Sequential(
174
+ nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
175
+ nn.Mish(),
176
+ nn.Conv1d(dim, dim, kernel_size, groups=groups, padding=kernel_size // 2),
177
+ nn.Mish(),
178
+ )
179
+
180
+ def forward(self, x: float["b n d"], mask: bool["b n"] | None = None): # noqa: F722
181
+ if mask is not None:
182
+ mask = mask[..., None]
183
+ x = x.masked_fill(~mask, 0.0)
184
+
185
+ x = x.permute(0, 2, 1)
186
+ x = self.conv1d(x)
187
+ out = x.permute(0, 2, 1)
188
+
189
+ if mask is not None:
190
+ out = out.masked_fill(~mask, 0.0)
191
+
192
+ return out
193
+
194
+
195
+ # rotary positional embedding related
196
+
197
+
198
+ def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0, theta_rescale_factor=1.0):
199
+ # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
200
+ # has some connection to NTK literature
201
+ # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
202
+ # https://github.com/lucidrains/rotary-embedding-torch/blob/main/rotary_embedding_torch/rotary_embedding_torch.py
203
+ theta *= theta_rescale_factor ** (dim / (dim - 2))
204
+ freqs = 1.0 / (theta ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim))
205
+ t = torch.arange(end, device=freqs.device) # type: ignore
206
+ freqs = torch.outer(t, freqs).float() # type: ignore
207
+ freqs_cos = torch.cos(freqs) # real part
208
+ freqs_sin = torch.sin(freqs) # imaginary part
209
+ return torch.cat([freqs_cos, freqs_sin], dim=-1)
210
+
211
+
212
+ def get_pos_embed_indices(start, length, max_pos, scale=1.0):
213
+ # length = length if isinstance(length, int) else length.max()
214
+ scale = scale * torch.ones_like(start, dtype=torch.float32) # in case scale is a scalar
215
+ pos = (
216
+ start.unsqueeze(1)
217
+ + (torch.arange(length, device=start.device, dtype=torch.float32).unsqueeze(0) * scale.unsqueeze(1)).long()
218
+ )
219
+ # avoid extra long error.
220
+ pos = torch.where(pos < max_pos, pos, max_pos - 1)
221
+ return pos
222
+
223
+
224
+ # Global Response Normalization layer (Instance Normalization ?)
225
+
226
+
227
+ class GRN(nn.Module):
228
+ def __init__(self, dim):
229
+ super().__init__()
230
+ self.gamma = nn.Parameter(torch.zeros(1, 1, dim))
231
+ self.beta = nn.Parameter(torch.zeros(1, 1, dim))
232
+
233
+ def forward(self, x):
234
+ Gx = torch.norm(x, p=2, dim=1, keepdim=True)
235
+ Nx = Gx / (Gx.mean(dim=-1, keepdim=True) + 1e-6)
236
+ return self.gamma * (x * Nx) + self.beta + x
237
+
238
+
239
+ # ConvNeXt-V2 Block https://github.com/facebookresearch/ConvNeXt-V2/blob/main/models/convnextv2.py
240
+ # ref: https://github.com/bfs18/e2_tts/blob/main/rfwave/modules.py#L108
241
+
242
+
243
+ class ConvNeXtV2Block(nn.Module):
244
+ def __init__(
245
+ self,
246
+ dim: int,
247
+ intermediate_dim: int,
248
+ dilation: int = 1,
249
+ ):
250
+ super().__init__()
251
+ padding = (dilation * (7 - 1)) // 2
252
+ self.dwconv = nn.Conv1d(
253
+ dim, dim, kernel_size=7, padding=padding, groups=dim, dilation=dilation
254
+ ) # depthwise conv
255
+ self.norm = nn.LayerNorm(dim, eps=1e-6)
256
+ self.pwconv1 = nn.Linear(dim, intermediate_dim) # pointwise/1x1 convs, implemented with linear layers
257
+ self.act = nn.GELU()
258
+ self.grn = GRN(intermediate_dim)
259
+ self.pwconv2 = nn.Linear(intermediate_dim, dim)
260
+
261
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
262
+ residual = x
263
+ x = x.transpose(1, 2) # b n d -> b d n
264
+ x = self.dwconv(x)
265
+ x = x.transpose(1, 2) # b d n -> b n d
266
+ x = self.norm(x)
267
+ x = self.pwconv1(x)
268
+ x = self.act(x)
269
+ x = self.grn(x)
270
+ x = self.pwconv2(x)
271
+ return residual + x
272
+
273
+
274
+ # RMSNorm
275
+
276
+
277
+ class RMSNorm(nn.Module):
278
+ def __init__(self, dim: int, eps: float):
279
+ super().__init__()
280
+ self.eps = eps
281
+ self.weight = nn.Parameter(torch.ones(dim))
282
+ self.native_rms_norm = float(torch.__version__[:3]) >= 2.4
283
+
284
+ def forward(self, x):
285
+ if self.native_rms_norm:
286
+ if self.weight.dtype in [torch.float16, torch.bfloat16]:
287
+ x = x.to(self.weight.dtype)
288
+ x = F.rms_norm(x, normalized_shape=(x.shape[-1],), weight=self.weight, eps=self.eps)
289
+ else:
290
+ variance = x.to(torch.float32).pow(2).mean(-1, keepdim=True)
291
+ x = x * torch.rsqrt(variance + self.eps)
292
+ if self.weight.dtype in [torch.float16, torch.bfloat16]:
293
+ x = x.to(self.weight.dtype)
294
+ x = x * self.weight
295
+
296
+ return x
297
+
298
+
299
+ # AdaLayerNorm
300
+ # return with modulated x for attn input, and params for later mlp modulation
301
+
302
+
303
+ class AdaLayerNorm(nn.Module):
304
+ def __init__(self, dim):
305
+ super().__init__()
306
+
307
+ self.silu = nn.SiLU()
308
+ self.linear = nn.Linear(dim, dim * 6)
309
+
310
+ self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
311
+
312
+ def forward(self, x, emb=None):
313
+ emb = self.linear(self.silu(emb))
314
+ shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = torch.chunk(emb, 6, dim=1)
315
+
316
+ x = self.norm(x) * (1 + scale_msa[:, None]) + shift_msa[:, None]
317
+ return x, gate_msa, shift_mlp, scale_mlp, gate_mlp
318
+
319
+
320
+ # AdaLayerNorm for final layer
321
+ # return only with modulated x for attn input, cuz no more mlp modulation
322
+
323
+
324
+ class AdaLayerNorm_Final(nn.Module):
325
+ def __init__(self, dim):
326
+ super().__init__()
327
+
328
+ self.silu = nn.SiLU()
329
+ self.linear = nn.Linear(dim, dim * 2)
330
+
331
+ self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
332
+
333
+ def forward(self, x, emb):
334
+ emb = self.linear(self.silu(emb))
335
+ scale, shift = torch.chunk(emb, 2, dim=1)
336
+
337
+ x = self.norm(x) * (1 + scale)[:, None, :] + shift[:, None, :]
338
+ return x
339
+
340
+
341
+ # FeedForward
342
+
343
+
344
+ class FeedForward(nn.Module):
345
+ def __init__(self, dim, dim_out=None, mult=4, dropout=0.0, approximate: str = "none"):
346
+ super().__init__()
347
+ inner_dim = int(dim * mult)
348
+ dim_out = dim_out if dim_out is not None else dim
349
+
350
+ activation = nn.GELU(approximate=approximate)
351
+ project_in = nn.Sequential(nn.Linear(dim, inner_dim), activation)
352
+ self.ff = nn.Sequential(project_in, nn.Dropout(dropout), nn.Linear(inner_dim, dim_out))
353
+
354
+ def forward(self, x):
355
+ return self.ff(x)
356
+
357
+
358
+ # Attention with possible joint part
359
+ # modified from diffusers/src/diffusers/models/attention_processor.py
360
+
361
+
362
+ class Attention(nn.Module):
363
+ def __init__(
364
+ self,
365
+ processor: JointAttnProcessor | AttnProcessor | ChunkAttnProcessor | BlockAttnProcessor,
366
+ dim: int,
367
+ heads: int = 8,
368
+ dim_head: int = 64,
369
+ dropout: float = 0.0,
370
+ context_dim: Optional[int] = None, # if not None -> joint attention
371
+ context_pre_only: bool = False,
372
+ qk_norm: Optional[str] = None,
373
+ ):
374
+ super().__init__()
375
+
376
+ if not hasattr(F, "scaled_dot_product_attention"):
377
+ raise ImportError("Attention equires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
378
+
379
+ self.processor = processor
380
+
381
+ self.dim = dim
382
+ self.heads = heads
383
+ self.inner_dim = dim_head * heads
384
+ self.dropout = dropout
385
+
386
+ self.context_dim = context_dim
387
+ self.context_pre_only = context_pre_only
388
+
389
+ self.to_q = nn.Linear(dim, self.inner_dim)
390
+ self.to_k = nn.Linear(dim, self.inner_dim)
391
+ self.to_v = nn.Linear(dim, self.inner_dim)
392
+
393
+ if qk_norm is None:
394
+ self.q_norm = None
395
+ self.k_norm = None
396
+ elif qk_norm == "rms_norm":
397
+ self.q_norm = RMSNorm(dim_head, eps=1e-6)
398
+ self.k_norm = RMSNorm(dim_head, eps=1e-6)
399
+ else:
400
+ raise ValueError(f"Unimplemented qk_norm: {qk_norm}")
401
+
402
+ if self.context_dim is not None:
403
+ self.to_q_c = nn.Linear(context_dim, self.inner_dim)
404
+ self.to_k_c = nn.Linear(context_dim, self.inner_dim)
405
+ self.to_v_c = nn.Linear(context_dim, self.inner_dim)
406
+ if qk_norm is None:
407
+ self.c_q_norm = None
408
+ self.c_k_norm = None
409
+ elif qk_norm == "rms_norm":
410
+ self.c_q_norm = RMSNorm(dim_head, eps=1e-6)
411
+ self.c_k_norm = RMSNorm(dim_head, eps=1e-6)
412
+
413
+ self.to_out = nn.ModuleList([])
414
+ self.to_out.append(nn.Linear(self.inner_dim, dim))
415
+ self.to_out.append(nn.Dropout(dropout))
416
+
417
+ if self.context_dim is not None and not self.context_pre_only:
418
+ self.to_out_c = nn.Linear(self.inner_dim, context_dim)
419
+
420
+ def forward(
421
+ self,
422
+ x: float["b n d"], # noised input x # noqa: F722
423
+ c: float["b n d"] = None, # context c # noqa: F722
424
+ mask: bool["b n"] | None = None, # noqa: F722
425
+ rope=None, # rotary position embedding for x
426
+ c_rope=None, # rotary position embedding for c
427
+ is_inference=False,
428
+ ) -> torch.Tensor:
429
+ if c is not None:
430
+ return self.processor(self, x, c=c, mask=mask, rope=rope, c_rope=c_rope, is_inference=is_inference)
431
+ else:
432
+ return self.processor(self, x, mask=mask, rope=rope, is_inference=is_inference)
433
+
434
+
435
+ # Attention processor
436
+
437
+
438
+ class AttnProcessor:
439
+ def __init__(
440
+ self,
441
+ pe_attn_head: int | None = None, # number of attention head to apply rope, None for all
442
+ ):
443
+
444
+ self.pe_attn_head = pe_attn_head
445
+
446
+ def __call__(
447
+ self,
448
+ attn: Attention,
449
+ x: float["b n d"], # noised input x # noqa: F722
450
+ mask: bool["b n"] | None = None, # noqa: F722
451
+ rope=None, # rotary position embedding
452
+ ) -> torch.FloatTensor:
453
+ batch_size = x.shape[0]
454
+
455
+ # `sample` projections
456
+ query = attn.to_q(x)
457
+ key = attn.to_k(x)
458
+ value = attn.to_v(x)
459
+
460
+ # attention
461
+ inner_dim = key.shape[-1]
462
+ head_dim = inner_dim // attn.heads
463
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
464
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
465
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
466
+
467
+ # qk norm
468
+ if attn.q_norm is not None:
469
+ query = attn.q_norm(query)
470
+ if attn.k_norm is not None:
471
+ key = attn.k_norm(key)
472
+
473
+ # apply rotary position embedding
474
+ if rope is not None:
475
+ freqs, xpos_scale = rope
476
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
477
+
478
+ if self.pe_attn_head is not None:
479
+ pn = self.pe_attn_head
480
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
481
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
482
+ else:
483
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
484
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
485
+
486
+ # mask. e.g. inference got a batch with different target durations, mask out the padding
487
+ if mask is not None:
488
+ attn_mask = mask
489
+ attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
490
+ attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
491
+ else:
492
+ attn_mask = None
493
+
494
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
495
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=True)
496
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
497
+ x = x.to(query.dtype)
498
+
499
+ # linear proj
500
+ x = attn.to_out[0](x)
501
+ # dropout
502
+ x = attn.to_out[1](x)
503
+
504
+ if mask is not None:
505
+ mask = mask.unsqueeze(-1)
506
+ x = x.masked_fill(~mask, 0.0)
507
+
508
+ return x
509
+
510
+ def scaled_dot_product_attention_only(query, key, value, attn_mask=None, dropout_p=0.0,
511
+ is_causal=False, scale=None, enable_gqa=False) -> torch.Tensor:
512
+
513
+ L, S = query.size(-2), key.size(-2)
514
+ B = query.size(0)
515
+ scale_factor = 1 / math.sqrt(query.size(-1)) if scale is None else scale
516
+ attn_bias = torch.zeros(B, 1, L, S, dtype=query.dtype, device=query.device)
517
+ if is_causal:
518
+ assert attn_mask is None
519
+ temp_mask = torch.ones(B, 1, L, S, dtype=torch.bool).tril(diagonal=0)
520
+ attn_bias.masked_fill_(temp_mask.logical_not(), float("-inf"))
521
+ attn_bias.to(query.dtype)
522
+
523
+ if attn_mask is not None:
524
+ if attn_mask.dtype == torch.bool:
525
+ attn_bias.masked_fill_(attn_mask.logical_not(), float("-inf"))
526
+ else:
527
+ attn_bias = attn_mask + attn_bias
528
+
529
+ if enable_gqa:
530
+ key = key.repeat_interleave(query.size(-3)//key.size(-3), -3)
531
+ value = value.repeat_interleave(query.size(-3)//value.size(-3), -3)
532
+
533
+ attn_weight = query @ key.transpose(-2, -1) * scale_factor
534
+ attn_weight += attn_bias
535
+ attn_weight = torch.softmax(attn_weight, dim=-1)
536
+ attn_weight = torch.dropout(attn_weight, dropout_p, train=True)
537
+ return attn_weight @ value
538
+
539
+ class BlockAttnProcessor:
540
+ def __init__(self, chunk_size: int, block_size: int, t_p: int, t_f: int, pe_attn_head: int | None = None,):
541
+ """
542
+ Args:
543
+ chunk_size (int): Number of tokens per chunk
544
+ block_size (int): Number of tokens per block.
545
+ t_p (int): Number of past chunks to attend to
546
+ t_f (int): Number of leading blocks in the future chunk to attend to
547
+ """
548
+ self.pe_attn_head = pe_attn_head
549
+ self.chunk_size = chunk_size
550
+ self.block_size = block_size
551
+ self.t_p = t_p
552
+ self.t_f = t_f
553
+
554
+ def try_cached_mask(self, seq_len, device):
555
+ idx = torch.arange(seq_len, device=device)
556
+ ci = idx // self.chunk_size
557
+
558
+ qi = ci[:, None]
559
+ kj = ci[None, :]
560
+
561
+ # Within the same chunk
562
+ same_chunk = (qi == kj)
563
+
564
+ # Previous t_p chunks
565
+ # prev_chunk = (kj == (qi - self.t_p))
566
+ prev_chunk = (kj >= (qi - self.t_p)) & (kj < qi)
567
+
568
+ # First t_f blocks of the next chunk
569
+ # First compute key(j)'s offset within its own chunk; this offset is bounded by block_size * t_f
570
+ offset_in_chunk = (idx % self.chunk_size)[None, :]
571
+ next_chunk_first_block = (kj == (qi + 1)) & (offset_in_chunk < self.block_size * self.t_f)
572
+
573
+ computed_mask = same_chunk | prev_chunk | next_chunk_first_block
574
+
575
+ return computed_mask
576
+
577
+ def __call__(
578
+ self,
579
+ attn: Attention,
580
+ x: float["b n d"], # noised input x # noqa: F722
581
+ mask: bool["b n"] | None = None, # noqa: F722
582
+ rope=None, # rotary position embedding
583
+ is_inference=False,
584
+ ) -> torch.FloatTensor:
585
+
586
+ batch_size, seq_len, _ = x.shape
587
+ device = x.device
588
+
589
+ # 1. Compute query, key, value projections
590
+ query = attn.to_q(x) # Linear layer expands dims [b, n, d * heads]
591
+ key = attn.to_k(x)
592
+ value = attn.to_v(x) #torch.Size([batch, seq, 1024])
593
+
594
+ ## 3. Reshape query, key, value into multi-head format: [batch, heads, seq_len, head_dim] attention
595
+ inner_dim = key.shape[-1] # d * heads
596
+ head_dim = inner_dim // attn.heads
597
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2) # [b, heads, n, d]
598
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
599
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
600
+
601
+ # qk norm
602
+ if attn.q_norm is not None:
603
+ query = attn.q_norm(query)
604
+ if attn.k_norm is not None:
605
+ key = attn.k_norm(key)
606
+
607
+
608
+ # apply rotary position embedding
609
+ # Apply rotary position encoding to q and k
610
+ if rope is not None:
611
+ freqs, xpos_scale = rope
612
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
613
+
614
+ if self.pe_attn_head is not None:
615
+ pn = self.pe_attn_head
616
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
617
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
618
+ else:
619
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
620
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
621
+
622
+ # [b, i, j] = True means token i can attend to token j,
623
+ # [b, i, j] = False means token i cannot attend to token j,
624
+ computed_mask = self.try_cached_mask(seq_len,device).unsqueeze(0).expand(batch_size, -1, -1) # [b, seq_len, seq_len]
625
+
626
+ # 4.5 Expand the final mask to multi-head dimensions; shape becomes [batch, heads, seq_len, seq_len] #torch.Size([2, 16, 636, 636])
627
+ attn_mask = computed_mask.unsqueeze(1).expand(batch_size, 1, seq_len, seq_len)
628
+
629
+ # 5. Call PyTorch 2.0 scaled_dot_product_attention
630
+ attn_output = scaled_dot_product_attention_only(
631
+ query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False
632
+ )#attn_mask.to(query.dtype)
633
+ # attn_output shape: [batch, heads, seq_len, head_dim]
634
+
635
+ # 6. Restore shape; concatenate multi-head back to original dims [batch, seq_len, inner_dim]
636
+ attn_output = attn_output.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
637
+ attn_output = attn_output.to(query.dtype)
638
+
639
+ # 7. Pass through output projection and dropout
640
+ attn_output = attn.to_out[0](attn_output)
641
+ attn_output = attn.to_out[1](attn_output)
642
+
643
+ # Expand mask to [batch, seq_len, 1] and zero out the output accordingly
644
+ if mask is not None:
645
+ mask = mask.unsqueeze(-1)
646
+ attn_output = attn_output.masked_fill(~mask, 0.0)
647
+
648
+ return attn_output
649
+
650
+ class ChunkAttnProcessor:
651
+ def __init__(
652
+ self,
653
+ chunk_size: int,
654
+ pe_attn_head=None, # number of attention head to apply rope, None for all
655
+ ):
656
+ self.chunk_size = chunk_size
657
+ self.pe_attn_head = pe_attn_head
658
+
659
+ def __call__(
660
+ self,
661
+ attn: Attention,
662
+ x: float["b 2*N*chunk_size d"], # noised input x # noqa: F722
663
+ mask: bool["b n"] | None = None, # noqa: F722
664
+ rope=None, # rotary position embedding
665
+ is_inference=False,
666
+ ) -> torch.FloatTensor:
667
+ batch_size, seq_len, _ = x.shape
668
+
669
+ # `sample` projections
670
+ query = attn.to_q(x)
671
+ key = attn.to_k(x)
672
+ value = attn.to_v(x)
673
+
674
+ # attention
675
+ inner_dim = key.shape[-1]
676
+ head_dim = inner_dim // attn.heads
677
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
678
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
679
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
680
+
681
+ # qk norm
682
+ if attn.q_norm is not None:
683
+ query = attn.q_norm(query)
684
+ if attn.k_norm is not None:
685
+ key = attn.k_norm(key)
686
+
687
+ # apply rotary position embedding
688
+ if rope is not None:
689
+ freqs, xpos_scale = rope
690
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
691
+
692
+ if self.pe_attn_head is not None:
693
+ pn = self.pe_attn_head
694
+ query[:, :pn, :, :] = apply_rotary_pos_emb(query[:, :pn, :, :], freqs, q_xpos_scale)
695
+ key[:, :pn, :, :] = apply_rotary_pos_emb(key[:, :pn, :, :], freqs, k_xpos_scale)
696
+ else:
697
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
698
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
699
+
700
+ indices = torch.arange(seq_len, device=x.device)
701
+ chunk_indices = indices // self.chunk_size
702
+ N = int(seq_len / 2 / self.chunk_size)
703
+
704
+ # attn_mask_1 = chunk_indices.unsqueeze(0) <= chunk_indices.unsqueeze(1)
705
+ # attn_mask_2 = (chunk_indices.unsqueeze(0) + N < chunk_indices.unsqueeze(1)) | (chunk_indices.unsqueeze(0) == chunk_indices.unsqueeze(1))
706
+ # attn_mask = attn_mask_1 & attn_mask_2
707
+
708
+ # Generate left/right side identifiers (left side = first N*chunk_size frames)
709
+ is_right_side = indices >= (N * self.chunk_size)
710
+
711
+ # Left blocks (M_i) can attend to <= current block's left blocks
712
+ # left_mask = chunk_indices.unsqueeze(0) <= chunk_indices.unsqueeze(1)
713
+
714
+ # Right blocks (M'_i) can only attend to left clean blocks (all M_j, j < i) and itself
715
+ # right_mask = (
716
+ # (chunk_indices.unsqueeze(0) < (chunk_indices.unsqueeze(1) - N)) | # Access left clean blocks
717
+ # (chunk_indices.unsqueeze(0) == chunk_indices.unsqueeze(1)) # Access itself
718
+ # )
719
+
720
+ max_lookback = 5
721
+ num_cache_blocks = N # N
722
+
723
+ # 3. Expand dims for broadcasting
724
+ ci = chunk_indices.unsqueeze(0) # [L,1], row: chunk the query position belongs to ??? shouldn't it be [1, L]?
725
+ cj = chunk_indices.unsqueeze(1) # [1,L], col: chunk the key position belongs to ??? shouldn't it be [L, 1]?
726
+
727
+ # 4. Compute relative new block index: for block j, rel_j = cj - N; only rel_j >= 0 is a new block
728
+ rel_j = cj - num_cache_blocks # [1,L]
729
+
730
+ # 5. Self-attention: token can always attend to itself
731
+ mask_self = ci == cj # [L,L]
732
+
733
+ mask_cache = (
734
+ (rel_j >= 0) &
735
+ (ci < num_cache_blocks) &
736
+ (ci < rel_j) &
737
+ (ci >= rel_j - max_lookback)
738
+ )
739
+
740
+ right_mask = mask_self | mask_cache # [L,L] boolean matrix
741
+
742
+
743
+ lookback_k = 5 # Look back at most 5 previous blocks + self = 6 blocks total
744
+ block_diff = cj - ci
745
+ left_mask = (block_diff >= 0) & (block_diff <= lookback_k)
746
+
747
+ # Combine masks
748
+ if not is_inference:
749
+ # attn_mask = torch.where(
750
+ # is_right_side.unsqueeze(1), # Apply right_mask to right-side blocks
751
+ # right_mask,
752
+ # left_mask, # Apply left_mask to left-side blocks
753
+ # )
754
+ attn_mask = right_mask
755
+ else:
756
+ attn_mask = left_mask
757
+
758
+
759
+ if mask is not None:
760
+ pad_mask = mask.unsqueeze(1) & mask.unsqueeze(2) | torch.eye(seq_len, device=x.device).unsqueeze(0).bool()
761
+ attn_mask = attn_mask.unsqueeze(0).expand(batch_size, -1, -1) & pad_mask
762
+ else:
763
+ attn_mask = attn_mask.unsqueeze(0).expand(batch_size, -1, -1)
764
+
765
+ attn_mask = attn_mask.unsqueeze(1).expand(batch_size, 1, seq_len, seq_len)
766
+
767
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
768
+ x = scaled_dot_product_attention_only(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
769
+ # x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=True)
770
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
771
+ x = x.to(query.dtype)
772
+
773
+ # linear proj
774
+ x = attn.to_out[0](x)
775
+ # dropout
776
+ x = attn.to_out[1](x)
777
+
778
+ if mask is not None:
779
+ mask = mask.unsqueeze(-1)
780
+ x = x.masked_fill(~mask, 0.0)
781
+
782
+ return x
783
+
784
+ # Joint Attention processor for MM-DiT
785
+ # modified from diffusers/src/diffusers/models/attention_processor.py
786
+
787
+
788
+ class JointAttnProcessor:
789
+ def __init__(self):
790
+ pass
791
+
792
+ def __call__(
793
+ self,
794
+ attn: Attention,
795
+ x: float["b n d"], # noised input x # noqa: F722
796
+ c: float["b nt d"] = None, # context c, here text # noqa: F722
797
+ mask: bool["b n"] | None = None, # noqa: F722
798
+ rope=None, # rotary position embedding for x
799
+ c_rope=None, # rotary position embedding for c
800
+ ) -> torch.FloatTensor:
801
+ residual = x
802
+
803
+ batch_size = c.shape[0]
804
+
805
+ # `sample` projections
806
+ query = attn.to_q(x)
807
+ key = attn.to_k(x)
808
+ value = attn.to_v(x)
809
+
810
+ # `context` projections
811
+ c_query = attn.to_q_c(c)
812
+ c_key = attn.to_k_c(c)
813
+ c_value = attn.to_v_c(c)
814
+
815
+ # attention
816
+ inner_dim = key.shape[-1]
817
+ head_dim = inner_dim // attn.heads
818
+ query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
819
+ key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
820
+ value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
821
+ c_query = c_query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
822
+ c_key = c_key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
823
+ c_value = c_value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
824
+
825
+ # qk norm
826
+ if attn.q_norm is not None:
827
+ query = attn.q_norm(query)
828
+ if attn.k_norm is not None:
829
+ key = attn.k_norm(key)
830
+ if attn.c_q_norm is not None:
831
+ c_query = attn.c_q_norm(c_query)
832
+ if attn.c_k_norm is not None:
833
+ c_key = attn.c_k_norm(c_key)
834
+
835
+ # apply rope for context and noised input independently
836
+ if rope is not None:
837
+ freqs, xpos_scale = rope
838
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
839
+ query = apply_rotary_pos_emb(query, freqs, q_xpos_scale)
840
+ key = apply_rotary_pos_emb(key, freqs, k_xpos_scale)
841
+ if c_rope is not None:
842
+ freqs, xpos_scale = c_rope
843
+ q_xpos_scale, k_xpos_scale = (xpos_scale, xpos_scale**-1.0) if xpos_scale is not None else (1.0, 1.0)
844
+ c_query = apply_rotary_pos_emb(c_query, freqs, q_xpos_scale)
845
+ c_key = apply_rotary_pos_emb(c_key, freqs, k_xpos_scale)
846
+
847
+ # joint attention
848
+ query = torch.cat([query, c_query], dim=2)
849
+ key = torch.cat([key, c_key], dim=2)
850
+ value = torch.cat([value, c_value], dim=2)
851
+
852
+ # mask. e.g. inference got a batch with different target durations, mask out the padding
853
+ if mask is not None:
854
+ attn_mask = F.pad(mask, (0, c.shape[1]), value=True) # no mask for c (text)
855
+ attn_mask = attn_mask.unsqueeze(1).unsqueeze(1) # 'b n -> b 1 1 n'
856
+ attn_mask = attn_mask.expand(batch_size, attn.heads, query.shape[-2], key.shape[-2])
857
+ else:
858
+ attn_mask = None
859
+
860
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
861
+ x = x.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
862
+ x = x.to(query.dtype)
863
+
864
+ # Split the attention outputs.
865
+ x, c = (
866
+ x[:, : residual.shape[1]],
867
+ x[:, residual.shape[1] :],
868
+ )
869
+
870
+ # linear proj
871
+ x = attn.to_out[0](x)
872
+ # dropout
873
+ x = attn.to_out[1](x)
874
+ if not attn.context_pre_only:
875
+ c = attn.to_out_c(c)
876
+
877
+ if mask is not None:
878
+ mask = mask.unsqueeze(-1)
879
+ x = x.masked_fill(~mask, 0.0)
880
+ # c = c.masked_fill(~mask, 0.) # no mask for c (text)
881
+
882
+ return x, c
883
+
884
+
885
+ # DiT Block
886
+
887
+
888
+ class DiTBlock(nn.Module):
889
+ def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, qk_norm=None, pe_attn_head=None):
890
+ super().__init__()
891
+
892
+ self.attn_norm = AdaLayerNorm(dim)
893
+ self.attn = Attention(
894
+ processor=AttnProcessor(pe_attn_head=pe_attn_head),
895
+ dim=dim,
896
+ heads=heads,
897
+ dim_head=dim_head,
898
+ dropout=dropout,
899
+ qk_norm=qk_norm,
900
+ )
901
+
902
+ self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
903
+ self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
904
+
905
+ def forward(self, x, t, mask=None, rope=None): # x: noised input, t: time embedding
906
+ # pre-norm & modulation for attention input
907
+ norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t)
908
+
909
+ # attention
910
+ attn_output = self.attn(x=norm, mask=mask, rope=rope)
911
+
912
+ # process attention output for input x
913
+ x = x + gate_msa.unsqueeze(1) * attn_output
914
+
915
+ norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
916
+ ff_output = self.ff(norm)
917
+ x = x + gate_mlp.unsqueeze(1) * ff_output
918
+
919
+ return x
920
+
921
+
922
+ class ChunkDiTBlock(nn.Module):
923
+ def __init__(self, dim, heads, dim_head, ff_mult=4, dropout=0.1, qk_norm=None, chunk_size=16, block_size=8, t_p=0, t_f=0, pe_attn_head=None):
924
+ super().__init__()
925
+
926
+ self.attn_norm = AdaLayerNorm(dim)
927
+ self.attn = Attention(
928
+ processor=BlockAttnProcessor(chunk_size=chunk_size, block_size=block_size, t_p=t_p, t_f=t_f, pe_attn_head=pe_attn_head),
929
+ dim=dim,
930
+ heads=heads,
931
+ dim_head=dim_head,
932
+ dropout=dropout,
933
+ qk_norm=qk_norm,
934
+ )
935
+
936
+ self.ff_norm = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
937
+ self.ff = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
938
+
939
+ def forward(self, x, t, mask=None, rope=None, is_inference=False): # x: noised input, t: time embedding
940
+ # pre-norm & modulation for attention input
941
+ norm, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.attn_norm(x, emb=t)
942
+
943
+ # attention
944
+ attn_output = self.attn(x=norm, mask=mask, rope=rope, is_inference=is_inference)
945
+
946
+ # process attention output for input x
947
+ x = x + gate_msa.unsqueeze(1) * attn_output
948
+
949
+ norm = self.ff_norm(x) * (1 + scale_mlp[:, None]) + shift_mlp[:, None]
950
+ ff_output = self.ff(norm)
951
+ x = x + gate_mlp.unsqueeze(1) * ff_output
952
+
953
+ return x
954
+
955
+ # MMDiT Block https://arxiv.org/abs/2403.03206
956
+
957
+
958
+ class MMDiTBlock(nn.Module):
959
+ r"""
960
+ modified from diffusers/src/diffusers/models/attention.py
961
+
962
+ notes.
963
+ _c: context related. text, cond, etc. (left part in sd3 fig2.b)
964
+ _x: noised input related. (right part)
965
+ context_pre_only: last layer only do prenorm + modulation cuz no more ffn
966
+ """
967
+
968
+ def __init__(
969
+ self, dim, heads, dim_head, ff_mult=4, dropout=0.1, context_dim=None, context_pre_only=False, qk_norm=None
970
+ ):
971
+ super().__init__()
972
+ if context_dim is None:
973
+ context_dim = dim
974
+ self.context_pre_only = context_pre_only
975
+
976
+ self.attn_norm_c = AdaLayerNorm_Final(context_dim) if context_pre_only else AdaLayerNorm(context_dim)
977
+ self.attn_norm_x = AdaLayerNorm(dim)
978
+ self.attn = Attention(
979
+ processor=JointAttnProcessor(),
980
+ dim=dim,
981
+ heads=heads,
982
+ dim_head=dim_head,
983
+ dropout=dropout,
984
+ context_dim=context_dim,
985
+ context_pre_only=context_pre_only,
986
+ qk_norm=qk_norm,
987
+ )
988
+
989
+ if not context_pre_only:
990
+ self.ff_norm_c = nn.LayerNorm(context_dim, elementwise_affine=False, eps=1e-6)
991
+ self.ff_c = FeedForward(dim=context_dim, mult=ff_mult, dropout=dropout, approximate="tanh")
992
+ else:
993
+ self.ff_norm_c = None
994
+ self.ff_c = None
995
+ self.ff_norm_x = nn.LayerNorm(dim, elementwise_affine=False, eps=1e-6)
996
+ self.ff_x = FeedForward(dim=dim, mult=ff_mult, dropout=dropout, approximate="tanh")
997
+
998
+ def forward(self, x, c, t, mask=None, rope=None, c_rope=None): # x: noised input, c: context, t: time embedding
999
+ # pre-norm & modulation for attention input
1000
+ if self.context_pre_only:
1001
+ norm_c = self.attn_norm_c(c, t)
1002
+ else:
1003
+ norm_c, c_gate_msa, c_shift_mlp, c_scale_mlp, c_gate_mlp = self.attn_norm_c(c, emb=t)
1004
+ norm_x, x_gate_msa, x_shift_mlp, x_scale_mlp, x_gate_mlp = self.attn_norm_x(x, emb=t)
1005
+
1006
+ # attention
1007
+ x_attn_output, c_attn_output = self.attn(x=norm_x, c=norm_c, mask=mask, rope=rope, c_rope=c_rope)
1008
+
1009
+ # process attention output for context c
1010
+ if self.context_pre_only:
1011
+ c = None
1012
+ else: # if not last layer
1013
+ c = c + c_gate_msa.unsqueeze(1) * c_attn_output
1014
+
1015
+ norm_c = self.ff_norm_c(c) * (1 + c_scale_mlp[:, None]) + c_shift_mlp[:, None]
1016
+ c_ff_output = self.ff_c(norm_c)
1017
+ c = c + c_gate_mlp.unsqueeze(1) * c_ff_output
1018
+
1019
+ # process attention output for input x
1020
+ x = x + x_gate_msa.unsqueeze(1) * x_attn_output
1021
+
1022
+ norm_x = self.ff_norm_x(x) * (1 + x_scale_mlp[:, None]) + x_shift_mlp[:, None]
1023
+ x_ff_output = self.ff_x(norm_x)
1024
+ x = x + x_gate_mlp.unsqueeze(1) * x_ff_output
1025
+
1026
+ return c, x
1027
+
1028
+
1029
+ # time step conditioning embedding
1030
+
1031
+
1032
+ class TimestepEmbedding(nn.Module):
1033
+ def __init__(self, dim, freq_embed_dim=256):
1034
+ super().__init__()
1035
+ self.time_embed = SinusPositionEmbedding(freq_embed_dim)
1036
+ self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
1037
+
1038
+ def forward(self, timestep: float["b"]): # noqa: F821
1039
+ time_hidden = self.time_embed(timestep)
1040
+ time_hidden = time_hidden.to(timestep.dtype)
1041
+ time = self.time_mlp(time_hidden) # b d
1042
+ return time
silencio/paradee/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ """Paradee-8M v1.1 text side (Sahil Mahendrakar, Apache-2.0) on Kokoro modules (hexgrad, Apache-2.0)."""
2
+ from .student import TextStudent
silencio/paradee/kokoro_modules.py ADDED
@@ -0,0 +1,267 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/hexgrad/kokoro (kokoro/modules.py + AdaIN blocks from kokoro/istftnet.py), Apache-2.0.
2
+ # Kept verbatim apart from import paths so Silencio runs without the original repo.
3
+ from torch.nn.utils.parametrizations import weight_norm
4
+ from transformers import AlbertModel
5
+ import math
6
+ import numpy as np
7
+ import torch
8
+ import torch.nn as nn
9
+ import torch.nn.functional as F
10
+
11
+
12
+ def init_weights(m, mean=0.0, std=0.01):
13
+ classname = m.__class__.__name__
14
+ if classname.find("Conv") != -1:
15
+ m.weight.data.normal_(mean, std)
16
+
17
+ def get_padding(kernel_size, dilation=1):
18
+ return int((kernel_size*dilation - dilation)/2)
19
+
20
+
21
+ class AdaIN1d(nn.Module):
22
+ def __init__(self, style_dim, num_features):
23
+ super().__init__()
24
+ # affine should be False, however there's a bug in the old torch.onnx.export (not newer dynamo) that causes the channel dimension to be lost if affine=False. When affine is true, there's additional learnably parameters. This shouldn't really matter setting it to True, since we're in inference mode
25
+ self.norm = nn.InstanceNorm1d(num_features, affine=True)
26
+ self.fc = nn.Linear(style_dim, num_features*2)
27
+
28
+ def forward(self, x, s):
29
+ h = self.fc(s)
30
+ h = h.view(h.size(0), h.size(1), 1)
31
+ gamma, beta = torch.chunk(h, chunks=2, dim=1)
32
+ return (1 + gamma) * self.norm(x) + beta
33
+
34
+
35
+ class UpSample1d(nn.Module):
36
+ def __init__(self, layer_type):
37
+ super().__init__()
38
+ self.layer_type = layer_type
39
+
40
+ def forward(self, x):
41
+ if self.layer_type == 'none':
42
+ return x
43
+ else:
44
+ return F.interpolate(x, scale_factor=2, mode='nearest')
45
+
46
+
47
+ class AdainResBlk1d(nn.Module):
48
+ def __init__(self, dim_in, dim_out, style_dim=64, actv=nn.LeakyReLU(0.2), upsample='none', dropout_p=0.0):
49
+ super().__init__()
50
+ self.actv = actv
51
+ self.upsample_type = upsample
52
+ self.upsample = UpSample1d(upsample)
53
+ self.learned_sc = dim_in != dim_out
54
+ self._build_weights(dim_in, dim_out, style_dim)
55
+ self.dropout = nn.Dropout(dropout_p)
56
+ if upsample == 'none':
57
+ self.pool = nn.Identity()
58
+ else:
59
+ self.pool = weight_norm(nn.ConvTranspose1d(dim_in, dim_in, kernel_size=3, stride=2, groups=dim_in, padding=1, output_padding=1))
60
+
61
+ def _build_weights(self, dim_in, dim_out, style_dim):
62
+ self.conv1 = weight_norm(nn.Conv1d(dim_in, dim_out, 3, 1, 1))
63
+ self.conv2 = weight_norm(nn.Conv1d(dim_out, dim_out, 3, 1, 1))
64
+ self.norm1 = AdaIN1d(style_dim, dim_in)
65
+ self.norm2 = AdaIN1d(style_dim, dim_out)
66
+ if self.learned_sc:
67
+ self.conv1x1 = weight_norm(nn.Conv1d(dim_in, dim_out, 1, 1, 0, bias=False))
68
+
69
+ def _shortcut(self, x):
70
+ x = self.upsample(x)
71
+ if self.learned_sc:
72
+ x = self.conv1x1(x)
73
+ return x
74
+
75
+ def _residual(self, x, s):
76
+ x = self.norm1(x, s)
77
+ x = self.actv(x)
78
+ x = self.pool(x)
79
+ x = self.conv1(self.dropout(x))
80
+ x = self.norm2(x, s)
81
+ x = self.actv(x)
82
+ x = self.conv2(self.dropout(x))
83
+ return x
84
+
85
+ def forward(self, x, s):
86
+ out = self._residual(x, s)
87
+ out = (out + self._shortcut(x)) * torch.rsqrt(torch.tensor(2))
88
+ return out
89
+
90
+
91
+
92
+ # https://github.com/yl4579/StyleTTS2/blob/main/models.py
93
+
94
+
95
+ class LinearNorm(nn.Module):
96
+ def __init__(self, in_dim, out_dim, bias=True, w_init_gain='linear'):
97
+ super(LinearNorm, self).__init__()
98
+ self.linear_layer = nn.Linear(in_dim, out_dim, bias=bias)
99
+ nn.init.xavier_uniform_(self.linear_layer.weight, gain=nn.init.calculate_gain(w_init_gain))
100
+
101
+ def forward(self, x):
102
+ return self.linear_layer(x)
103
+
104
+
105
+ class LayerNorm(nn.Module):
106
+ def __init__(self, channels, eps=1e-5):
107
+ super().__init__()
108
+ self.channels = channels
109
+ self.eps = eps
110
+ self.gamma = nn.Parameter(torch.ones(channels))
111
+ self.beta = nn.Parameter(torch.zeros(channels))
112
+
113
+ def forward(self, x):
114
+ x = x.transpose(1, -1)
115
+ x = F.layer_norm(x, (self.channels,), self.gamma, self.beta, self.eps)
116
+ return x.transpose(1, -1)
117
+
118
+
119
+ class TextEncoder(nn.Module):
120
+ def __init__(self, channels, kernel_size, depth, n_symbols, actv=nn.LeakyReLU(0.2)):
121
+ super().__init__()
122
+ self.embedding = nn.Embedding(n_symbols, channels)
123
+ padding = (kernel_size - 1) // 2
124
+ self.cnn = nn.ModuleList()
125
+ for _ in range(depth):
126
+ self.cnn.append(nn.Sequential(
127
+ weight_norm(nn.Conv1d(channels, channels, kernel_size=kernel_size, padding=padding)),
128
+ LayerNorm(channels),
129
+ actv,
130
+ nn.Dropout(0.2),
131
+ ))
132
+ self.lstm = nn.LSTM(channels, channels//2, 1, batch_first=True, bidirectional=True)
133
+
134
+ def forward(self, x, input_lengths, m):
135
+ x = self.embedding(x) # [B, T, emb]
136
+ x = x.transpose(1, 2) # [B, emb, T]
137
+ m = m.unsqueeze(1)
138
+ x.masked_fill_(m, 0.0)
139
+ for c in self.cnn:
140
+ x = c(x)
141
+ x.masked_fill_(m, 0.0)
142
+ x = x.transpose(1, 2) # [B, T, chn]
143
+ lengths = input_lengths if input_lengths.device == torch.device('cpu') else input_lengths.to('cpu')
144
+ x = nn.utils.rnn.pack_padded_sequence(x, lengths, batch_first=True, enforce_sorted=False)
145
+ self.lstm.flatten_parameters()
146
+ x, _ = self.lstm(x)
147
+ x, _ = nn.utils.rnn.pad_packed_sequence(x, batch_first=True)
148
+ x = x.transpose(-1, -2)
149
+ x_pad = torch.zeros([x.shape[0], x.shape[1], m.shape[-1]], device=x.device)
150
+ x_pad[:, :, :x.shape[-1]] = x
151
+ x = x_pad
152
+ x.masked_fill_(m, 0.0)
153
+ return x
154
+
155
+
156
+ class AdaLayerNorm(nn.Module):
157
+ def __init__(self, style_dim, channels, eps=1e-5):
158
+ super().__init__()
159
+ self.channels = channels
160
+ self.eps = eps
161
+ self.fc = nn.Linear(style_dim, channels*2)
162
+
163
+ def forward(self, x, s):
164
+ x = x.transpose(-1, -2)
165
+ x = x.transpose(1, -1)
166
+ h = self.fc(s)
167
+ h = h.view(h.size(0), h.size(1), 1)
168
+ gamma, beta = torch.chunk(h, chunks=2, dim=1)
169
+ gamma, beta = gamma.transpose(1, -1), beta.transpose(1, -1)
170
+ x = F.layer_norm(x, (self.channels,), eps=self.eps)
171
+ x = (1 + gamma) * x + beta
172
+ return x.transpose(1, -1).transpose(-1, -2)
173
+
174
+
175
+ class ProsodyPredictor(nn.Module):
176
+ def __init__(self, style_dim, d_hid, nlayers, max_dur=50, dropout=0.1):
177
+ super().__init__()
178
+ self.text_encoder = DurationEncoder(sty_dim=style_dim, d_model=d_hid,nlayers=nlayers, dropout=dropout)
179
+ self.lstm = nn.LSTM(d_hid + style_dim, d_hid // 2, 1, batch_first=True, bidirectional=True)
180
+ self.duration_proj = LinearNorm(d_hid, max_dur)
181
+ self.shared = nn.LSTM(d_hid + style_dim, d_hid // 2, 1, batch_first=True, bidirectional=True)
182
+ self.F0 = nn.ModuleList()
183
+ self.F0.append(AdainResBlk1d(d_hid, d_hid, style_dim, dropout_p=dropout))
184
+ self.F0.append(AdainResBlk1d(d_hid, d_hid // 2, style_dim, upsample=True, dropout_p=dropout))
185
+ self.F0.append(AdainResBlk1d(d_hid // 2, d_hid // 2, style_dim, dropout_p=dropout))
186
+ self.N = nn.ModuleList()
187
+ self.N.append(AdainResBlk1d(d_hid, d_hid, style_dim, dropout_p=dropout))
188
+ self.N.append(AdainResBlk1d(d_hid, d_hid // 2, style_dim, upsample=True, dropout_p=dropout))
189
+ self.N.append(AdainResBlk1d(d_hid // 2, d_hid // 2, style_dim, dropout_p=dropout))
190
+ self.F0_proj = nn.Conv1d(d_hid // 2, 1, 1, 1, 0)
191
+ self.N_proj = nn.Conv1d(d_hid // 2, 1, 1, 1, 0)
192
+
193
+ def forward(self, texts, style, text_lengths, alignment, m):
194
+ d = self.text_encoder(texts, style, text_lengths, m)
195
+ m = m.unsqueeze(1)
196
+ lengths = text_lengths if text_lengths.device == torch.device('cpu') else text_lengths.to('cpu')
197
+ x = nn.utils.rnn.pack_padded_sequence(d, lengths, batch_first=True, enforce_sorted=False)
198
+ self.lstm.flatten_parameters()
199
+ x, _ = self.lstm(x)
200
+ x, _ = nn.utils.rnn.pad_packed_sequence(x, batch_first=True)
201
+ x_pad = torch.zeros([x.shape[0], m.shape[-1], x.shape[-1]], device=x.device)
202
+ x_pad[:, :x.shape[1], :] = x
203
+ x = x_pad
204
+ duration = self.duration_proj(nn.functional.dropout(x, 0.5, training=False))
205
+ en = (d.transpose(-1, -2) @ alignment)
206
+ return duration.squeeze(-1), en
207
+
208
+ def F0Ntrain(self, x, s):
209
+ x, _ = self.shared(x.transpose(-1, -2))
210
+ F0 = x.transpose(-1, -2)
211
+ for block in self.F0:
212
+ F0 = block(F0, s)
213
+ F0 = self.F0_proj(F0)
214
+ N = x.transpose(-1, -2)
215
+ for block in self.N:
216
+ N = block(N, s)
217
+ N = self.N_proj(N)
218
+ return F0.squeeze(1), N.squeeze(1)
219
+
220
+
221
+ class DurationEncoder(nn.Module):
222
+ def __init__(self, sty_dim, d_model, nlayers, dropout=0.1):
223
+ super().__init__()
224
+ self.lstms = nn.ModuleList()
225
+ for _ in range(nlayers):
226
+ self.lstms.append(nn.LSTM(d_model + sty_dim, d_model // 2, num_layers=1, batch_first=True, bidirectional=True))
227
+ self.lstms.append(AdaLayerNorm(sty_dim, d_model))
228
+ self.dropout = dropout
229
+ self.d_model = d_model
230
+ self.sty_dim = sty_dim
231
+
232
+ def forward(self, x, style, text_lengths, m):
233
+ masks = m
234
+ x = x.permute(2, 0, 1)
235
+ s = style.expand(x.shape[0], x.shape[1], -1)
236
+ x = torch.cat([x, s], axis=-1)
237
+ x.masked_fill_(masks.unsqueeze(-1).transpose(0, 1), 0.0)
238
+ x = x.transpose(0, 1)
239
+ x = x.transpose(-1, -2)
240
+ for block in self.lstms:
241
+ if isinstance(block, AdaLayerNorm):
242
+ x = block(x.transpose(-1, -2), style).transpose(-1, -2)
243
+ x = torch.cat([x, s.permute(1, 2, 0)], axis=1)
244
+ x.masked_fill_(masks.unsqueeze(-1).transpose(-1, -2), 0.0)
245
+ else:
246
+ lengths = text_lengths if text_lengths.device == torch.device('cpu') else text_lengths.to('cpu')
247
+ x = x.transpose(-1, -2)
248
+ x = nn.utils.rnn.pack_padded_sequence(
249
+ x, lengths, batch_first=True, enforce_sorted=False)
250
+ block.flatten_parameters()
251
+ x, _ = block(x)
252
+ x, _ = nn.utils.rnn.pad_packed_sequence(
253
+ x, batch_first=True)
254
+ x = F.dropout(x, p=self.dropout, training=False)
255
+ x = x.transpose(-1, -2)
256
+ x_pad = torch.zeros([x.shape[0], x.shape[1], m.shape[-1]], device=x.device)
257
+ x_pad[:, :, :x.shape[-1]] = x
258
+ x = x_pad
259
+
260
+ return x.transpose(-1, -2)
261
+
262
+
263
+ # https://github.com/yl4579/StyleTTS2/blob/main/Utils/PLBERT/util.py
264
+ class CustomAlbert(AlbertModel):
265
+ def forward(self, *args, **kwargs):
266
+ outputs = super().forward(*args, **kwargs)
267
+ return outputs.last_hidden_state
silencio/paradee/student.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vendored from https://github.com/sahilmahendrakar/paradee (training/scripts/student.py), Apache-2.0.
2
+ # Kept verbatim apart from import paths so Silencio runs without the original repo.
3
+ """Single-voice text-side student: width-scaled instances of Kokoro's own modules
4
+ (CustomAlbert, ProsodyPredictor, TextEncoder) with a learned constant style vector in place
5
+ of the voice input, plus a projection to the teacher's 512-channel asr space so the frozen
6
+ teacher decoder can consume its output."""
7
+ import torch, torch.nn as nn
8
+ from transformers import AlbertConfig
9
+ from .kokoro_modules import CustomAlbert, ProsodyPredictor, TextEncoder
10
+
11
+ PRESETS = { # name: (albert hidden, albert layers, albert heads, hidden_dim, style_dim, n_layer)
12
+ # "<name>+mlp" variants use a 2-layer nonlinear asr projection (the teacher asr features are rank ~430 for 99% variance)
13
+ "xs": (128, 4, 4, 128, 16, 2),
14
+ "s": (256, 6, 4, 192, 32, 3),
15
+ "m": (384, 6, 6, 256, 32, 3),
16
+ "l": (512, 8, 8, 384, 64, 3),
17
+ }
18
+
19
+ class TextStudent(nn.Module):
20
+ def __init__(self, preset="s", n_token=178, teacher_dim=512, max_dur=50):
21
+ super().__init__()
22
+ mlp = preset.endswith("+mlp"); preset = preset.replace("+mlp", "")
23
+ ah, al, an, hd, sd, nl = PRESETS[preset]
24
+ self.bert = CustomAlbert(AlbertConfig(vocab_size=n_token, hidden_size=ah, num_attention_heads=an,
25
+ intermediate_size=ah * 3, num_hidden_layers=al,
26
+ max_position_embeddings=512, embedding_size=min(128, ah), dropout=0.1))
27
+ self.bert_encoder = nn.Linear(ah, hd)
28
+ self.predictor = ProsodyPredictor(style_dim=sd, d_hid=hd, nlayers=nl, max_dur=max_dur, dropout=0.1)
29
+ self.text_encoder = TextEncoder(channels=hd, kernel_size=5, depth=nl, n_symbols=n_token)
30
+ self.asr_proj = (nn.Sequential(nn.Linear(hd, teacher_dim), nn.GELU(), nn.Linear(teacher_dim, teacher_dim)) if mlp
31
+ else nn.Linear(hd, teacher_dim))
32
+ self.style = nn.Parameter(torch.randn(1, sd) * 0.1) # the one voice
33
+
34
+ def forward(self, ids, lengths, aln=None):
35
+ """ids [B,T] long, lengths [B]. aln: optional teacher alignment [B,T,F] (teacher forcing).
36
+ Returns dur [B,T] (pre-round sigmoid sums), F0 [B,2F], N [B,2F], asr_tok [B,512,T]."""
37
+ B, T = ids.shape
38
+ mask = torch.arange(T, device=ids.device)[None].expand(B, -1) >= lengths[:, None]
39
+ s = self.style.expand(B, -1)
40
+ bert = self.bert(ids, attention_mask=(~mask).int())
41
+ d_en = self.bert_encoder(bert).transpose(-1, -2)
42
+ d = self.predictor.text_encoder(d_en, s, lengths, mask)
43
+ x, _ = self.predictor.lstm(d)
44
+ dur = torch.sigmoid(self.predictor.duration_proj(x)).sum(-1)
45
+ t_en = self.text_encoder(ids, lengths, mask)
46
+ asr_tok = self.asr_proj(t_en.transpose(-1, -2)).transpose(-1, -2)
47
+ if aln is None:
48
+ return dur, None, None, asr_tok
49
+ en = d.transpose(-1, -2) @ aln
50
+ F0, N = self.predictor.F0Ntrain(en, s)
51
+ return dur, F0, N, asr_tok
52
+
53
+ def alignment(pred_dur, F=None):
54
+ """pred_dur [T] long -> [T, F] one-hot expansion matrix."""
55
+ idx = torch.repeat_interleave(torch.arange(len(pred_dur), device=pred_dur.device), pred_dur)
56
+ F = F or len(idx)
57
+ a = torch.zeros(len(pred_dur), F, device=pred_dur.device); a[idx[:F], torch.arange(min(F, len(idx)), device=pred_dur.device)] = 1
58
+ return a
59
+
60
+ if __name__ == "__main__":
61
+ for p in list(PRESETS) + ["s+mlp"]:
62
+ m = TextStudent(p); n = sum(x.numel() for x in m.parameters())
63
+ parts = {k: sum(x.numel() for x in getattr(m, k).parameters()) for k in ["bert", "predictor", "text_encoder", "asr_proj"]}
64
+ print(f"{p}: {n/1e6:.2f}M " + " ".join(f"{k} {v/1e6:.2f}M" for k, v in parts.items()))
silencio/text.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Silencio's text encoder: English text -> phonemes -> durations, pitch, loudness and phonetic features.
2
+
3
+ This is the text side of Paradee-8M v1.1 (Kokoro's architecture at small width), used as a frozen
4
+ encoder. Its output is aligned to 40 Hz frames, which the adapter maps into the renderer's content space.
5
+ """
6
+ import torch
7
+ import torch.nn as nn
8
+
9
+ from .paradee import TextStudent
10
+
11
+ MAX_PHONEMES = 510 # 512 positions, two of them the pad tokens at each end
12
+
13
+
14
+ def strip_weight_norm(module):
15
+ """Fold weight-norm parametrisations into plain weights (how the released weights are stored)."""
16
+ from torch.nn.utils import remove_weight_norm
17
+ from torch.nn.utils.parametrize import is_parametrized, remove_parametrizations
18
+ for m in module.modules():
19
+ try:
20
+ remove_weight_norm(m)
21
+ except Exception:
22
+ pass
23
+ if is_parametrized(m, "weight"):
24
+ remove_parametrizations(m, "weight", leave_parametrized=True)
25
+ return module
26
+
27
+
28
+ class TextEncoder(nn.Module):
29
+ def __init__(self, vocab):
30
+ super().__init__()
31
+ self.vocab = vocab
32
+ self.ts = strip_weight_norm(TextStudent("s+mlp"))
33
+ self._g2p = None
34
+
35
+ def phonemize(self, text):
36
+ if self._g2p is None: # misaki: Kokoro's own G2P, the spelling Paradee was trained on
37
+ from misaki import en, espeak
38
+ self._g2p = en.G2P(trf=False, british=False, fallback=espeak.EspeakFallback(british=False), unk="")
39
+ return self._g2p(text)[0]
40
+
41
+ def ids(self, phonemes):
42
+ ids = [self.vocab[c] for c in phonemes if c in self.vocab][:MAX_PHONEMES]
43
+ return torch.LongTensor([[0] + ids + [0]])
44
+
45
+ @torch.no_grad()
46
+ def forward(self, ids, speed=1.0):
47
+ """ids [1,T] -> asr [1,512,F], F0 [1,2F], N [1,2F]; F frames at 40 Hz."""
48
+ t = self.ts; p = t.predictor; s = t.style
49
+ d_en = t.bert_encoder(t.bert(ids, attention_mask=torch.ones_like(ids))).transpose(-1, -2)
50
+ T = ids.shape[1]; sx = s[:, :, None].expand(-1, -1, T); x = torch.cat([d_en, sx], 1)
51
+ for b in p.text_encoder.lstms:
52
+ if isinstance(b, nn.LSTM):
53
+ x = b(x.transpose(-1, -2))[0].transpose(-1, -2)
54
+ else:
55
+ x = torch.cat([b(x.transpose(-1, -2), s).transpose(-1, -2), sx], 1)
56
+ d = x.transpose(-1, -2)
57
+ dur = torch.sigmoid(p.duration_proj(p.lstm(d)[0])).sum(-1)[0]
58
+ e = t.text_encoder; h = e.embedding(ids).transpose(1, 2)
59
+ for c in e.cnn:
60
+ h = c(h)
61
+ asr_tok = t.asr_proj(e.lstm(h.transpose(1, 2))[0]).transpose(-1, -2)
62
+ n = torch.clamp(torch.round(dur / speed), min=1).long()
63
+ end = torch.cumsum(n, 0); start = end - n
64
+ f = torch.arange(int(end[-1]), device=ids.device)[None]
65
+ aln = ((f >= start[:, None]) & (f < end[:, None])).float()[None]
66
+ F0, N = p.F0Ntrain(d.transpose(-1, -2) @ aln, s)
67
+ return asr_tok @ aln, F0, N
silencio/tts.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Silencio: zero-shot English text-to-speech from one weights file.
2
+
3
+ text -> TextEncoder (durations, pitch, phonetic features @40 Hz)
4
+ -> Adapter (content tokens @25 Hz)
5
+ -> DiT renderer (mean-flow, 3 steps, streaming chunks, conditioned on a voice) -> 80-bin mel
6
+ -> Vocos -> 16 kHz speech
7
+
8
+ `convert` swaps the text front-end for MeanVC2's streaming ASR encoder, turning the same renderer into
9
+ voice conversion. A voice is a 256-d speaker embedding. Bundled voices ship in voices.safetensors; new ones come from
10
+ `silencio.enroll` (needs the WavLM/ECAPA speaker encoder, which is not part of the weights file).
11
+ """
12
+ import json
13
+ import os
14
+ import re
15
+ import numpy as np
16
+ import torch
17
+ import torch.nn as nn
18
+
19
+ from .adapter import Adapter, bnf_frames, PARADEE_FPS
20
+ from .meanvc2 import DiT
21
+ from .text import TextEncoder
22
+ from .vocos import Vocos
23
+
24
+ SAMPLE_RATE = 16000
25
+ DEFAULT_REPO = "BlueRoseLabs/Silencio"
26
+
27
+
28
+ class SilencioTTS(nn.Module):
29
+ def __init__(self, config):
30
+ super().__init__()
31
+ self.config = config
32
+ self.text = TextEncoder(config["vocab"])
33
+ self.adapter = Adapter(**config["adapter"])
34
+ self.dit = DiT(**config["dit"])
35
+ self.vocos = Vocos(**config["vocos"])
36
+ self.voices = {}
37
+
38
+ # ---- loading -------------------------------------------------------------------------
39
+ @classmethod
40
+ def from_pretrained(cls, path_or_repo=DEFAULT_REPO, device=None, revision=None):
41
+ from safetensors.torch import load_file
42
+ if os.path.isdir(path_or_repo):
43
+ get = lambda f: os.path.join(path_or_repo, f)
44
+ else:
45
+ from huggingface_hub import hf_hub_download
46
+ get = lambda f: hf_hub_download(path_or_repo, f, revision=revision)
47
+ model = cls(json.load(open(get("config.json"))))
48
+ model.load_state_dict(load_file(get("model.safetensors")), strict=True)
49
+ try:
50
+ model.voices = {k: v.float() for k, v in load_file(get("voices.safetensors")).items()}
51
+ except Exception:
52
+ model.voices = {}
53
+ device = device or ("cuda" if torch.cuda.is_available() else "cpu")
54
+ return model.to(device).eval()
55
+
56
+ @property
57
+ def device(self):
58
+ return next(self.parameters()).device
59
+
60
+ def voice(self, voice):
61
+ """A voice name from voices.safetensors, a .npy/.pt path, or a [256] tensor/array."""
62
+ if voice is None:
63
+ voice = self.config.get("default_voice") or next(iter(self.voices))
64
+ if isinstance(voice, str):
65
+ if voice in self.voices:
66
+ return self.voices[voice]
67
+ v = np.load(voice) if voice.endswith(".npy") else torch.load(voice, map_location="cpu")
68
+ return torch.as_tensor(v, dtype=torch.float32).reshape(-1)
69
+ return torch.as_tensor(voice, dtype=torch.float32).reshape(-1)
70
+
71
+ # ---- synthesis -----------------------------------------------------------------------
72
+ @torch.inference_mode()
73
+ def content(self, phonemes, speed=1.0):
74
+ """Phonemes -> content tokens [T, 256] at 25 Hz."""
75
+ dev = self.device
76
+ asr, F0, N = self.text(self.text.ids(phonemes).to(dev), speed)
77
+ T = bnf_frames(asr.shape[2] / PARADEE_FPS)
78
+ return self.adapter(asr, F0, N, torch.tensor([T], device=dev))[0]
79
+
80
+ @torch.inference_mode()
81
+ def render(self, content, voice, steps=3, seed=0):
82
+ """Content tokens [T, 256] + voice [256] -> 16 kHz waveform. Chunked exactly as MeanVC2 streams."""
83
+ dev, cs, bs = self.device, self.config["chunk_size"], self.config["block_size"]
84
+ g = torch.Generator(device=dev).manual_seed(seed) if seed is not None else None
85
+ ts = torch.linspace(1.0, 0.0, steps + 1, device=dev)
86
+ b = content.to(dev, torch.float32)[None].transpose(1, 2)
87
+ b = nn.functional.interpolate(b, size=b.shape[2] * 4, mode="linear", align_corners=True).transpose(1, 2)
88
+ spk = self.voice(voice).to(dev).reshape(1, -1)
89
+ L = b.shape[1]
90
+ noise = torch.randn(1, L, 80, device=dev, generator=g)
91
+ outs, offset, kv = [], 0, None
92
+ for c in range((L + cs - 1) // cs):
93
+ s0 = c * cs; mid = min(s0 + cs, L); end = min(mid + bs, L)
94
+ x = noise[:, s0:end]
95
+ for i in range(steps):
96
+ t, r = ts[i], ts[i + 1]
97
+ u, tmp = self.dit(x, t[None], r[None], cache=None, cond=b[:, s0:end], spks=spk,
98
+ offset=offset, is_inference=True, kv_cache=kv)
99
+ x = x - (t - r) * u
100
+ kv = tmp
101
+ outs.append(x[:, :mid - s0]); offset += mid - s0
102
+ mel = (torch.cat(outs, 1).transpose(1, 2) + 1) / 2
103
+ return self.vocos.decode(mel).reshape(-1).float().cpu()
104
+
105
+ def sentences(self, text):
106
+ for s in re.split(r"(?<=[.!?…])\s+|\n+", text.strip()):
107
+ if s.strip():
108
+ ps = self.text.phonemize(s)
109
+ if ps:
110
+ yield ps
111
+
112
+ def stream(self, text, voice=None, speed=1.0, steps=3, seed=0):
113
+ """Yields float32 numpy chunks at 16 kHz, one sentence at a time."""
114
+ for ps in self.sentences(text):
115
+ yield self.render(self.content(ps, speed), voice, steps, seed).numpy()
116
+
117
+ def generate(self, text, voice=None, speed=1.0, steps=3, seed=0, pause=0.12):
118
+ """Text -> float32 numpy waveform at 16 kHz."""
119
+ gap = np.zeros(int(pause * SAMPLE_RATE), np.float32)
120
+ parts = [p for chunk in self.stream(text, voice, speed, steps, seed) for p in (chunk, gap)]
121
+ return np.concatenate(parts[:-1]) if parts else np.zeros(0, np.float32)
122
+
123
+ __call__ = generate
124
+
125
+ def convert(self, wav, sr, voice=None, steps=3, seed=0):
126
+ """Voice conversion: speech `wav` at `sr` -> the same words in `voice`, 16 kHz numpy.
127
+ Content comes from MeanVC2's streaming Fast-U2++ encoder (downloaded on first use, CPU)."""
128
+ from .asr import ContentEncoder, to_16k
129
+ if getattr(self, "_asr", None) is None:
130
+ self._asr = ContentEncoder()
131
+ wav = np.asarray(wav, dtype=np.float32)
132
+ return self.render(self._asr(to_16k(wav.mean(-1) if wav.ndim > 1 else wav, sr)), voice, steps, seed).numpy()
133
+
134
+ def save(self, path, text, voice=None, **kw):
135
+ import soundfile as sf
136
+ sf.write(path, self.generate(text, voice, **kw), SAMPLE_RATE)