ChristianYang commited on
Commit
7dd5a70
Β·
verified Β·
1 Parent(s): 9d047fd

Add merged checkpoint at step 10000

Browse files
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Meituan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: longcat-audiodit-license
4
+ base_model: meituan-longcat/LongCat-AudioDiT-1B
5
+ tags:
6
+ - audio
7
+ - text-to-speech
8
+ - tts
9
+ - environmental-tts
10
+ - flow-matching
11
+ - dit
12
+ library_name: transformers
13
+ pipeline_tag: text-to-speech
14
+ ---
15
+
16
+ # LongCat-AudioDiT Env-TTS β€” 10000-step fine-tune
17
+
18
+ Fine-tune of [meituan-longcat/LongCat-AudioDiT-1B](https://huggingface.co/meituan-longcat/LongCat-AudioDiT-1B) for the
19
+ **three-stream env-tts task**: given a reference environment audio, a reference
20
+ speaker audio, and three text streams (env caption / speaker caption / target
21
+ speech text), generate target speech that places the target text in the
22
+ referenced environment with the referenced speaker timbre.
23
+
24
+ ## Differences from the base model
25
+
26
+ The transformer adds **six learnable boundary tokens** (three latent-space, three
27
+ text-space):
28
+
29
+ ```
30
+ latent sequence : [<boe> z_env <bos> z_spk <bon> z_target]
31
+ text sequence : [<boe_t> env_text_emb <bos_t> spk_text_emb <bon_t> target_text_emb]
32
+ ```
33
+
34
+ `encode_multistream_text(env, spk, target, drop_env_text=…, drop_spk_text=…,
35
+ drop_target_text=…)` is the new entry-point. `AudioDiTModel.forward(...)` also
36
+ accepts a pre-assembled `prompt_latent` (replaces `prompt_audio`) so the inference
37
+ path can feed the boundary-tokenized three-stream prompt directly.
38
+
39
+ ## Training summary
40
+
41
+ | Field | Value |
42
+ |---|---|
43
+ | Steps | 10000 |
44
+ | Effective batch | 16 Γ— grad_accum 2 Γ— 2 GPU = **64 rows / step** |
45
+ | Learning rate | cosine 5e-5 (warmup 250) |
46
+ | AdamW | β₁=0.9, Ξ²β‚‚=0.999, wd=0.01 |
47
+ | EMA | disabled |
48
+ | LoRA | r=32, alpha=32, target = attn + ffn |
49
+ | Full-train | boundary tokens + AdaLN + text_conv + latent_embed + input_embed + output_proj + time_embed |
50
+ | Audio filter | target duration ∈ [3, 45] s |
51
+ | RMS normalize | three-stream independent to **-23 dBFS** (target_rms=0.0708) |
52
+ | Augmentation | noise + RIR on spk_audio (DNS5 64GB) |
53
+ | Data | [ChristianYang/Env-TTS-Clean](https://huggingface.co/datasets/ChristianYang/Env-TTS-Clean) |
54
+
55
+ ## How to load
56
+
57
+ The model uses **custom code** in this repo, so pass `trust_remote_code=True`:
58
+
59
+ ```python
60
+ from transformers import AutoModel, AutoTokenizer
61
+
62
+ model = AutoModel.from_pretrained(
63
+ "meituan-longcat/LongCat-AudioDiT-Env-TTS-1B-10000Step",
64
+ trust_remote_code=True,
65
+ ).cuda().eval()
66
+
67
+ tokenizer = AutoTokenizer.from_pretrained(model.config.text_encoder_model)
68
+ ```
69
+
70
+ For end-to-end env-tts inference (three-stream prompt + ASR fallback for missing
71
+ env/spk text) see the training repo's `tasks/inference.py`.
72
+
73
+ ## License
74
+
75
+ Inherits the original [meituan-longcat/LongCat-AudioDiT-1B](https://huggingface.co/meituan-longcat/LongCat-AudioDiT-1B) license.
config.json ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "AudioDiTModel"
4
+ ],
5
+ "dit_adaln_type": "global",
6
+ "dit_adaln_use_text_cond": true,
7
+ "dit_bias": true,
8
+ "dit_cross_attn": true,
9
+ "dit_cross_attn_norm": false,
10
+ "dit_depth": 24,
11
+ "dit_dim": 1536,
12
+ "dit_dropout": 0.0,
13
+ "dit_eps": 1e-06,
14
+ "dit_ff_mult": 4,
15
+ "dit_heads": 24,
16
+ "dit_long_skip": true,
17
+ "dit_qk_norm": true,
18
+ "dit_text_conv": true,
19
+ "dit_text_dim": 768,
20
+ "dit_use_latent_condition": true,
21
+ "dtype": "float32",
22
+ "latent_dim": 64,
23
+ "latent_hop": 2048,
24
+ "max_wav_duration": 30,
25
+ "model_type": "audiodit",
26
+ "repa_dit_layer": 8,
27
+ "sampling_rate": 24000,
28
+ "sigma": 0.0,
29
+ "text_add_embed": true,
30
+ "text_encoder_config": {
31
+ "_name_or_path": "ArthurZ/umt5-base",
32
+ "architectures": [
33
+ "UMT5ForConditionalGeneration"
34
+ ],
35
+ "classifier_dropout": 0.0,
36
+ "d_ff": 2048,
37
+ "d_kv": 64,
38
+ "d_model": 768,
39
+ "dense_act_fn": "gelu_new",
40
+ "dropout_rate": 0.1,
41
+ "dtype": "float32",
42
+ "feed_forward_proj": "gated-gelu",
43
+ "initializer_factor": 1.0,
44
+ "is_gated_act": true,
45
+ "layer_norm_epsilon": 1e-06,
46
+ "model_type": "umt5",
47
+ "num_decoder_layers": 12,
48
+ "num_heads": 12,
49
+ "num_layers": 12,
50
+ "output_past": true,
51
+ "relative_attention_max_distance": 128,
52
+ "relative_attention_num_buckets": 32,
53
+ "scalable_attention": true,
54
+ "use_cache": true,
55
+ "vocab_size": 256384
56
+ },
57
+ "text_encoder_model": "google/umt5-base",
58
+ "text_norm_feat": true,
59
+ "transformers_version": "4.57.6",
60
+ "vae_config": {
61
+ "c_mults": [
62
+ 1,
63
+ 2,
64
+ 4,
65
+ 8,
66
+ 16
67
+ ],
68
+ "channels": 128,
69
+ "downsample_shortcut": "averaging",
70
+ "downsampling_ratio": 2048,
71
+ "dtype": "float32",
72
+ "encoder_latent_dim": 128,
73
+ "final_tanh": false,
74
+ "in_channels": 1,
75
+ "in_shortcut": "duplicating",
76
+ "latent_dim": 64,
77
+ "model_type": "audiodit_vae",
78
+ "out_shortcut": "averaging",
79
+ "sample_rate": 24000,
80
+ "scale": 0.71,
81
+ "strides": [
82
+ 2,
83
+ 4,
84
+ 4,
85
+ 8,
86
+ 8
87
+ ],
88
+ "upsample_shortcut": "duplicating",
89
+ "use_snake": true
90
+ },
91
+ "auto_map": {
92
+ "AutoConfig": "configuration_audiodit.AudioDiTConfig",
93
+ "AutoModel": "modeling_audiodit.AudioDiTModel"
94
+ }
95
+ }
configuration_audiodit.py ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AudioDiT model configuration"""
2
+
3
+ # transformers >= 5.0 renamed PretrainedConfig β†’ PreTrainedConfig; tolerate both.
4
+ try:
5
+ from transformers import PreTrainedConfig
6
+ except ImportError:
7
+ from transformers import PretrainedConfig as PreTrainedConfig
8
+ from transformers import logging
9
+ from transformers.models.umt5.configuration_umt5 import UMT5Config
10
+
11
+
12
+ logger = logging.get_logger(__name__)
13
+
14
+
15
+ class AudioDiTVaeConfig(PreTrainedConfig):
16
+ r"""
17
+ Configuration class for the AudioDiT WAV-VAE audio autoencoder.
18
+
19
+ Args:
20
+ in_channels (`int`, *optional*, defaults to 1):
21
+ Number of input audio channels (mono=1).
22
+ channels (`int`, *optional*, defaults to 128):
23
+ Base channel count for encoder/decoder.
24
+ c_mults (`list[int]`, *optional*, defaults to `[1, 2, 4, 8, 16]`):
25
+ Channel multipliers for each encoder/decoder stage.
26
+ strides (`list[int]`, *optional*, defaults to `[2, 4, 4, 8, 8]`):
27
+ Downsampling strides for each encoder stage.
28
+ latent_dim (`int`, *optional*, defaults to 64):
29
+ Dimensionality of the latent space (after VAE bottleneck: encoder outputs 128, split to mean+scale β†’ 64).
30
+ encoder_latent_dim (`int`, *optional*, defaults to 128):
31
+ Dimensionality of the encoder output before VAE bottleneck.
32
+ use_snake (`bool`, *optional*, defaults to `True`):
33
+ Whether to use Snake activation instead of ELU.
34
+ downsample_shortcut (`str`, *optional*, defaults to `"averaging"`):
35
+ Shortcut type for encoder downsampling blocks.
36
+ upsample_shortcut (`str`, *optional*, defaults to `"duplicating"`):
37
+ Shortcut type for decoder upsampling blocks.
38
+ out_shortcut (`str`, *optional*, defaults to `"averaging"`):
39
+ Shortcut type for encoder output projection.
40
+ in_shortcut (`str`, *optional*, defaults to `"duplicating"`):
41
+ Shortcut type for decoder input projection.
42
+ final_tanh (`bool`, *optional*, defaults to `False`):
43
+ Whether to apply tanh to the decoder output.
44
+ downsampling_ratio (`int`, *optional*, defaults to 2048):
45
+ Total downsampling ratio from audio samples to latent frames.
46
+ sample_rate (`int`, *optional*, defaults to 24000):
47
+ Audio sample rate.
48
+ scale (`float`, *optional*, defaults to 0.71):
49
+ Scale factor for the latent space.
50
+ """
51
+
52
+ model_type = "audiodit_vae"
53
+
54
+ def __init__(
55
+ self,
56
+ in_channels: int = 1,
57
+ channels: int = 128,
58
+ c_mults: list[int] | None = None,
59
+ strides: list[int] | None = None,
60
+ latent_dim: int = 64,
61
+ encoder_latent_dim: int = 128,
62
+ use_snake: bool = True,
63
+ downsample_shortcut: str = "averaging",
64
+ upsample_shortcut: str = "duplicating",
65
+ out_shortcut: str = "averaging",
66
+ in_shortcut: str = "duplicating",
67
+ final_tanh: bool = False,
68
+ downsampling_ratio: int = 2048,
69
+ sample_rate: int = 24000,
70
+ scale: float = 0.71,
71
+ **kwargs,
72
+ ):
73
+ super().__init__(**kwargs)
74
+ self.in_channels = in_channels
75
+ self.channels = channels
76
+ self.c_mults = c_mults if c_mults is not None else [1, 2, 4, 8, 16]
77
+ self.strides = strides if strides is not None else [2, 4, 4, 8, 8]
78
+ self.latent_dim = latent_dim
79
+ self.encoder_latent_dim = encoder_latent_dim
80
+ self.use_snake = use_snake
81
+ self.downsample_shortcut = downsample_shortcut
82
+ self.upsample_shortcut = upsample_shortcut
83
+ self.out_shortcut = out_shortcut
84
+ self.in_shortcut = in_shortcut
85
+ self.final_tanh = final_tanh
86
+ self.downsampling_ratio = downsampling_ratio
87
+ self.sample_rate = sample_rate
88
+ self.scale = scale
89
+
90
+
91
+ class AudioDiTConfig(PreTrainedConfig):
92
+ r"""
93
+ Configuration class for AudioDiT, a Conditional Flow Matching TTS model based on DiT architecture.
94
+
95
+ Args:
96
+ dit_dim (`int`, *optional*, defaults to 1536):
97
+ Hidden dimension of the DiT transformer.
98
+ dit_depth (`int`, *optional*, defaults to 24):
99
+ Number of transformer layers.
100
+ dit_heads (`int`, *optional*, defaults to 24):
101
+ Number of attention heads.
102
+ dit_ff_mult (`float`, *optional*, defaults to 4.0):
103
+ Feed-forward network multiplier.
104
+ dit_text_dim (`int`, *optional*, defaults to 768):
105
+ Dimension of the text encoder output (UMT5-base).
106
+ dit_dropout (`float`, *optional*, defaults to 0.0):
107
+ Dropout rate.
108
+ dit_bias (`bool`, *optional*, defaults to `True`):
109
+ Whether to use bias in linear layers.
110
+ dit_cross_attn (`bool`, *optional*, defaults to `True`):
111
+ Whether to use cross-attention layers.
112
+ dit_adaln_type (`str`, *optional*, defaults to `"global"`):
113
+ Type of adaptive layer norm (`"global"` or `"local"`).
114
+ dit_adaln_use_text_cond (`bool`, *optional*, defaults to `True`):
115
+ Whether to condition AdaLN on text embeddings.
116
+ dit_long_skip (`bool`, *optional*, defaults to `True`):
117
+ Whether to use long skip connection (input added to output).
118
+ dit_text_conv (`bool`, *optional*, defaults to `True`):
119
+ Whether to apply ConvNeXt blocks on text embeddings.
120
+ dit_qk_norm (`bool`, *optional*, defaults to `True`):
121
+ Whether to apply RMS normalization to Q and K.
122
+ dit_cross_attn_norm (`bool`, *optional*, defaults to `False`):
123
+ Whether to apply layer normalization in cross-attention.
124
+ dit_eps (`float`, *optional*, defaults to 1e-6):
125
+ Epsilon for normalization layers.
126
+ dit_use_latent_condition (`bool`, *optional*, defaults to `True`):
127
+ Whether to use latent conditioning (for prompt audio).
128
+ repa_dit_layer (`int`, *optional*, defaults to 8):
129
+ Layer index for representation alignment.
130
+ latent_dim (`int`, *optional*, defaults to 64):
131
+ Dimensionality of the audio latent space.
132
+ sigma (`float`, *optional*, defaults to 0.0):
133
+ Noise level for conditional flow matching.
134
+ sampling_rate (`int`, *optional*, defaults to 24000):
135
+ Audio sample rate.
136
+ latent_hop (`int`, *optional*, defaults to 2048):
137
+ Hop size in audio samples per latent frame.
138
+ max_wav_duration (`float`, *optional*, defaults to 30.0):
139
+ Maximum audio duration in seconds.
140
+ text_encoder_model (`str`, *optional*, defaults to `"google/umt5-base"`):
141
+ HuggingFace model identifier for the text encoder.
142
+ text_add_embed (`bool`, *optional*, defaults to `True`):
143
+ Whether to add the first hidden state to the last hidden state in text encoding.
144
+ text_norm_feat (`bool`, *optional*, defaults to `True`):
145
+ Whether to apply layer normalization to text features.
146
+ vae_config (`AudioDiTVaeConfig` or `dict`, *optional*):
147
+ Configuration for the WAV-VAE audio autoencoder.
148
+
149
+ Example:
150
+
151
+ ```python
152
+ >>> from transformers import AudioDiTConfig, AudioDiTModel
153
+
154
+ >>> configuration = AudioDiTConfig()
155
+ >>> model = AudioDiTModel(configuration)
156
+ >>> configuration = model.config
157
+ ```
158
+ """
159
+
160
+ model_type = "audiodit"
161
+ sub_configs = {"vae_config": AudioDiTVaeConfig, "text_encoder_config": UMT5Config}
162
+
163
+ def __init__(
164
+ self,
165
+ dit_dim: int = 1536,
166
+ dit_depth: int = 24,
167
+ dit_heads: int = 24,
168
+ dit_ff_mult: float = 4.0,
169
+ dit_text_dim: int = 768,
170
+ dit_dropout: float = 0.0,
171
+ dit_bias: bool = True,
172
+ dit_cross_attn: bool = True,
173
+ dit_adaln_type: str = "global",
174
+ dit_adaln_use_text_cond: bool = True,
175
+ dit_long_skip: bool = True,
176
+ dit_text_conv: bool = True,
177
+ dit_qk_norm: bool = True,
178
+ dit_cross_attn_norm: bool = False,
179
+ dit_eps: float = 1e-6,
180
+ dit_use_latent_condition: bool = True,
181
+ repa_dit_layer: int = 8,
182
+ latent_dim: int = 64,
183
+ sigma: float = 0.0,
184
+ sampling_rate: int = 24000,
185
+ latent_hop: int = 2048,
186
+ max_wav_duration: float = 30.0,
187
+ text_encoder_model: str = "google/umt5-base",
188
+ text_add_embed: bool = True,
189
+ text_norm_feat: bool = True,
190
+ vae_config: AudioDiTVaeConfig | dict | None = None,
191
+ text_encoder_config: UMT5Config | dict | None = None,
192
+ **kwargs,
193
+ ):
194
+ super().__init__(**kwargs)
195
+ self.dit_dim = dit_dim
196
+ self.dit_depth = dit_depth
197
+ self.dit_heads = dit_heads
198
+ self.dit_ff_mult = dit_ff_mult
199
+ self.dit_text_dim = dit_text_dim
200
+ self.dit_dropout = dit_dropout
201
+ self.dit_bias = dit_bias
202
+ self.dit_cross_attn = dit_cross_attn
203
+ self.dit_adaln_type = dit_adaln_type
204
+ self.dit_adaln_use_text_cond = dit_adaln_use_text_cond
205
+ self.dit_long_skip = dit_long_skip
206
+ self.dit_text_conv = dit_text_conv
207
+ self.dit_qk_norm = dit_qk_norm
208
+ self.dit_cross_attn_norm = dit_cross_attn_norm
209
+ self.dit_eps = dit_eps
210
+ self.dit_use_latent_condition = dit_use_latent_condition
211
+ self.repa_dit_layer = repa_dit_layer
212
+ self.latent_dim = latent_dim
213
+ self.sigma = sigma
214
+ self.sampling_rate = sampling_rate
215
+ self.latent_hop = latent_hop
216
+ self.max_wav_duration = max_wav_duration
217
+ self.text_encoder_model = text_encoder_model
218
+ self.text_add_embed = text_add_embed
219
+ self.text_norm_feat = text_norm_feat
220
+
221
+ if isinstance(vae_config, dict):
222
+ vae_config = AudioDiTVaeConfig(**vae_config)
223
+ self.vae_config = vae_config if vae_config is not None else AudioDiTVaeConfig()
224
+
225
+ if isinstance(text_encoder_config, dict):
226
+ text_encoder_config = UMT5Config(**text_encoder_config)
227
+ self.text_encoder_config = text_encoder_config
228
+
229
+
230
+ __all__ = ["AudioDiTConfig", "AudioDiTVaeConfig"]
model-00001-of-00002.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d8d814aa8350d765dfb9f12e67db470062a5411ba03f9e1a96af271df8ee5b20
3
+ size 4998324592
model-00002-of-00002.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c6d93027ccaaeec9aab217bccc88da12d7a93ee6d9345ce47c366558097b00b0
3
+ size 681520212
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
modeling_audiodit.py ADDED
@@ -0,0 +1,1416 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PyTorch AudioDiT model β€” Conditional Flow Matching TTS with DiT backbone."""
2
+
3
+ import math
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+ import torch
8
+ import torch.nn.functional as F
9
+ from torch import nn
10
+ # Use the modern parametrizations-based weight_norm. Backward-compatible: it
11
+ # auto-remaps legacy `weight_g`/`weight_v` keys in state_dict to the new
12
+ # `parametrizations.weight.original0`/`original1` names on load (via a
13
+ # state_dict pre-hook registered inside parametrize.register_parametrization).
14
+ # This fixes the silent VAE load failure where transformers 5.x's
15
+ # `Materializing param` path renamed weight_norm modules to parametrizations
16
+ # without remapping, leading to VAE outputs of pure silence.
17
+ from torch.nn.utils.parametrizations import weight_norm
18
+ from torch.nn.utils.rnn import pad_sequence
19
+
20
+ from transformers import PreTrainedModel, logging
21
+ from transformers.modeling_outputs import ModelOutput
22
+ from .configuration_audiodit import AudioDiTConfig, AudioDiTVaeConfig
23
+
24
+
25
+ logger = logging.get_logger(__name__)
26
+
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Output dataclass
30
+ # ---------------------------------------------------------------------------
31
+
32
+
33
+ @dataclass
34
+ class AudioDiTOutput(ModelOutput):
35
+ """
36
+ Output of [`AudioDiTModel`].
37
+
38
+ Args:
39
+ waveform (`torch.FloatTensor` of shape `(batch_size, num_samples)`):
40
+ Generated audio waveform.
41
+ latent (`torch.FloatTensor` of shape `(batch_size, latent_dim, num_frames)`):
42
+ Predicted latent representation before VAE decoding.
43
+ """
44
+
45
+ waveform: torch.FloatTensor | None = None
46
+ latent: torch.FloatTensor | None = None
47
+
48
+
49
+ # ---------------------------------------------------------------------------
50
+ # ODE solver (inline Euler β€” replaces torchdiffeq dependency)
51
+ # ---------------------------------------------------------------------------
52
+
53
+
54
+ def odeint_euler(fn, y0, t):
55
+ """Simple Euler ODE integrator (equivalent to `torchdiffeq.odeint` with `method='euler'`).
56
+
57
+ Args:
58
+ fn: callable(t, y) β†’ dy/dt
59
+ y0: initial state tensor
60
+ t: 1-D tensor of time steps (must be monotonically increasing)
61
+
62
+ Returns:
63
+ Tensor of shape `(len(t), *y0.shape)` containing the trajectory.
64
+ """
65
+ ys = [y0]
66
+ y = y0
67
+ for i in range(len(t) - 1):
68
+ dt = t[i + 1] - t[i]
69
+ y = y + fn(t[i], y) * dt
70
+ ys.append(y)
71
+ return torch.stack(ys)
72
+
73
+
74
+ # ---------------------------------------------------------------------------
75
+ # Utility helpers (from model/utils.py)
76
+ # ---------------------------------------------------------------------------
77
+
78
+
79
+ def lens_to_mask(lengths: torch.Tensor, length: int | None = None) -> torch.BoolTensor:
80
+ if length is None:
81
+ length = lengths.amax()
82
+ seq = torch.arange(length, device=lengths.device)
83
+ return seq[None, :] < lengths[:, None]
84
+
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Low-level modules (from model/modules.py)
88
+ # ---------------------------------------------------------------------------
89
+
90
+
91
+ class AudioDiTRMSNorm(nn.Module):
92
+ def __init__(self, dim: int, eps: float = 1e-6):
93
+ super().__init__()
94
+ self.dim = dim
95
+ self.eps = eps
96
+ self.weight = nn.Parameter(torch.ones(dim))
97
+
98
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
99
+ return self._norm(x.float()).type_as(x) * self.weight
100
+
101
+ def _norm(self, x: torch.Tensor) -> torch.Tensor:
102
+ return x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
103
+
104
+
105
+ class AudioDiTSinusPositionEmbedding(nn.Module):
106
+ def __init__(self, dim: int):
107
+ super().__init__()
108
+ self.dim = dim
109
+
110
+ def forward(self, x: torch.Tensor, scale: float = 1000.0) -> torch.Tensor:
111
+ device = x.device
112
+ half_dim = self.dim // 2
113
+ emb = math.log(10000) / (half_dim - 1)
114
+ emb = torch.exp(torch.arange(half_dim, device=device).float() * -emb)
115
+ emb = scale * x.unsqueeze(1) * emb.unsqueeze(0)
116
+ return torch.cat((emb.sin(), emb.cos()), dim=-1)
117
+
118
+
119
+ class AudioDiTTimestepEmbedding(nn.Module):
120
+ def __init__(self, dim: int, freq_embed_dim: int = 256):
121
+ super().__init__()
122
+ self.time_embed = AudioDiTSinusPositionEmbedding(freq_embed_dim)
123
+ self.time_mlp = nn.Sequential(nn.Linear(freq_embed_dim, dim), nn.SiLU(), nn.Linear(dim, dim))
124
+
125
+ def forward(self, timestep: torch.Tensor) -> torch.Tensor:
126
+ time_hidden = self.time_embed(timestep)
127
+ time_hidden = time_hidden.to(timestep.dtype)
128
+ return self.time_mlp(time_hidden)
129
+
130
+
131
+ class AudioDiTRotaryEmbedding(nn.Module):
132
+ """Qwen2-style rotary position embedding.
133
+
134
+ All state (inv_freq, cos/sin caches) is built lazily on first ``forward``
135
+ call. This avoids corruption from ``from_pretrained`` meta-device
136
+ construction while producing bit-identical results to the original
137
+ ``Qwen2RotaryEmbedding`` (which creates ``inv_freq`` on CPU then moves
138
+ the whole model to CUDA with ``.to(device)``).
139
+ """
140
+
141
+ def __init__(self, dim: int, max_position_embeddings: int = 2048, base: float = 100000.0):
142
+ super().__init__()
143
+ self.dim = dim
144
+ self.max_position_embeddings = max_position_embeddings
145
+ self.base = base
146
+ # Do NOT register any buffers here β€” they get corrupted by meta-device.
147
+ # Everything is built lazily in forward().
148
+ self._cos: torch.Tensor | None = None
149
+ self._sin: torch.Tensor | None = None
150
+ self._cached_len: int = 0
151
+ self._cached_device: torch.device | None = None
152
+
153
+ def _build(self, seq_len: int, device: torch.device, dtype: torch.dtype):
154
+ """Build cos/sin tables entirely on CPU (matching original
155
+ Qwen2RotaryEmbedding which builds in __init__ on CPU, then the
156
+ whole model is moved with .to(device)), then move to target."""
157
+ inv_freq = 1.0 / (self.base ** (torch.arange(0, self.dim, 2, dtype=torch.int64).float() / self.dim))
158
+ t = torch.arange(seq_len, dtype=torch.int64).type_as(inv_freq)
159
+ freqs = torch.outer(t, inv_freq)
160
+ emb = torch.cat((freqs, freqs), dim=-1)
161
+ self._cos = emb.cos().to(dtype=dtype, device=device)
162
+ self._sin = emb.sin().to(dtype=dtype, device=device)
163
+ self._cached_len = seq_len
164
+ self._cached_device = device
165
+
166
+ def forward(self, x: torch.Tensor, seq_len: int | None = None) -> tuple[torch.Tensor, torch.Tensor]:
167
+ if seq_len is None:
168
+ seq_len = x.shape[1]
169
+ if self._cos is None or seq_len > self._cached_len or self._cached_device != x.device:
170
+ self._build(max(seq_len, self.max_position_embeddings), x.device, x.dtype)
171
+ return (
172
+ self._cos[:seq_len].to(dtype=x.dtype),
173
+ self._sin[:seq_len].to(dtype=x.dtype),
174
+ )
175
+
176
+
177
+ def _rotate_half(x: torch.Tensor) -> torch.Tensor:
178
+ x1, x2 = x.chunk(2, dim=-1)
179
+ return torch.cat([-x2, x1], dim=-1)
180
+
181
+
182
+ def _apply_rotary_emb(x: torch.Tensor, freqs_cis: tuple[torch.Tensor, torch.Tensor]) -> torch.Tensor:
183
+ cos, sin = freqs_cis
184
+ cos = cos[None, None].to(x.device)
185
+ sin = sin[None, None].to(x.device)
186
+ return (x.float() * cos + _rotate_half(x).float() * sin).to(x.dtype)
187
+
188
+
189
+ # ---------------------------------------------------------------------------
190
+ # GRN + ConvNeXtV2 (for text conv)
191
+ # ---------------------------------------------------------------------------
192
+
193
+
194
+ class AudioDiTGRN(nn.Module):
195
+ """Global Response Normalization."""
196
+
197
+ def __init__(self, dim: int):
198
+ super().__init__()
199
+ self.gamma = nn.Parameter(torch.zeros(1, 1, dim))
200
+ self.beta = nn.Parameter(torch.zeros(1, 1, dim))
201
+
202
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
203
+ gx = torch.norm(x, p=2, dim=1, keepdim=True)
204
+ nx = gx / (gx.mean(dim=-1, keepdim=True) + 1e-6)
205
+ return self.gamma * (x * nx) + self.beta + x
206
+
207
+
208
+ class AudioDiTConvNeXtV2Block(nn.Module):
209
+ def __init__(self, dim: int, intermediate_dim: int, dilation: int = 1, kernel_size: int = 7, bias: bool = True, eps: float = 1e-6):
210
+ super().__init__()
211
+ padding = (dilation * (kernel_size - 1)) // 2
212
+ self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=padding, groups=dim, dilation=dilation, bias=bias)
213
+ self.norm = nn.LayerNorm(dim, eps=eps)
214
+ self.pwconv1 = nn.Linear(dim, intermediate_dim, bias=bias)
215
+ self.act = nn.SiLU()
216
+ self.grn = AudioDiTGRN(intermediate_dim)
217
+ self.pwconv2 = nn.Linear(intermediate_dim, dim, bias=bias)
218
+
219
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
220
+ residual = x
221
+ x = x.transpose(1, 2)
222
+ x = self.dwconv(x)
223
+ x = x.transpose(1, 2)
224
+ x = self.norm(x)
225
+ x = self.pwconv1(x)
226
+ x = self.act(x)
227
+ x = self.grn(x)
228
+ x = self.pwconv2(x)
229
+ return residual + x
230
+
231
+
232
+ # ---------------------------------------------------------------------------
233
+ # Embedder (shared for input / text / latent)
234
+ # ---------------------------------------------------------------------------
235
+
236
+
237
+ class AudioDiTEmbedder(nn.Module):
238
+ def __init__(self, in_dim: int, out_dim: int):
239
+ super().__init__()
240
+ self.proj = nn.Sequential(nn.Linear(in_dim, out_dim), nn.SiLU(), nn.Linear(out_dim, out_dim))
241
+
242
+ def forward(self, x: torch.Tensor, mask: torch.BoolTensor | None = None) -> torch.Tensor:
243
+ if mask is not None:
244
+ x = x.masked_fill(mask.logical_not().unsqueeze(-1), 0.0)
245
+ x = self.proj(x)
246
+ if mask is not None:
247
+ x = x.masked_fill(mask.logical_not().unsqueeze(-1), 0.0)
248
+ return x
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # AdaLN modules
253
+ # ---------------------------------------------------------------------------
254
+
255
+
256
+ class AudioDiTAdaLNMLP(nn.Module):
257
+ def __init__(self, in_dim: int, out_dim: int, bias: bool = True):
258
+ super().__init__()
259
+ self.mlp = nn.Sequential(nn.SiLU(), nn.Linear(in_dim, out_dim, bias=bias))
260
+
261
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
262
+ return self.mlp(x)
263
+
264
+
265
+ class AudioDiTAdaLayerNormZeroFinal(nn.Module):
266
+ def __init__(self, dim: int, bias: bool = True, eps: float = 1e-6):
267
+ super().__init__()
268
+ self.silu = nn.SiLU()
269
+ self.linear = nn.Linear(dim, dim * 2, bias=bias)
270
+ self.norm = nn.LayerNorm(dim, elementwise_affine=False, eps=eps)
271
+
272
+ def forward(self, x: torch.Tensor, emb: torch.Tensor) -> torch.Tensor:
273
+ emb = self.linear(self.silu(emb))
274
+ scale, shift = torch.chunk(emb, 2, dim=-1)
275
+ x = self.norm(x.float()).type_as(x)
276
+ if scale.ndim == 2:
277
+ x = x * (1 + scale)[:, None, :] + shift[:, None, :]
278
+ else:
279
+ x = x * (1 + scale) + shift
280
+ return x
281
+
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # Attention
285
+ # ---------------------------------------------------------------------------
286
+
287
+
288
+ def _modulate(x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor, eps: float = 1e-6) -> torch.Tensor:
289
+ """LayerNorm without affine + modulate."""
290
+ x = F.layer_norm(x.float(), (x.shape[-1],), eps=eps).type_as(x)
291
+ if scale.ndim == 2:
292
+ return x * (1 + scale[:, None]) + shift[:, None]
293
+ return x * (1 + scale) + shift
294
+
295
+
296
+ class AudioDiTSelfAttention(nn.Module):
297
+ def __init__(self, dim: int, heads: int, dim_head: int, dropout: float = 0.0, bias: bool = True, qk_norm: bool = False, eps: float = 1e-6):
298
+ super().__init__()
299
+ self.heads = heads
300
+ self.inner_dim = dim_head * heads
301
+ self.to_q = nn.Linear(dim, self.inner_dim, bias=bias)
302
+ self.to_k = nn.Linear(dim, self.inner_dim, bias=bias)
303
+ self.to_v = nn.Linear(dim, self.inner_dim, bias=bias)
304
+ self.qk_norm = qk_norm
305
+ if qk_norm:
306
+ self.q_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps)
307
+ self.k_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps)
308
+ self.to_out = nn.ModuleList([nn.Linear(self.inner_dim, dim, bias=bias), nn.Dropout(dropout)])
309
+
310
+ def forward(self, x: torch.Tensor, mask: torch.BoolTensor | None = None, rope: tuple | None = None) -> torch.Tensor:
311
+ batch_size = x.shape[0]
312
+ query = self.to_q(x)
313
+ key = self.to_k(x)
314
+ value = self.to_v(x)
315
+ if self.qk_norm:
316
+ query = self.q_norm(query)
317
+ key = self.k_norm(key)
318
+ head_dim = self.inner_dim // self.heads
319
+ query = query.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
320
+ key = key.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
321
+ value = value.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
322
+ if rope is not None:
323
+ query = _apply_rotary_emb(query, rope)
324
+ key = _apply_rotary_emb(key, rope)
325
+ attn_mask = None
326
+ if mask is not None:
327
+ attn_mask = mask.unsqueeze(1).unsqueeze(1).expand(batch_size, self.heads, query.shape[-2], key.shape[-2])
328
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
329
+ x = x.transpose(1, 2).reshape(batch_size, -1, self.inner_dim).to(query.dtype)
330
+ x = self.to_out[0](x)
331
+ x = self.to_out[1](x)
332
+ return x
333
+
334
+
335
+ class AudioDiTCrossAttention(nn.Module):
336
+ def __init__(self, q_dim: int, kv_dim: int, heads: int, dim_head: int, dropout: float = 0.0, bias: bool = True, qk_norm: bool = False, eps: float = 1e-6):
337
+ super().__init__()
338
+ self.heads = heads
339
+ self.inner_dim = dim_head * heads
340
+ self.to_q = nn.Linear(q_dim, self.inner_dim, bias=bias)
341
+ self.to_k = nn.Linear(kv_dim, self.inner_dim, bias=bias)
342
+ self.to_v = nn.Linear(kv_dim, self.inner_dim, bias=bias)
343
+ self.qk_norm = qk_norm
344
+ if qk_norm:
345
+ self.q_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps)
346
+ self.k_norm = AudioDiTRMSNorm(self.inner_dim, eps=eps)
347
+ self.to_out = nn.ModuleList([nn.Linear(self.inner_dim, q_dim, bias=bias), nn.Dropout(dropout)])
348
+
349
+ def forward(
350
+ self, x: torch.Tensor, cond: torch.Tensor, mask: torch.BoolTensor | None = None,
351
+ cond_mask: torch.BoolTensor | None = None, rope: tuple | None = None, cond_rope: tuple | None = None,
352
+ ) -> torch.Tensor:
353
+ batch_size = x.shape[0]
354
+ query = self.to_q(x)
355
+ key = self.to_k(cond)
356
+ value = self.to_v(cond)
357
+ if self.qk_norm:
358
+ query = self.q_norm(query)
359
+ key = self.k_norm(key)
360
+ head_dim = self.inner_dim // self.heads
361
+ query = query.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
362
+ key = key.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
363
+ value = value.view(batch_size, -1, self.heads, head_dim).transpose(1, 2)
364
+ if rope is not None:
365
+ query = _apply_rotary_emb(query, rope)
366
+ if cond_rope is not None:
367
+ key = _apply_rotary_emb(key, cond_rope)
368
+ attn_mask = None
369
+ if mask is not None:
370
+ attn_mask = cond_mask.unsqueeze(1).expand(-1, mask.shape[1], -1).unsqueeze(1)
371
+ attn_mask = attn_mask.expand(batch_size, self.heads, query.shape[-2], key.shape[-2])
372
+ x = F.scaled_dot_product_attention(query, key, value, attn_mask=attn_mask, dropout_p=0.0, is_causal=False)
373
+ x = x.transpose(1, 2).reshape(batch_size, -1, self.inner_dim).to(query.dtype)
374
+ x = self.to_out[0](x)
375
+ x = self.to_out[1](x)
376
+ return x
377
+
378
+
379
+ # ---------------------------------------------------------------------------
380
+ # FeedForward
381
+ # ---------------------------------------------------------------------------
382
+
383
+
384
+ class AudioDiTFeedForward(nn.Module):
385
+ def __init__(self, dim: int, mult: float = 4.0, dropout: float = 0.0, bias: bool = True):
386
+ super().__init__()
387
+ inner_dim = int(dim * mult)
388
+ self.ff = nn.Sequential(
389
+ nn.Linear(dim, inner_dim, bias=bias),
390
+ nn.GELU(approximate="tanh"),
391
+ nn.Dropout(dropout),
392
+ nn.Linear(inner_dim, dim, bias=bias),
393
+ )
394
+
395
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
396
+ return self.ff(x)
397
+
398
+
399
+ # ---------------------------------------------------------------------------
400
+ # Transformer Block (CrossDiTBlock)
401
+ # ---------------------------------------------------------------------------
402
+
403
+
404
+ class AudioDiTBlock(nn.Module):
405
+ """Single DiT block with self-attention, optional cross-attention, FFN, and AdaLN modulation."""
406
+
407
+ def __init__(self, config: AudioDiTConfig):
408
+ super().__init__()
409
+ dim = config.dit_dim
410
+ cond_dim = config.dit_dim # after text embedding, cond_dim == dim
411
+ heads = config.dit_heads
412
+ dim_head = dim // heads
413
+ bias = config.dit_bias
414
+ eps = config.dit_eps
415
+
416
+ self.adaln_type = config.dit_adaln_type
417
+ self.adaln_use_text_cond = config.dit_adaln_use_text_cond
418
+ if config.dit_adaln_type == "local":
419
+ self.adaln_mlp = AudioDiTAdaLNMLP(dim, dim * 6, bias=True)
420
+ elif config.dit_adaln_type == "global":
421
+ self.adaln_scale_shift = nn.Parameter(torch.randn(dim * 6) / dim**0.5)
422
+
423
+ self.self_attn = AudioDiTSelfAttention(
424
+ dim=dim, heads=heads, dim_head=dim_head, dropout=config.dit_dropout,
425
+ bias=bias, qk_norm=config.dit_qk_norm, eps=eps,
426
+ )
427
+
428
+ self.use_cross_attn = config.dit_cross_attn
429
+ if config.dit_cross_attn:
430
+ self.cross_attn = AudioDiTCrossAttention(
431
+ q_dim=dim, kv_dim=cond_dim, heads=heads, dim_head=dim_head,
432
+ dropout=config.dit_dropout, bias=bias, qk_norm=config.dit_qk_norm, eps=eps,
433
+ )
434
+ self.cross_attn_norm = nn.LayerNorm(dim, elementwise_affine=True, eps=eps) if config.dit_cross_attn_norm else nn.Identity()
435
+ self.cross_attn_norm_c = nn.LayerNorm(cond_dim, elementwise_affine=True, eps=eps) if config.dit_cross_attn_norm else nn.Identity()
436
+
437
+ self.ffn = AudioDiTFeedForward(dim=dim, mult=config.dit_ff_mult, dropout=config.dit_dropout, bias=bias)
438
+
439
+ def forward(
440
+ self, x: torch.Tensor, t: torch.Tensor, cond: torch.Tensor,
441
+ mask: torch.BoolTensor | None = None, cond_mask: torch.BoolTensor | None = None,
442
+ rope: tuple | None = None, cond_rope: tuple | None = None,
443
+ adaln_global_out: torch.Tensor | None = None,
444
+ ) -> torch.Tensor:
445
+ if self.adaln_type == "local" and adaln_global_out is None:
446
+ if self.adaln_use_text_cond:
447
+ cond_mean = cond.sum(1) / cond_mask.sum(1, keepdim=True)
448
+ norm_cond = t + cond_mean
449
+ else:
450
+ norm_cond = t
451
+ adaln_out = self.adaln_mlp(norm_cond)
452
+ gate_sa, scale_sa, shift_sa, gate_ffn, scale_ffn, shift_ffn = torch.chunk(adaln_out, 6, dim=-1)
453
+ else:
454
+ from einops import rearrange
455
+ adaln_out = adaln_global_out + rearrange(self.adaln_scale_shift, "f -> 1 f")
456
+ gate_sa, scale_sa, shift_sa, gate_ffn, scale_ffn, shift_ffn = torch.chunk(adaln_out, 6, dim=-1)
457
+
458
+ # Self-attention
459
+ norm = _modulate(x, scale_sa, shift_sa)
460
+ attn_output = self.self_attn(norm, mask=mask, rope=rope)
461
+ if gate_sa.ndim == 2:
462
+ gate_sa = gate_sa.unsqueeze(1)
463
+ x = x + gate_sa * attn_output
464
+
465
+ # Cross-attention
466
+ if self.use_cross_attn:
467
+ cross_out = self.cross_attn(
468
+ x=self.cross_attn_norm(x), cond=self.cross_attn_norm_c(cond),
469
+ mask=mask, cond_mask=cond_mask, rope=rope, cond_rope=cond_rope,
470
+ )
471
+ x = x + cross_out
472
+
473
+ # FFN
474
+ norm = _modulate(x, scale_ffn, shift_ffn)
475
+ ff_output = self.ffn(norm)
476
+ if gate_ffn.ndim == 2:
477
+ gate_ffn = gate_ffn.unsqueeze(1)
478
+ x = x + gate_ffn * ff_output
479
+ return x
480
+
481
+
482
+ # ---------------------------------------------------------------------------
483
+ # AudioDiTTransformer (CrossDiT backbone)
484
+ # ---------------------------------------------------------------------------
485
+
486
+
487
+ class AudioDiTTransformer(nn.Module):
488
+ """The core DiT transformer backbone for AudioDiT."""
489
+
490
+ def __init__(self, config: AudioDiTConfig):
491
+ super().__init__()
492
+ dim = config.dit_dim
493
+ latent_dim = config.latent_dim # 64
494
+ text_dim = config.dit_text_dim
495
+ dim_head = dim // config.dit_heads
496
+
497
+ self.config = config
498
+ self.dim = dim
499
+ self.depth = config.dit_depth
500
+ self.long_skip = config.dit_long_skip
501
+ self.adaln_type = config.dit_adaln_type
502
+ self.adaln_use_text_cond = config.dit_adaln_use_text_cond
503
+
504
+ self.time_embed = AudioDiTTimestepEmbedding(dim)
505
+ self.input_embed = AudioDiTEmbedder(latent_dim, dim)
506
+ self.text_embed = AudioDiTEmbedder(text_dim, dim)
507
+ self.rotary_embed = AudioDiTRotaryEmbedding(dim_head, 2048, base=100000.0)
508
+
509
+ self.blocks = nn.ModuleList([AudioDiTBlock(config) for _ in range(config.dit_depth)])
510
+
511
+ self.norm_out = AudioDiTAdaLayerNormZeroFinal(dim, bias=True, eps=config.dit_eps)
512
+ self.proj_out = nn.Linear(dim, latent_dim)
513
+
514
+ if config.dit_adaln_type == "global":
515
+ self.adaln_global_mlp = AudioDiTAdaLNMLP(dim, dim * 6, bias=True)
516
+
517
+ self.text_conv = config.dit_text_conv
518
+ if config.dit_text_conv:
519
+ self.text_conv_layer = nn.Sequential(
520
+ *[AudioDiTConvNeXtV2Block(dim, dim * 2, bias=config.dit_bias, eps=config.dit_eps) for _ in range(4)]
521
+ )
522
+
523
+ self.use_latent_condition = config.dit_use_latent_condition
524
+ if config.dit_use_latent_condition:
525
+ self.latent_embed = AudioDiTEmbedder(latent_dim, dim)
526
+ self.latent_cond_embedder = AudioDiTEmbedder(dim * 2, dim)
527
+
528
+ # Latent-space boundary tokens for env-spk multistream input.
529
+ # Layout: [<boe>, env_latent, <bos>, spk_latent, <bon>, target_latent].
530
+ # Each (1, 1, latent_dim), trainable nn.Parameter. Re-initialized in
531
+ # _initialize_weights() to N(0, 0.02). The trainer / inference code
532
+ # is responsible for concatenating these into the latent sequence.
533
+ self.boe_token = nn.Parameter(torch.zeros(1, 1, latent_dim))
534
+ self.bos_token = nn.Parameter(torch.zeros(1, 1, latent_dim))
535
+ self.bon_token = nn.Parameter(torch.zeros(1, 1, latent_dim))
536
+
537
+ # Text-space boundary tokens (parallel to latent ones but in the UMT5
538
+ # output space). Used in encode_multistream_text() to build:
539
+ # [<boe_text>, env_text_emb, <bos_text>, spk_text_emb, <bon_text>, target_text_emb].
540
+ # Sized to dit_text_dim (UMT5 d_model = 768 for the base config).
541
+ self.boe_text_token = nn.Parameter(torch.zeros(1, 1, text_dim))
542
+ self.bos_text_token = nn.Parameter(torch.zeros(1, 1, text_dim))
543
+ self.bon_text_token = nn.Parameter(torch.zeros(1, 1, text_dim))
544
+
545
+ self._initialize_weights()
546
+
547
+ def _initialize_weights(self):
548
+ """Zero-out AdaLN and output projection weights for stable training init."""
549
+ bias = self.config.dit_bias
550
+ if self.adaln_type == "local":
551
+ for block in self.blocks:
552
+ nn.init.constant_(block.adaln_mlp.mlp[-1].weight, 0)
553
+ if bias:
554
+ nn.init.constant_(block.adaln_mlp.mlp[-1].bias, 0)
555
+ elif self.adaln_type == "global":
556
+ nn.init.constant_(self.adaln_global_mlp.mlp[-1].weight, 0)
557
+ if bias:
558
+ nn.init.constant_(self.adaln_global_mlp.mlp[-1].bias, 0)
559
+
560
+ nn.init.constant_(self.norm_out.linear.weight, 0)
561
+ nn.init.constant_(self.proj_out.weight, 0)
562
+ if bias:
563
+ nn.init.constant_(self.norm_out.linear.bias, 0)
564
+ nn.init.constant_(self.proj_out.bias, 0)
565
+
566
+ for m in self.time_embed.modules():
567
+ if isinstance(m, nn.Linear):
568
+ nn.init.normal_(m.weight, std=0.02)
569
+ if m.bias is not None:
570
+ nn.init.constant_(m.bias, 0)
571
+ for m in self.text_embed.modules():
572
+ if isinstance(m, nn.Linear):
573
+ nn.init.normal_(m.weight, std=0.02)
574
+ if m.bias is not None:
575
+ nn.init.constant_(m.bias, 0)
576
+
577
+ # Boundary tokens: N(0, 0.02) so they carry non-trivial signal from
578
+ # step 0. HF from_pretrained's meta-init can leave new params with
579
+ # garbage (~1e36) past bf16 saturation; this re-init guarantees finite.
580
+ for tok in (
581
+ self.boe_token, self.bos_token, self.bon_token,
582
+ self.boe_text_token, self.bos_text_token, self.bon_text_token,
583
+ ):
584
+ nn.init.normal_(tok, mean=0.0, std=0.02)
585
+
586
+ def forward(
587
+ self,
588
+ x: torch.Tensor,
589
+ text: torch.Tensor,
590
+ text_len: torch.Tensor,
591
+ time: torch.Tensor,
592
+ mask: torch.BoolTensor | None = None,
593
+ cond_mask: torch.BoolTensor | None = None,
594
+ return_ith_layer: int | None = None,
595
+ latent_cond: torch.Tensor | None = None,
596
+ ) -> dict[str, torch.Tensor | None]:
597
+ dtype = next(self.parameters()).dtype
598
+ x = x.to(dtype)
599
+ text = text.to(dtype)
600
+ time = time.to(dtype)
601
+
602
+ batch = x.shape[0]
603
+ text_seq_len = text.shape[1]
604
+ if time.ndim == 0:
605
+ time = time.repeat(batch)
606
+
607
+ t = self.time_embed(time)
608
+ text = self.text_embed(text, cond_mask)
609
+ if self.text_conv:
610
+ # The text ConvNeXt contains a GRN (ConvNeXtV2) that L2-pools over the
611
+ # TIME axis. GRN assumes every position is valid (it's an image op);
612
+ # running it on a zero-padded batch makes the pool length/padding-
613
+ # dependent β†’ batched output β‰  single-sample. UMT5's own norms are
614
+ # per-token, so they're already batch-invariant. To match B=1 exactly,
615
+ # run the conv PER SAMPLE on each sequence's valid tokens (no padding
616
+ # enters the GRN), then scatter back. Robust to padding side; a no-op
617
+ # difference vs the old path when B=1 / no padding.
618
+ conv_out = torch.zeros_like(text)
619
+ for i in range(text.shape[0]):
620
+ mi = cond_mask[i]
621
+ conv_out[i][mi] = self.text_conv_layer(text[i][mi].unsqueeze(0))[0]
622
+ text = conv_out
623
+
624
+ x = self.input_embed(x, mask)
625
+ if self.use_latent_condition:
626
+ latent_cond = latent_cond.to(dtype)
627
+ latent_cond = self.latent_embed(latent_cond, mask)
628
+ x = self.latent_cond_embedder(torch.cat([x, latent_cond], dim=-1))
629
+
630
+ if self.long_skip:
631
+ x_clone = x.clone()
632
+
633
+ seq_len = x.shape[1]
634
+ rope = self.rotary_embed(x, seq_len)
635
+ cond_rope = self.rotary_embed(text, text_seq_len)
636
+
637
+ if self.adaln_type == "global":
638
+ if self.adaln_use_text_cond:
639
+ text_mean = text.sum(1) / text_len.unsqueeze(1).to(text.dtype)
640
+ norm_cond = t + text_mean
641
+ else:
642
+ norm_cond = t
643
+ adaln_mlp_out = self.adaln_global_mlp(norm_cond)
644
+ else:
645
+ adaln_mlp_out = None
646
+ norm_cond = None
647
+
648
+ hidden_state = None
649
+ for i, block in enumerate(self.blocks):
650
+ x = block(
651
+ x=x, t=t, cond=text, mask=mask, cond_mask=cond_mask,
652
+ rope=rope, cond_rope=cond_rope, adaln_global_out=adaln_mlp_out,
653
+ )
654
+ if return_ith_layer == i + 1:
655
+ hidden_state = x.clone()
656
+ if self.long_skip:
657
+ x = x + x_clone
658
+
659
+ if self.long_skip:
660
+ x = x + x_clone
661
+
662
+ x = self.norm_out(x, norm_cond if norm_cond is not None else t)
663
+ output = self.proj_out(x)
664
+ return {"last_hidden_state": output, "hidden_state": hidden_state}
665
+
666
+
667
+ # ---------------------------------------------------------------------------
668
+ # WAV-VAE components (from wav_vae.py)
669
+ # ---------------------------------------------------------------------------
670
+
671
+
672
+ def _snake_beta(x: torch.Tensor, alpha: torch.Tensor, beta: torch.Tensor) -> torch.Tensor:
673
+ return x + (1.0 / (beta + 1e-9)) * torch.sin(x * alpha).pow(2)
674
+
675
+
676
+ class AudioDiTSnakeBeta(nn.Module):
677
+ def __init__(self, in_features: int, alpha_logscale: bool = True):
678
+ super().__init__()
679
+ self.alpha_logscale = alpha_logscale
680
+ self.alpha = nn.Parameter(torch.zeros(in_features))
681
+ self.beta = nn.Parameter(torch.zeros(in_features))
682
+
683
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
684
+ alpha = self.alpha.unsqueeze(0).unsqueeze(-1)
685
+ beta = self.beta.unsqueeze(0).unsqueeze(-1)
686
+ if self.alpha_logscale:
687
+ alpha = torch.exp(alpha)
688
+ beta = torch.exp(beta)
689
+ return _snake_beta(x, alpha, beta)
690
+
691
+
692
+ def _get_vae_activation(activation: str, channels: int | None = None) -> nn.Module:
693
+ if activation == "elu":
694
+ return nn.ELU()
695
+ elif activation == "snake":
696
+ return AudioDiTSnakeBeta(channels)
697
+ elif activation == "none":
698
+ return nn.Identity()
699
+ raise ValueError(f"Unknown activation {activation}")
700
+
701
+
702
+ def _wn_conv1d(*args, **kwargs):
703
+ return weight_norm(nn.Conv1d(*args, **kwargs))
704
+
705
+
706
+ def _wn_conv_transpose1d(*args, **kwargs):
707
+ return weight_norm(nn.ConvTranspose1d(*args, **kwargs))
708
+
709
+
710
+ def _pixel_unshuffle_1d(x: torch.Tensor, factor: int) -> torch.Tensor:
711
+ b, c, w = x.size()
712
+ return x.view(b, c, w // factor, factor).permute(0, 1, 3, 2).contiguous().view(b, c * factor, w // factor)
713
+
714
+
715
+ def _pixel_shuffle_1d(x: torch.Tensor, factor: int) -> torch.Tensor:
716
+ b, c, w = x.size()
717
+ c = c // factor
718
+ return x.view(b, c, factor, w).permute(0, 1, 3, 2).contiguous().view(b, c, w * factor)
719
+
720
+
721
+ class _DownsampleShortcut(nn.Module):
722
+ def __init__(self, in_channels: int, out_channels: int, factor: int):
723
+ super().__init__()
724
+ self.factor = factor
725
+ self.group_size = in_channels * factor // out_channels
726
+ self.out_channels = out_channels
727
+
728
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
729
+ x = _pixel_unshuffle_1d(x, self.factor)
730
+ b, c, n = x.shape
731
+ return x.view(b, self.out_channels, self.group_size, n).mean(dim=2)
732
+
733
+
734
+ class _UpsampleShortcut(nn.Module):
735
+ def __init__(self, in_channels: int, out_channels: int, factor: int):
736
+ super().__init__()
737
+ self.factor = factor
738
+ self.repeats = out_channels * factor // in_channels
739
+
740
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
741
+ x = x.repeat_interleave(self.repeats, dim=1)
742
+ return _pixel_shuffle_1d(x, self.factor)
743
+
744
+
745
+ class _VaeResidualUnit(nn.Module):
746
+ def __init__(self, in_channels: int, out_channels: int, dilation: int, kernel_size: int = 7, use_snake: bool = False):
747
+ super().__init__()
748
+ padding = (dilation * (kernel_size - 1)) // 2
749
+ act = "snake" if use_snake else "elu"
750
+ self.layers = nn.Sequential(
751
+ _get_vae_activation(act, channels=out_channels),
752
+ _wn_conv1d(in_channels, out_channels, kernel_size, dilation=dilation, padding=padding),
753
+ _get_vae_activation(act, channels=out_channels),
754
+ _wn_conv1d(out_channels, out_channels, kernel_size=1),
755
+ )
756
+
757
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
758
+ return x + self.layers(x)
759
+
760
+
761
+ class _VaeEncoderBlock(nn.Module):
762
+ def __init__(self, in_ch: int, out_ch: int, stride: int, use_snake: bool = False, downsample_shortcut: str = "none"):
763
+ super().__init__()
764
+ layers = []
765
+ for d in [1, 3, 9]:
766
+ layers.append(_VaeResidualUnit(in_ch, in_ch, dilation=d, use_snake=use_snake))
767
+ act = "snake" if use_snake else "elu"
768
+ layers.append(_get_vae_activation(act, channels=in_ch))
769
+ layers.append(_wn_conv1d(in_ch, out_ch, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2)))
770
+ self.layers = nn.Sequential(*layers)
771
+ self.res = _DownsampleShortcut(in_ch, out_ch, stride) if downsample_shortcut == "averaging" else None
772
+
773
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
774
+ if self.res is not None:
775
+ return self.layers(x) + self.res(x)
776
+ return self.layers(x)
777
+
778
+
779
+ class _VaeDecoderBlock(nn.Module):
780
+ def __init__(self, in_ch: int, out_ch: int, stride: int, use_snake: bool = False, upsample_shortcut: str = "none"):
781
+ super().__init__()
782
+ act = "snake" if use_snake else "elu"
783
+ layers = [
784
+ _get_vae_activation(act, channels=in_ch),
785
+ _wn_conv_transpose1d(in_ch, out_ch, kernel_size=2 * stride, stride=stride, padding=math.ceil(stride / 2)),
786
+ ]
787
+ for d in [1, 3, 9]:
788
+ layers.append(_VaeResidualUnit(out_ch, out_ch, dilation=d, use_snake=use_snake))
789
+ self.layers = nn.Sequential(*layers)
790
+ self.res = _UpsampleShortcut(in_ch, out_ch, stride) if upsample_shortcut == "duplicating" else None
791
+
792
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
793
+ if self.res is not None:
794
+ return self.layers(x) + self.res(x)
795
+ return self.layers(x)
796
+
797
+
798
+ class AudioDiTVaeEncoder(nn.Module):
799
+ def __init__(self, config: AudioDiTVaeConfig):
800
+ super().__init__()
801
+ c_mults = [1] + config.c_mults
802
+ ch = config.channels
803
+ layers = [_wn_conv1d(config.in_channels, c_mults[0] * ch, kernel_size=7, padding=3)]
804
+ for i in range(len(c_mults) - 1):
805
+ layers.append(_VaeEncoderBlock(c_mults[i] * ch, c_mults[i + 1] * ch, config.strides[i], use_snake=config.use_snake, downsample_shortcut=config.downsample_shortcut))
806
+ layers.append(_wn_conv1d(c_mults[-1] * ch, config.encoder_latent_dim, kernel_size=3, padding=1))
807
+ self.layers = nn.Sequential(*layers)
808
+
809
+ if config.out_shortcut == "averaging":
810
+ self.shortcut = _DownsampleShortcut(c_mults[-1] * ch, config.encoder_latent_dim, 1)
811
+ else:
812
+ self.shortcut = None
813
+
814
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
815
+ if self.shortcut is None:
816
+ return self.layers(x)
817
+ x = self.layers[:-1](x)
818
+ return self.layers[-1](x) + self.shortcut(x)
819
+
820
+
821
+ class AudioDiTVaeDecoder(nn.Module):
822
+ def __init__(self, config: AudioDiTVaeConfig):
823
+ super().__init__()
824
+ c_mults = [1] + config.c_mults
825
+ ch = config.channels
826
+
827
+ if config.in_shortcut == "duplicating":
828
+ self.shortcut = _UpsampleShortcut(config.latent_dim, c_mults[-1] * ch, 1)
829
+ else:
830
+ self.shortcut = None
831
+
832
+ layers = [_wn_conv1d(config.latent_dim, c_mults[-1] * ch, kernel_size=7, padding=3)]
833
+ for i in range(len(c_mults) - 1, 0, -1):
834
+ layers.append(_VaeDecoderBlock(c_mults[i] * ch, c_mults[i - 1] * ch, config.strides[i - 1], use_snake=config.use_snake, upsample_shortcut=config.upsample_shortcut))
835
+ act = "snake" if config.use_snake else "elu"
836
+ layers.append(_get_vae_activation(act, channels=c_mults[0] * ch))
837
+ layers.append(_wn_conv1d(c_mults[0] * ch, config.in_channels, kernel_size=7, padding=3, bias=False))
838
+ if config.final_tanh:
839
+ layers.append(nn.Tanh())
840
+ else:
841
+ layers.append(nn.Identity())
842
+ self.layers = nn.Sequential(*layers)
843
+
844
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
845
+ if self.shortcut is None:
846
+ return self.layers(x)
847
+ x_short = self.shortcut(x) + self.layers[0](x)
848
+ return self.layers[1:](x_short)
849
+
850
+
851
+ class AudioDiTVae(nn.Module):
852
+ """WAV-VAE audio autoencoder with VAE bottleneck and scale factor.
853
+
854
+ The original checkpoint runs encode/decode in **float16** (``model_half=True``
855
+ in ``AutoencoderPretransform``). We replicate this behaviour so that the
856
+ outputs are numerically identical to the original codebase.
857
+ """
858
+
859
+ def __init__(self, config: AudioDiTVaeConfig):
860
+ super().__init__()
861
+ self.config = config
862
+ self.encoder = AudioDiTVaeEncoder(config)
863
+ self.decoder = AudioDiTVaeDecoder(config)
864
+ self.scale = config.scale
865
+ self.downsampling_ratio = config.downsampling_ratio
866
+
867
+ def to_half(self):
868
+ """Convert encoder and decoder weights to float16 (matching original behaviour)."""
869
+ self.encoder.half()
870
+ self.decoder.half()
871
+ return self
872
+
873
+ def encode(self, audio: torch.Tensor) -> torch.Tensor:
874
+ """Encode audio to latent space.
875
+
876
+ Runs encoder **and** VAE bottleneck in float16 when weights are float16,
877
+ matching the original ``AutoencoderPretransform(model_half=True)`` +
878
+ ``AudioAutoencoder.encode`` behaviour where the bottleneck operates on
879
+ the fp16 encoder output before the final ``.float()`` conversion.
880
+
881
+ Args:
882
+ audio: ``(batch, 1, num_samples)`` raw waveform.
883
+
884
+ Returns:
885
+ Latent tensor ``(batch, latent_dim, num_frames)`` in float32.
886
+ """
887
+ is_half = next(self.encoder.parameters()).dtype == torch.float16
888
+ if is_half:
889
+ audio = audio.half()
890
+ latents = self.encoder(audio)
891
+ # VAE bottleneck runs in the same dtype as encoder output (fp16)
892
+ # to match original: bottleneck.encode(latents) happens before .float()
893
+ mean, scale_param = latents.chunk(2, dim=1)
894
+ stdev = F.softplus(scale_param) + 1e-4
895
+ latents = torch.randn_like(mean) * stdev + mean
896
+ # Convert to fp32 after bottleneck, matching original AutoencoderPretransform
897
+ if is_half:
898
+ latents = latents.float()
899
+ return latents / self.scale
900
+
901
+ def decode(self, latents: torch.Tensor) -> torch.Tensor:
902
+ """Decode latents to audio waveform.
903
+
904
+ Runs decoder in float16 when weights are float16, matching the original
905
+ ``AutoencoderPretransform(model_half=True)`` behaviour.
906
+
907
+ Args:
908
+ latents: ``(batch, latent_dim, num_frames)``.
909
+
910
+ Returns:
911
+ Waveform tensor ``(batch, 1, num_samples)`` in float32.
912
+ """
913
+ z = latents * self.scale
914
+ is_half = next(self.decoder.parameters()).dtype == torch.float16
915
+ if is_half:
916
+ z = z.half()
917
+ decoded = self.decoder(z)
918
+ if is_half:
919
+ decoded = decoded.float()
920
+ return decoded
921
+
922
+
923
+ # ---------------------------------------------------------------------------
924
+ # Top-level AudioDiTModel
925
+ # ---------------------------------------------------------------------------
926
+
927
+
928
+ class AudioDiTPreTrainedModel(PreTrainedModel):
929
+ config_class = AudioDiTConfig
930
+ base_model_prefix = "audiodit"
931
+ supports_gradient_checkpointing = True
932
+ _supports_sdpa = True
933
+
934
+ def _init_weights(self, module):
935
+ if isinstance(module, nn.Linear):
936
+ nn.init.normal_(module.weight, std=0.02)
937
+ if module.bias is not None:
938
+ nn.init.zeros_(module.bias)
939
+ elif isinstance(module, nn.Embedding):
940
+ nn.init.normal_(module.weight, std=0.02)
941
+ elif isinstance(module, AudioDiTTransformer):
942
+ # Re-init the boundary tokens after HF from_pretrained β€” they're
943
+ # nn.Parameter (not modules) and don't appear in the pretrained
944
+ # ckpt for the env-tts task, so HF's meta-init path leaves them
945
+ # with uninitialized memory (~1e32) past bf16 saturation.
946
+ for tok_name in (
947
+ "boe_token", "bos_token", "bon_token",
948
+ "boe_text_token", "bos_text_token", "bon_text_token",
949
+ ):
950
+ tok = getattr(module, tok_name, None)
951
+ if tok is not None:
952
+ nn.init.normal_(tok, mean=0.0, std=0.02)
953
+
954
+
955
+ class AudioDiTModel(AudioDiTPreTrainedModel):
956
+ """AudioDiT: Conditional Flow Matching TTS model with DiT backbone, UMT5 text encoder, and WAV-VAE.
957
+
958
+ All sub-models (text_encoder, transformer, vae) are constructed from config
959
+ and their weights are loaded together via ``from_pretrained``.
960
+
961
+ Example::
962
+
963
+ model = AudioDiTModel.from_pretrained("hf_audiodit_1b")
964
+ tokenizer = AutoTokenizer.from_pretrained(model.config.text_encoder_model)
965
+ output = model(text=["Hello world"], tokenizer=tokenizer)
966
+ waveform = output.waveform # (B, num_samples)
967
+ """
968
+
969
+ def __init__(self, config: AudioDiTConfig):
970
+ super().__init__(config)
971
+ self.config = config
972
+
973
+ # Text encoder β€” constructed from embedded config, weights loaded by from_pretrained
974
+ from transformers import UMT5EncoderModel, UMT5Config
975
+
976
+ if config.text_encoder_config is not None:
977
+ self.text_encoder = UMT5EncoderModel(config.text_encoder_config)
978
+ else:
979
+ te_config = UMT5Config.from_pretrained(config.text_encoder_model)
980
+ self.text_encoder = UMT5EncoderModel(te_config)
981
+ self.text_encoder.requires_grad_(False)
982
+
983
+ # DiT transformer
984
+ self.transformer = AudioDiTTransformer(config)
985
+
986
+ # WAV-VAE
987
+ self.vae = AudioDiTVae(config.vae_config)
988
+ self.vae.requires_grad_(False)
989
+
990
+ self.post_init()
991
+
992
+ def encode_text(
993
+ self,
994
+ input_ids: torch.LongTensor,
995
+ attention_mask: torch.LongTensor,
996
+ ) -> torch.FloatTensor:
997
+ """Encode tokenized text using the UMT5 text encoder.
998
+
999
+ Args:
1000
+ input_ids: Token ids ``(batch, seq_len)``.
1001
+ attention_mask: Attention mask ``(batch, seq_len)``.
1002
+
1003
+ Returns:
1004
+ Text embeddings ``(batch, seq_len, text_dim)`` in float32.
1005
+ """
1006
+ with torch.no_grad():
1007
+ output = self.text_encoder(
1008
+ input_ids=input_ids,
1009
+ attention_mask=attention_mask,
1010
+ output_hidden_states=True,
1011
+ )
1012
+ emb = output.last_hidden_state
1013
+ d_model = self.text_encoder.config.d_model
1014
+
1015
+ if self.config.text_norm_feat:
1016
+ emb = F.layer_norm(emb, (d_model,), eps=1e-6)
1017
+
1018
+ if self.config.text_add_embed:
1019
+ first_hidden = output.hidden_states[0]
1020
+ if self.config.text_norm_feat:
1021
+ first_hidden = F.layer_norm(first_hidden, (d_model,), eps=1e-6)
1022
+ emb = emb + first_hidden
1023
+
1024
+ return emb.float()
1025
+
1026
+ def encode_multistream_text(
1027
+ self,
1028
+ env_input_ids: torch.LongTensor,
1029
+ env_attn: torch.LongTensor,
1030
+ spk_input_ids: torch.LongTensor,
1031
+ spk_attn: torch.LongTensor,
1032
+ target_input_ids: torch.LongTensor,
1033
+ target_attn: torch.LongTensor,
1034
+ drop_env_text: torch.BoolTensor | None = None,
1035
+ drop_spk_text: torch.BoolTensor | None = None,
1036
+ drop_target_text: torch.BoolTensor | None = None,
1037
+ ) -> tuple[torch.FloatTensor, torch.BoolTensor, torch.LongTensor]:
1038
+ """Encode three text streams and assemble with boundary tokens.
1039
+
1040
+ Each segment is independently tokenized + frozen-UMT5 encoded, then
1041
+ concatenated as:
1042
+ [<boe_text>, env_emb, <bos_text>, spk_emb, <bon_text>, tgt_emb]
1043
+ Boundary tokens are always visible in the output mask. If a per-sample
1044
+ drop flag is set, that segment's embedding values are zeroed in place
1045
+ (position + boundary preserved, content zeroed).
1046
+
1047
+ Args:
1048
+ env_input_ids / env_attn: (B, S_env)
1049
+ spk_input_ids / spk_attn: (B, S_spk)
1050
+ target_input_ids / target_attn: (B, S_tgt)
1051
+ drop_env_text / drop_spk_text / drop_target_text: (B,) bool or None.
1052
+
1053
+ Returns:
1054
+ text_emb: (B, 3 + S_env + S_spk + S_tgt, dit_text_dim) float32
1055
+ text_mask: (B, 3 + S_env + S_spk + S_tgt) bool β€” boundary positions
1056
+ always True; segment positions follow their attention masks
1057
+ (NOT cleared by drop flags, consistent with dit-training CFG
1058
+ null-pass convention which preserves cond_mask).
1059
+ text_len: (B,) long β€” sum of text_mask along dim=1.
1060
+ """
1061
+ device = self.device
1062
+
1063
+ # Concat-batch UMT5 encode: pad three streams to a common seq_len then
1064
+ # run ONE encode_text call on shape (3B, S_max). Splits back per-stream
1065
+ # at the end. Saves 2 kernel-launch round-trips per cfm_step.
1066
+ env_ids = env_input_ids.to(device)
1067
+ spk_ids = spk_input_ids.to(device)
1068
+ tgt_ids = target_input_ids.to(device)
1069
+ env_msk = env_attn.to(device)
1070
+ spk_msk = spk_attn.to(device)
1071
+ tgt_msk = target_attn.to(device)
1072
+ S_env, S_spk, S_tgt = env_ids.shape[1], spk_ids.shape[1], tgt_ids.shape[1]
1073
+ S_max = max(S_env, S_spk, S_tgt)
1074
+
1075
+ def _pad(t, s):
1076
+ return F.pad(t, (0, s - t.shape[1])) if t.shape[1] < s else t
1077
+
1078
+ all_ids = torch.cat([_pad(env_ids, S_max), _pad(spk_ids, S_max), _pad(tgt_ids, S_max)], dim=0)
1079
+ all_msk = torch.cat([_pad(env_msk, S_max), _pad(spk_msk, S_max), _pad(tgt_msk, S_max)], dim=0)
1080
+ all_emb = self.encode_text(all_ids, all_msk) # (3B, S_max, D)
1081
+
1082
+ B = env_ids.shape[0]
1083
+ env_emb = all_emb[0 : B , :S_env, :]
1084
+ spk_emb = all_emb[B : 2*B , :S_spk, :]
1085
+ tgt_emb = all_emb[2*B : 3*B , :S_tgt, :]
1086
+ text_dim = env_emb.shape[-1]
1087
+
1088
+ # Apply text-side drop (position-preserving content zero).
1089
+ if drop_env_text is not None:
1090
+ env_emb = env_emb * (~drop_env_text.to(device)).view(B, 1, 1).to(env_emb.dtype)
1091
+ if drop_spk_text is not None:
1092
+ spk_emb = spk_emb * (~drop_spk_text.to(device)).view(B, 1, 1).to(spk_emb.dtype)
1093
+ if drop_target_text is not None:
1094
+ tgt_emb = tgt_emb * (~drop_target_text.to(device)).view(B, 1, 1).to(tgt_emb.dtype)
1095
+
1096
+ # Resolve PEFT-wrapped transformer to access the boundary nn.Parameter.
1097
+ src = getattr(self.transformer, "base_model", None)
1098
+ src = src.model if src is not None else self.transformer
1099
+ boe_t = src.boe_text_token.to(device=device, dtype=env_emb.dtype)
1100
+ bos_t = src.bos_text_token.to(device=device, dtype=env_emb.dtype)
1101
+ bon_t = src.bon_text_token.to(device=device, dtype=env_emb.dtype)
1102
+ # ── Tight per-sample assembly (batch-invariant positions) ─────────
1103
+ # Pack each sample's VALID tokens contiguously [boe|env|bos|spk|bon|tgt]
1104
+ # and end-pad to the batch max. A sample's assembled text β€” and thus its
1105
+ # cond_rope positions β€” is therefore INDEPENDENT of other batch members'
1106
+ # stream lengths, so batched == single-sample.
1107
+ #
1108
+ # (The earlier version padded each stream to the batch-max per-stream
1109
+ # length and concatenated WITH that padding inside, interleaving padding
1110
+ # mid-sequence. For any sample shorter than the batch max this shifted the
1111
+ # bos/bon + spk/tgt positions β†’ cond_rope mismatch at B>1 β†’ corrupted
1112
+ # generation that compounds over the ODE. B=1 is unaffected and stays
1113
+ # byte-identical; the single-stream encode_text path is untouched.)
1114
+ boe1, bos1, bon1 = boe_t.reshape(1, text_dim), bos_t.reshape(1, text_dim), bon_t.reshape(1, text_dim)
1115
+ env_m = env_attn.to(device).bool()
1116
+ spk_m = spk_attn.to(device).bool()
1117
+ tgt_m = target_attn.to(device).bool()
1118
+ seqs = [
1119
+ torch.cat([
1120
+ boe1, env_emb[i][env_m[i]],
1121
+ bos1, spk_emb[i][spk_m[i]],
1122
+ bon1, tgt_emb[i][tgt_m[i]],
1123
+ ], dim=0) # (L_i, text_dim), tight
1124
+ for i in range(B)
1125
+ ]
1126
+ L_max = max(s.shape[0] for s in seqs)
1127
+ text_emb = torch.stack(
1128
+ [F.pad(s, (0, 0, 0, L_max - s.shape[0])) for s in seqs], dim=0) # (B, L_max, text_dim)
1129
+ text_mask = torch.zeros(B, L_max, dtype=torch.bool, device=device)
1130
+ for i, s in enumerate(seqs):
1131
+ text_mask[i, : s.shape[0]] = True
1132
+ text_len = text_mask.sum(dim=1).long()
1133
+ return text_emb.float(), text_mask, text_len
1134
+
1135
+ def encode_prompt_audio(self, prompt_audio: torch.FloatTensor) -> tuple[torch.FloatTensor, int]:
1136
+ """Encode prompt audio to latent space.
1137
+
1138
+ Args:
1139
+ prompt_audio: Waveform tensor ``(batch, 1, num_samples)`` or ``(batch, num_samples)``.
1140
+
1141
+ Returns:
1142
+ Tuple of (prompt_latent ``(batch, num_frames, latent_dim)``, prompt_duration_frames).
1143
+ """
1144
+ full_hop = self.config.latent_hop
1145
+ off = 3
1146
+ wav = prompt_audio.to(self.device)
1147
+ if wav.ndim == 2:
1148
+ wav = wav.unsqueeze(1)
1149
+ if wav.shape[-1] % full_hop != 0:
1150
+ wav = F.pad(wav, (0, full_hop - wav.shape[-1] % full_hop))
1151
+ wav = F.pad(wav, (0, full_hop * off))
1152
+ latent = self.vae.encode(wav)
1153
+ if off != 0:
1154
+ latent = latent[..., :-off]
1155
+ prompt_duration_frames = latent.shape[-1]
1156
+ return latent.permute(0, 2, 1), prompt_duration_frames
1157
+
1158
+ @torch.no_grad()
1159
+ def forward(
1160
+ self,
1161
+ input_ids: torch.LongTensor | None = None,
1162
+ attention_mask: torch.LongTensor | None = None,
1163
+ text_embedding: torch.FloatTensor | None = None,
1164
+ text_mask: torch.BoolTensor | None = None,
1165
+ prompt_audio: torch.FloatTensor | None = None,
1166
+ prompt_latent: torch.FloatTensor | None = None,
1167
+ prompt_lens: torch.LongTensor | None = None,
1168
+ duration: int | None = None,
1169
+ steps: int = 16,
1170
+ cfg_strength: float = 4.0,
1171
+ guidance_method: str = "cfg",
1172
+ return_dict: bool = True,
1173
+ ) -> AudioDiTOutput | tuple:
1174
+ """Generate audio from text (and optional prompt audio).
1175
+
1176
+ Args:
1177
+ input_ids: Tokenized text ``(batch, seq_len)``. Use with ``attention_mask``.
1178
+ attention_mask: Attention mask ``(batch, seq_len)``.
1179
+ text_embedding: Pre-computed text embeddings ``(batch, seq_len, dim)``. Alternative to input_ids.
1180
+ When supplied alongside ``text_mask`` the model bypasses ``encode_text``
1181
+ entirely β€” used by the env-tts pipeline which builds a multi-stream
1182
+ text embedding via ``encode_multistream_text``.
1183
+ text_mask: Optional bool mask ``(batch, seq_len)`` for ``text_embedding``.
1184
+ Required when ``text_embedding`` is supplied without ``attention_mask``.
1185
+ prompt_audio: Optional prompt audio ``(batch, 1, num_samples)`` for voice cloning.
1186
+ prompt_latent: Optional pre-assembled prompt latent ``(batch, T_prompt, latent_dim)``,
1187
+ bypassing ``encode_prompt_audio``. Use this for env-tts multi-stream
1188
+ latents already containing latent-space boundary tokens.
1189
+ Mutually exclusive with ``prompt_audio`` β€” if both are given,
1190
+ ``prompt_latent`` wins.
1191
+ duration: Target duration in latent frames (prompt + gen). If None, uses max_wav_duration.
1192
+ steps: Number of ODE Euler steps (default 16).
1193
+ cfg_strength: Guidance strength for CFG/APG (default 4.0).
1194
+ guidance_method: ``"cfg"`` or ``"apg"`` (default ``"cfg"``).
1195
+ return_dict: Whether to return ``AudioDiTOutput`` or tuple.
1196
+ """
1197
+ device = self.device
1198
+ sr = self.config.sampling_rate
1199
+ full_hop = self.config.latent_hop
1200
+ max_duration_frames = int(self.config.max_wav_duration * sr // full_hop)
1201
+ repa_layer = self.config.repa_dit_layer
1202
+
1203
+ # ── text encoding ─────────────────────────────────────────────
1204
+ if text_embedding is not None:
1205
+ text_condition = text_embedding.to(device, torch.float32)
1206
+ if text_mask is not None:
1207
+ text_condition_len = text_mask.to(device).sum(dim=1).long()
1208
+ elif attention_mask is not None:
1209
+ text_condition_len = attention_mask.sum(dim=1).to(device)
1210
+ else:
1211
+ text_condition_len = torch.full(
1212
+ (text_condition.shape[0],), text_condition.shape[1], device=device,
1213
+ )
1214
+ else:
1215
+ text_condition = self.encode_text(
1216
+ input_ids.to(device), attention_mask.to(device),
1217
+ )
1218
+ text_condition_len = attention_mask.sum(dim=1).to(device)
1219
+
1220
+ batch = text_condition.shape[0]
1221
+
1222
+ # ── prompt latent / audio encoding ────────────────────────────
1223
+ # Precedence: explicit ``prompt_latent`` > ``prompt_audio`` > empty.
1224
+ # ``prompt_latent`` is used by the env-tts pipeline which builds a
1225
+ # multi-stream latent [boe|z_env|bos|z_spk|bon] externally; the
1226
+ # ``prompt_audio`` path is the single-stream voice-cloning default.
1227
+ has_prompt = prompt_latent is not None or prompt_audio is not None
1228
+ if prompt_latent is not None:
1229
+ prompt_latent = prompt_latent.to(device)
1230
+ prompt_dur = prompt_latent.shape[1]
1231
+ elif prompt_audio is not None:
1232
+ prompt_latent, prompt_dur = self.encode_prompt_audio(prompt_audio)
1233
+ else:
1234
+ prompt_latent = torch.empty(batch, 0, self.config.latent_dim, device=device)
1235
+ prompt_dur = 0
1236
+
1237
+ # ── duration ──────────────────────────────────────────────────
1238
+ # ``duration`` may be a scalar (uniform, the single-sample path) OR a
1239
+ # per-sample 1-D tensor / list of TOTAL frames (prompt+gen). The latter
1240
+ # enables BATCHED generation of variable-length samples β€” callers pad all
1241
+ # prompts to a common width (so ``prompt_dur`` stays uniform) and pass the
1242
+ # per-sample total lengths here; the transformer ``mask`` + per-sample
1243
+ # ``y0`` already handle ragged gen lengths.
1244
+ if duration is None:
1245
+ duration = max_duration_frames
1246
+ if torch.is_tensor(duration) or isinstance(duration, (list, tuple)):
1247
+ duration_tensor = torch.as_tensor(duration, device=device, dtype=torch.long).clamp(max=max_duration_frames)
1248
+ else:
1249
+ duration_tensor = torch.full((batch,), min(int(duration), max_duration_frames),
1250
+ device=device, dtype=torch.long)
1251
+ max_dur = int(duration_tensor.max().item())
1252
+
1253
+ # ── masks & conditioning ──────────────────────────────────────
1254
+ mask = lens_to_mask(duration_tensor, length=max_dur)
1255
+ if text_mask is not None:
1256
+ text_cond_mask = text_mask.to(device).bool()
1257
+ else:
1258
+ text_cond_mask = lens_to_mask(text_condition_len, length=text_condition.shape[1])
1259
+
1260
+ neg_text = torch.zeros_like(text_condition)
1261
+ neg_text_len = text_condition_len
1262
+
1263
+ # ``prompt_lens`` (B,) gives each sample's REAL prompt length so the gen
1264
+ # region starts immediately after that sample's ``bon`` (no padding between
1265
+ # the boundary token and gen). When None, the single-sample uniform path is
1266
+ # used unchanged. ``latent_cond`` is the real prompt at [0:T_p_i] then zeros.
1267
+ latent_len = prompt_dur
1268
+ prompt_mask = None
1269
+ if prompt_lens is not None:
1270
+ prompt_lens = torch.as_tensor(prompt_lens, device=device, dtype=torch.long)
1271
+ prompt_mask = lens_to_mask(prompt_lens, length=max_dur) # (B, max_dur)
1272
+ if has_prompt:
1273
+ latent_cond = F.pad(prompt_latent, (0, 0, 0, max_dur - prompt_latent.shape[1]))
1274
+ empty_latent_cond = torch.zeros_like(latent_cond)
1275
+ else:
1276
+ latent_cond = torch.zeros(batch, max_dur, self.config.latent_dim, device=device)
1277
+ empty_latent_cond = latent_cond
1278
+
1279
+ # ── APG buffer ────────────────────────────────────────────────
1280
+ if guidance_method == "apg":
1281
+ if prompt_mask is not None:
1282
+ raise NotImplementedError("APG guidance unsupported with batched prompt_lens; use cfg.")
1283
+ apg_buffer = _MomentumBuffer(momentum=-0.3)
1284
+
1285
+ # ── ODE function ──────────────────────────────────────────────
1286
+ def fn(t, x):
1287
+ if prompt_mask is not None: # per-sample prompt region (in-place reset each step)
1288
+ x[prompt_mask] = (prompt_noise * (1 - t) + latent_cond * t)[prompt_mask]
1289
+ else:
1290
+ x[:, :latent_len] = prompt_noise * (1 - t) + latent_cond[:, :latent_len] * t
1291
+ output = self.transformer(
1292
+ x=x, text=text_condition, text_len=text_condition_len, time=t,
1293
+ mask=mask, cond_mask=text_cond_mask,
1294
+ return_ith_layer=repa_layer, latent_cond=latent_cond,
1295
+ )
1296
+ pred = output["last_hidden_state"]
1297
+
1298
+ if cfg_strength < 1e-5:
1299
+ return pred
1300
+
1301
+ if prompt_mask is not None:
1302
+ x[prompt_mask] = 0
1303
+ else:
1304
+ x[:, :latent_len] = 0
1305
+ null_output = self.transformer(
1306
+ x=x, text=neg_text, text_len=neg_text_len, time=t,
1307
+ mask=mask, cond_mask=text_cond_mask,
1308
+ return_ith_layer=repa_layer, latent_cond=empty_latent_cond,
1309
+ )
1310
+ null_pred = null_output["last_hidden_state"]
1311
+
1312
+ if guidance_method == "cfg":
1313
+ return pred + (pred - null_pred) * cfg_strength
1314
+
1315
+ # APG (single-sample path only)
1316
+ x_s = x[:, latent_len:]
1317
+ pred_s = pred[:, latent_len:]
1318
+ null_s = null_pred[:, latent_len:]
1319
+ pred_sample = x_s + (1 - t) * pred_s
1320
+ null_sample = x_s + (1 - t) * null_s
1321
+ out = _apg_forward(
1322
+ pred_sample, null_sample, cfg_strength, apg_buffer,
1323
+ eta=0.5, norm_threshold=0.0, dims=[-1, -2],
1324
+ )
1325
+ out = (out - x_s) / (1 - t)
1326
+ return F.pad(out, (0, 0, latent_len, 0), value=0.0)
1327
+
1328
+ # ── initial noise ─────────────────────────────────────────────
1329
+ y0 = []
1330
+ for dur in duration_tensor:
1331
+ noise = torch.randn(dur.item(), self.config.latent_dim, device=device)
1332
+ y0.append(noise)
1333
+ y0 = pad_sequence(y0, padding_value=0, batch_first=True)
1334
+
1335
+ # ── ODE solve ─────────────────────────────────────────────────
1336
+ t = torch.linspace(0, 1, steps, device=device)
1337
+ prompt_noise = y0.clone() if prompt_mask is not None else y0[:, :latent_len].clone()
1338
+ trajectory = odeint_euler(fn, y0, t)
1339
+ sampled = trajectory[-1]
1340
+
1341
+ # ── extract gen region + decode ───────────────────────────────
1342
+ if prompt_mask is not None:
1343
+ # Decode each sample's gen latent at its EXACT length (decoding a
1344
+ # zero-PADDED batch latent bleeds VAE-decoder artifacts into the valid
1345
+ # audio), then zero-pad the waveforms; the caller trims to true length.
1346
+ wavs = []
1347
+ for b in range(batch):
1348
+ g = sampled[b, int(prompt_lens[b]):int(duration_tensor[b])] # (T_gen, D)
1349
+ gl = g.permute(1, 0).unsqueeze(0).float() # (1, D, T_gen)
1350
+ wavs.append(self.vae.decode(gl).reshape(-1)) # (T_gen*hop,)
1351
+ max_w = max((w.shape[0] for w in wavs), default=1)
1352
+ waveform = torch.stack([F.pad(w, (0, max_w - w.shape[0])) for w in wavs], dim=0)
1353
+ pred_latent = None
1354
+ else:
1355
+ pred_latent = sampled
1356
+ if has_prompt:
1357
+ pred_latent = pred_latent[:, prompt_dur:]
1358
+ pred_latent = pred_latent.permute(0, 2, 1).float()
1359
+ waveform = self.vae.decode(pred_latent).squeeze(1)
1360
+
1361
+ if not return_dict:
1362
+ return (waveform, pred_latent)
1363
+ return AudioDiTOutput(waveform=waveform, latent=pred_latent)
1364
+
1365
+
1366
+ # ---------------------------------------------------------------------------
1367
+ # APG helpers (from model/cfm.py β€” Adaptive Projected Guidance)
1368
+ # ---------------------------------------------------------------------------
1369
+
1370
+
1371
+ class _MomentumBuffer:
1372
+ def __init__(self, momentum: float = -0.75):
1373
+ self.momentum = momentum
1374
+ self.running_average = 0
1375
+
1376
+ def update(self, update_value: torch.Tensor):
1377
+ new_average = self.momentum * self.running_average
1378
+ self.running_average = update_value + new_average
1379
+
1380
+
1381
+ def _project(v0: torch.Tensor, v1: torch.Tensor, dims=(-1, -2)):
1382
+ dtype = v0.dtype
1383
+ device_type = v0.device.type
1384
+ if device_type == "mps":
1385
+ v0, v1 = v0.cpu(), v1.cpu()
1386
+ v0, v1 = v0.double(), v1.double()
1387
+ v1 = F.normalize(v1, dim=dims)
1388
+ v0_parallel = (v0 * v1).sum(dim=dims, keepdim=True) * v1
1389
+ v0_orthogonal = v0 - v0_parallel
1390
+ return v0_parallel.to(dtype).to(device_type), v0_orthogonal.to(dtype).to(device_type)
1391
+
1392
+
1393
+ def _apg_forward(pred_cond, pred_uncond, guidance_scale, momentum_buffer=None, eta=0.0, norm_threshold=2.5, dims=(-1, -2)):
1394
+ diff = pred_cond - pred_uncond
1395
+ if momentum_buffer is not None:
1396
+ momentum_buffer.update(diff)
1397
+ diff = momentum_buffer.running_average
1398
+ if norm_threshold > 0:
1399
+ ones = torch.ones_like(diff)
1400
+ diff_norm = diff.norm(p=2, dim=dims, keepdim=True)
1401
+ scale_factor = torch.minimum(ones, norm_threshold / diff_norm)
1402
+ diff = diff * scale_factor
1403
+ diff_parallel, diff_orthogonal = _project(diff, pred_cond, dims)
1404
+ normalized_update = diff_orthogonal + eta * diff_parallel
1405
+ return pred_cond + guidance_scale * normalized_update
1406
+
1407
+
1408
+ __all__ = [
1409
+ "AudioDiTConfig",
1410
+ "AudioDiTVaeConfig",
1411
+ "AudioDiTOutput",
1412
+ "AudioDiTPreTrainedModel",
1413
+ "AudioDiTModel",
1414
+ "AudioDiTTransformer",
1415
+ "AudioDiTVae",
1416
+ ]