owenisas commited on
Commit
6215e7d
·
verified ·
1 Parent(s): 0a91792

Vendor stable-audio-3 for ZeroGPU

Browse files
Files changed (38) hide show
  1. README.md +8 -0
  2. STABLE_AUDIO_3_LICENSE +21 -0
  3. requirements.txt +3 -0
  4. stable_audio_3/__init__.py +2 -0
  5. stable_audio_3/cli.py +292 -0
  6. stable_audio_3/data/__init__.py +0 -0
  7. stable_audio_3/data/dataset.py +510 -0
  8. stable_audio_3/data/utils.py +257 -0
  9. stable_audio_3/factory.py +157 -0
  10. stable_audio_3/inference/__init__.py +0 -0
  11. stable_audio_3/inference/audio_utils.py +98 -0
  12. stable_audio_3/inference/distribution_shift.py +198 -0
  13. stable_audio_3/inference/sampling.py +523 -0
  14. stable_audio_3/interface/__init__.py +0 -0
  15. stable_audio_3/interface/aeiou.py +303 -0
  16. stable_audio_3/interface/diffusion_cond.py +644 -0
  17. stable_audio_3/interface/reprompt.py +481 -0
  18. stable_audio_3/loading_utils.py +86 -0
  19. stable_audio_3/model.py +554 -0
  20. stable_audio_3/model_configs.py +116 -0
  21. stable_audio_3/models/__init__.py +0 -0
  22. stable_audio_3/models/autoencoders.py +639 -0
  23. stable_audio_3/models/blocks.py +84 -0
  24. stable_audio_3/models/bottleneck.py +71 -0
  25. stable_audio_3/models/conditioners.py +318 -0
  26. stable_audio_3/models/diffusion.py +253 -0
  27. stable_audio_3/models/dit.py +643 -0
  28. stable_audio_3/models/inpainting.py +242 -0
  29. stable_audio_3/models/lora/__init__.py +28 -0
  30. stable_audio_3/models/lora/loader.py +112 -0
  31. stable_audio_3/models/lora/model.py +471 -0
  32. stable_audio_3/models/lora/utils.py +420 -0
  33. stable_audio_3/models/pretransforms.py +84 -0
  34. stable_audio_3/models/transformer.py +1272 -0
  35. stable_audio_3/models/utils.py +19 -0
  36. stable_audio_3/training/diffusion.py +1143 -0
  37. stable_audio_3/training/utils.py +504 -0
  38. stable_audio_3/verbose.py +20 -0
README.md CHANGED
@@ -42,3 +42,11 @@ Base checkpoints are not gated, but they are intended mainly for fine-tuning and
42
  - `SAME-L` is GPU-first; `SAME-S` can be used for CPU autoencoder round trips.
43
 
44
  The Space is configured with `suggested_hardware: zero-a10g`.
 
 
 
 
 
 
 
 
 
42
  - `SAME-L` is GPU-first; `SAME-S` can be used for CPU autoencoder round trips.
43
 
44
  The Space is configured with `suggested_hardware: zero-a10g`.
45
+
46
+ ## Runtime note
47
+
48
+ The upstream `stable-audio-3` Python package is vendored in this Space from
49
+ Stability AI's public MIT-licensed repository because its package metadata pins
50
+ Torch 2.7.1. ZeroGPU currently provides Torch 2.8.0, so installing the upstream
51
+ package through normal dependency resolution would downgrade Torch and break the
52
+ ZeroGPU runtime.
STABLE_AUDIO_3_LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stability AI
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 all
13
+ 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.
requirements.txt CHANGED
@@ -3,6 +3,9 @@ torchaudio==2.8.0
3
  gradio==6.3.0
4
  spaces
5
  hf_transfer
 
 
 
6
  soundfile
7
  einops>=0.8.2
8
  einops-exts>=0.0.4
 
3
  gradio==6.3.0
4
  spaces
5
  hf_transfer
6
+ numpy>=2.2.6
7
+ packaging>=26.0
8
+ huggingface-hub>=1.7.1
9
  soundfile
10
  einops>=0.8.2
11
  einops-exts>=0.0.4
stable_audio_3/__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ from stable_audio_3.model import AutoencoderModel as AutoencoderModel
2
+ from stable_audio_3.model import StableAudioModel as StableAudioModel
stable_audio_3/cli.py ADDED
@@ -0,0 +1,292 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ stable-audio — command-line interface for Stable Audio 3.
3
+
4
+ Basic usage::
5
+
6
+ stable-audio --model small-music -p "lo-fi hip hop beat, 90 BPM" --duration 30 -o beat.wav
7
+
8
+ """
9
+
10
+ import argparse
11
+ import os
12
+ import torch
13
+ import torchaudio
14
+
15
+ from stable_audio_3 import StableAudioModel
16
+
17
+
18
+ def _save_output(audio: torch.Tensor, sample_rate: int, output: str, batch_size: int):
19
+ """Save generated audio tensor(s) to disk."""
20
+ base, ext = os.path.splitext(output)
21
+ if not ext:
22
+ ext = ".wav"
23
+ for i in range(batch_size):
24
+ path = f"{base}_{i}{ext}" if batch_size > 1 else f"{base}{ext}"
25
+ torchaudio.save(path, audio[i].cpu(), sample_rate)
26
+ print(f"Saved: {path}")
27
+
28
+
29
+ def main():
30
+ parser = argparse.ArgumentParser(
31
+ prog="stable-audio",
32
+ description="Stable Audio 3 — CLI for text-to-audio, audio-to-audio, and inpainting",
33
+ )
34
+
35
+ # Model
36
+ parser.add_argument(
37
+ "--model",
38
+ default="medium",
39
+ choices=[
40
+ "medium",
41
+ "small-music",
42
+ "small-sfx",
43
+ "medium-base",
44
+ "small-music-base",
45
+ "small-sfx-base",
46
+ ],
47
+ help="Model to load (default: medium)",
48
+ )
49
+ parser.add_argument(
50
+ "--device",
51
+ default=None,
52
+ help="Device: cuda / mps / cpu (auto-detected if omitted)",
53
+ )
54
+ parser.add_argument(
55
+ "--no-half", action="store_true", help="Disable half-precision (fp16) on CUDA"
56
+ )
57
+
58
+ # Generation
59
+ parser.add_argument(
60
+ "-p",
61
+ "--prompt",
62
+ required=True,
63
+ nargs="+",
64
+ help="Text prompt(s). Pass multiple for per-batch prompts",
65
+ )
66
+ parser.add_argument(
67
+ "--negative-prompt", nargs="+", default=None, help="Negative prompt(s)"
68
+ )
69
+ parser.add_argument(
70
+ "--duration",
71
+ type=float,
72
+ nargs="+",
73
+ default=[120.0],
74
+ help="Duration in seconds (default: 120). Pass multiple for per-batch durations",
75
+ )
76
+ parser.add_argument(
77
+ "--steps", type=int, default=8, help="Diffusion steps (default: 8)"
78
+ )
79
+ parser.add_argument(
80
+ "--cfg-scale",
81
+ type=float,
82
+ default=1.0,
83
+ help="CFG scale (default: 1.0; try 7.0 for base models)",
84
+ )
85
+ parser.add_argument(
86
+ "--seed", type=int, default=-1, help="Random seed (-1 = random, default: -1)"
87
+ )
88
+ parser.add_argument(
89
+ "--batch-size",
90
+ type=int,
91
+ default=None,
92
+ help="Batch size (default: inferred from number of prompts, or 1)",
93
+ )
94
+ parser.add_argument(
95
+ "-o",
96
+ "--output",
97
+ default="output.wav",
98
+ help="Output file path (default: output.wav)",
99
+ )
100
+
101
+ # Audio-to-Audio
102
+ parser.add_argument(
103
+ "--init-audio",
104
+ default=None,
105
+ metavar="PATH",
106
+ help="Source audio file for audio-to-audio generation",
107
+ )
108
+ parser.add_argument(
109
+ "--init-noise-level",
110
+ type=float,
111
+ default=0.9,
112
+ help="Noise level for audio-to-audio (0.0–1.0, default: 0.9)",
113
+ )
114
+
115
+ # Inpainting / Continuation
116
+ parser.add_argument(
117
+ "--inpaint-audio",
118
+ default=None,
119
+ metavar="PATH",
120
+ help="Source audio file for inpainting or continuation",
121
+ )
122
+ parser.add_argument(
123
+ "--inpaint-start",
124
+ type=float,
125
+ action="append",
126
+ dest="inpaint_starts",
127
+ metavar="SECONDS",
128
+ help="Start of inpaint region in seconds. Repeat for multiple regions.",
129
+ )
130
+ parser.add_argument(
131
+ "--inpaint-end",
132
+ type=float,
133
+ action="append",
134
+ dest="inpaint_ends",
135
+ metavar="SECONDS",
136
+ help="End of inpaint region in seconds. Repeat for multiple regions.",
137
+ )
138
+
139
+ # Chunked decode
140
+ decode_group = parser.add_mutually_exclusive_group()
141
+ decode_group.add_argument(
142
+ "--chunked-decode",
143
+ action="store_true",
144
+ default=None,
145
+ help="Force chunked decoding on",
146
+ )
147
+ decode_group.add_argument(
148
+ "--no-chunked-decode",
149
+ action="store_true",
150
+ default=None,
151
+ help="Force chunked decoding off",
152
+ )
153
+
154
+ # LoRA
155
+ parser.add_argument(
156
+ "--lora-ckpt-path",
157
+ action="append",
158
+ dest="loras",
159
+ metavar="PATH",
160
+ help="LoRA checkpoint path. Repeat to stack multiple LoRAs.",
161
+ )
162
+ parser.add_argument(
163
+ "--lora-strength",
164
+ type=float,
165
+ default=None,
166
+ help="LoRA strength (applied to all LoRAs)",
167
+ )
168
+ parser.add_argument(
169
+ "--lora-index",
170
+ type=int,
171
+ default=None,
172
+ help="Target a specific LoRA index when setting strength",
173
+ )
174
+
175
+ args = parser.parse_args()
176
+
177
+ # --- Validate inpaint args ---
178
+ if (args.inpaint_starts is None) != (args.inpaint_ends is None):
179
+ parser.error("--inpaint-start and --inpaint-end must both be provided together")
180
+ if args.inpaint_starts and len(args.inpaint_starts) != len(args.inpaint_ends):
181
+ parser.error(
182
+ "--inpaint-start and --inpaint-end must be specified the same number of times"
183
+ )
184
+ if args.inpaint_starts and not args.inpaint_audio:
185
+ parser.error("--inpaint-start/--inpaint-end require --inpaint-audio")
186
+ if args.inpaint_audio and not args.inpaint_starts:
187
+ parser.error("--inpaint-audio requires --inpaint-start and --inpaint-end")
188
+
189
+ # --- Resolve batch size ---
190
+ n_prompts = len(args.prompt)
191
+ if args.batch_size is None:
192
+ batch_size = n_prompts
193
+ elif n_prompts > 1 and args.batch_size != n_prompts:
194
+ parser.error(
195
+ f"--batch-size {args.batch_size} does not match the number of prompts "
196
+ f"({n_prompts}); omit --batch-size to have it inferred automatically"
197
+ )
198
+ else:
199
+ batch_size = args.batch_size
200
+
201
+ # --- Validate list-flag lengths against batch size ---
202
+ if (
203
+ args.negative_prompt
204
+ and len(args.negative_prompt) > 1
205
+ and len(args.negative_prompt) != batch_size
206
+ ):
207
+ parser.error(
208
+ f"Got {len(args.negative_prompt)} --negative-prompt values but batch size is {batch_size}"
209
+ )
210
+ if len(args.duration) > 1 and len(args.duration) != batch_size:
211
+ parser.error(
212
+ f"Got {len(args.duration)} --duration values but batch size is {batch_size}"
213
+ )
214
+
215
+ # --- Build scalar / list args ---
216
+ prompt = args.prompt[0] if len(args.prompt) == 1 else args.prompt
217
+ negative_prompt = None
218
+ if args.negative_prompt:
219
+ negative_prompt = (
220
+ args.negative_prompt[0]
221
+ if len(args.negative_prompt) == 1
222
+ else args.negative_prompt
223
+ )
224
+ duration = args.duration[0] if len(args.duration) == 1 else args.duration
225
+
226
+ # --- chunked_decode flag ---
227
+ chunked_decode = None
228
+ if args.chunked_decode:
229
+ chunked_decode = True
230
+ elif args.no_chunked_decode:
231
+ chunked_decode = False
232
+
233
+ # --- Load model ---
234
+ print(f"Loading model '{args.model}'…")
235
+ model = StableAudioModel.from_pretrained(
236
+ args.model, device=args.device, model_half=not args.no_half
237
+ )
238
+
239
+ # --- LoRA ---
240
+ if args.loras:
241
+ print(f"Loading LoRA(s): {args.loras}")
242
+ model.load_lora(args.loras)
243
+ if args.lora_strength is not None:
244
+ model.set_lora_strength(args.lora_strength, lora_index=args.lora_index)
245
+
246
+ # --- Load audio inputs ---
247
+ # torchaudio.load returns (waveform, sample_rate); model.generate expects (sample_rate, waveform)
248
+ init_audio = None
249
+ if args.init_audio:
250
+ waveform, sr = torchaudio.load(args.init_audio)
251
+ init_audio = (sr, waveform)
252
+
253
+ inpaint_audio = None
254
+ if args.inpaint_audio:
255
+ waveform, sr = torchaudio.load(args.inpaint_audio)
256
+ inpaint_audio = (sr, waveform)
257
+
258
+ inpaint_start = None
259
+ inpaint_end = None
260
+ if args.inpaint_starts:
261
+ inpaint_start = (
262
+ args.inpaint_starts[0]
263
+ if len(args.inpaint_starts) == 1
264
+ else args.inpaint_starts
265
+ )
266
+ inpaint_end = (
267
+ args.inpaint_ends[0] if len(args.inpaint_ends) == 1 else args.inpaint_ends
268
+ )
269
+
270
+ # --- Generate ---
271
+ print("Generating…")
272
+ audio = model.generate(
273
+ prompt=prompt,
274
+ negative_prompt=negative_prompt,
275
+ duration=duration,
276
+ steps=args.steps,
277
+ cfg_scale=args.cfg_scale,
278
+ seed=args.seed,
279
+ batch_size=batch_size,
280
+ init_audio=init_audio,
281
+ init_noise_level=args.init_noise_level,
282
+ inpaint_audio=inpaint_audio,
283
+ inpaint_mask_start_seconds=inpaint_start,
284
+ inpaint_mask_end_seconds=inpaint_end,
285
+ chunked_decode=chunked_decode,
286
+ )
287
+
288
+ _save_output(audio, model.model.sample_rate, args.output, batch_size)
289
+
290
+
291
+ if __name__ == "__main__":
292
+ main()
stable_audio_3/data/__init__.py ADDED
File without changes
stable_audio_3/data/dataset.py ADDED
@@ -0,0 +1,510 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import json
3
+ import os
4
+ import dill
5
+ import random
6
+ import time
7
+ import torch
8
+ import torchaudio
9
+
10
+ from os import path
11
+ from torchaudio import transforms as T
12
+ from typing import Optional, Callable, List
13
+
14
+ from .utils import Stereo, Mono, PhaseFlipper, PadCrop_Normalized_T, VolumeNorm, strip_trailing_silence
15
+
16
+ AUDIO_KEYS = ("flac", "wav", "mp3", "m4a", "ogg", "opus")
17
+
18
+ # fast_scandir implementation by Scott Hawley originally in https://github.com/zqevans/audio-diffusion/blob/main/dataset/dataset.py
19
+
20
+ def fast_scandir(
21
+ dir:str, # top-level directory at which to begin scanning
22
+ ext:list, # list of allowed file extensions,
23
+ #max_size = 1 * 1000 * 1000 * 1000 # Only files < 1 GB
24
+ ):
25
+ "very fast `glob` alternative. from https://stackoverflow.com/a/59803793/4259243"
26
+ subfolders, files = [], []
27
+ ext = ['.'+x if x[0]!='.' else x for x in ext] # add starting period to extensions if needed
28
+ try: # hope to avoid 'permission denied' by this try
29
+ for f in os.scandir(dir):
30
+ try: # 'hope to avoid too many levels of symbolic links' error
31
+ if f.is_dir():
32
+ subfolders.append(f.path)
33
+ elif f.is_file():
34
+ file_ext = os.path.splitext(f.name)[1].lower()
35
+ is_hidden = os.path.basename(f.path).startswith(".")
36
+
37
+ if file_ext in ext and not is_hidden:
38
+ files.append(f.path)
39
+ except:
40
+ pass
41
+ except:
42
+ pass
43
+
44
+ for dir in list(subfolders):
45
+ sf, f = fast_scandir(dir, ext)
46
+ subfolders.extend(sf)
47
+ files.extend(f)
48
+ return subfolders, files
49
+
50
+ def keyword_scandir(
51
+ dir: str, # top-level directory at which to begin scanning
52
+ ext: list, # list of allowed file extensions
53
+ keywords: list, # list of keywords to search for in the file name
54
+ ):
55
+ "very fast `glob` alternative. from https://stackoverflow.com/a/59803793/4259243"
56
+ subfolders, files = [], []
57
+ # make keywords case insensitive
58
+ keywords = [keyword.lower() for keyword in keywords]
59
+ # add starting period to extensions if needed
60
+ ext = ['.'+x if x[0] != '.' else x for x in ext]
61
+ banned_words = ["paxheader", "__macosx"]
62
+ try: # hope to avoid 'permission denied' by this try
63
+ for f in os.scandir(dir):
64
+ try: # 'hope to avoid too many levels of symbolic links' error
65
+ if f.is_dir():
66
+ subfolders.append(f.path)
67
+ elif f.is_file():
68
+ is_hidden = f.name.split("/")[-1][0] == '.'
69
+ has_ext = os.path.splitext(f.name)[1].lower() in ext
70
+ name_lower = f.name.lower()
71
+ has_keyword = any(
72
+ [keyword in name_lower for keyword in keywords])
73
+ has_banned = any(
74
+ [banned_word in name_lower for banned_word in banned_words])
75
+ if has_ext and has_keyword and not has_banned and not is_hidden and not os.path.basename(f.path).startswith("._"):
76
+ files.append(f.path)
77
+ except:
78
+ pass
79
+ except:
80
+ pass
81
+
82
+ for dir in list(subfolders):
83
+ sf, f = keyword_scandir(dir, ext, keywords)
84
+ subfolders.extend(sf)
85
+ files.extend(f)
86
+ return subfolders, files
87
+
88
+ def get_audio_filenames(
89
+ paths: list, # directories in which to search
90
+ keywords=None,
91
+ exts=['.wav', '.mp3', '.flac', '.ogg', '.aif', '.opus'],
92
+ filelist_path=None
93
+ ):
94
+ "recursively get a list of audio filenames"
95
+ filenames = []
96
+ if type(paths) is str:
97
+ paths = [paths]
98
+ for path in paths: # get a list of relevant filenames
99
+
100
+ if filelist_path is None:
101
+ # Check for filelist.txt at the root of the directory
102
+ filelist_path = os.path.join(path, "filelist.txt")
103
+
104
+ if os.path.exists(filelist_path):
105
+ with open(filelist_path, "r") as f:
106
+ files = f.readlines()
107
+ files = [os.path.join(path, file.strip()) for file in files]
108
+ filenames.extend(files)
109
+ continue
110
+
111
+ if keywords is not None:
112
+ subfolders, files = keyword_scandir(path, exts, keywords)
113
+ else:
114
+ subfolders, files = fast_scandir(path, exts)
115
+ filenames.extend(files)
116
+ return filenames
117
+
118
+ def get_latent_filenames(
119
+ paths, # directories in which to search
120
+ extension='npy',
121
+ filelist_path=None
122
+ ):
123
+ "recursively get a list of pre-encoded filenames"
124
+ filenames = []
125
+ if type(paths) is str:
126
+ paths = [paths]
127
+ for path in paths: # get a list of relevant filenames
128
+
129
+ if filelist_path is None:
130
+ # Check for filelist.txt at the root of the directory
131
+ filelist_path = os.path.join(path, "filelist.txt")
132
+
133
+ if os.path.exists(filelist_path):
134
+ with open(filelist_path, "r") as f:
135
+ files = f.readlines()
136
+ files = [os.path.join(path, file.strip()) for file in files]
137
+ filenames.extend(files)
138
+ continue
139
+
140
+ _, files = fast_scandir(path, [extension])
141
+ filenames.extend(files)
142
+
143
+ # Filter out silence.npy (used for silence latent padding, not a data sample)
144
+ filenames = [f for f in filenames if os.path.basename(f) != "silence.npy"]
145
+
146
+ # Add metadata paths
147
+ filenames = [(filename, filename.replace(f".{extension}", ".json")) for filename in filenames]
148
+
149
+ return filenames
150
+
151
+ class LocalDatasetConfig:
152
+ def __init__(
153
+ self,
154
+ id: str,
155
+ path: str,
156
+ keywords: Optional[List[str]]=None,
157
+ custom_metadata_fn: Optional[Callable[[str], str]] = None,
158
+ filelist_path = None,
159
+ weight: float = 1.0,
160
+ ):
161
+ self.id = id
162
+ self.path = path
163
+ self.custom_metadata_fn = custom_metadata_fn
164
+ self.keywords = keywords
165
+ self.filelist_path = filelist_path
166
+ self.weight = weight
167
+
168
+ class LatentDatasetConfig(LocalDatasetConfig):
169
+ def __init__(
170
+ self,
171
+ latent_extension: str = "npy",
172
+ filelist_path = None,
173
+ *args,
174
+ **kwargs,
175
+ ):
176
+ super().__init__(*args, **kwargs)
177
+ self.latent_extension = latent_extension
178
+ self.filelist_path = filelist_path
179
+ # weight is inherited from LocalDatasetConfig via **kwargs
180
+
181
+ class SampleDataset(torch.utils.data.Dataset):
182
+ def __init__(
183
+ self,
184
+ configs,
185
+ sample_size=65536,
186
+ sample_rate=48000,
187
+ random_crop=True,
188
+ force_channels="stereo",
189
+ volume_norm=False,
190
+ volume_norm_param=(-16, 2),
191
+ strip_silence=False,
192
+ pad=True,
193
+ ):
194
+ super().__init__()
195
+ self.filenames = []
196
+ self.sample_weights = []
197
+
198
+ self.augs = torch.nn.Sequential(
199
+ PhaseFlipper(),
200
+ #nn.Identity()
201
+ )
202
+
203
+
204
+ self.root_paths = []
205
+
206
+ self.pad_crop = PadCrop_Normalized_T(sample_size, sample_rate, randomize=random_crop, pad=pad)
207
+ self.strip_silence = strip_silence
208
+
209
+ self.force_channels = force_channels
210
+
211
+ self.encoding = torch.nn.Sequential(
212
+ Stereo() if self.force_channels == "stereo" else torch.nn.Identity(),
213
+ Mono() if self.force_channels == "mono" else torch.nn.Identity()
214
+ )
215
+
216
+ self.sr = sample_rate
217
+
218
+ self.volume_norm = VolumeNorm(volume_norm_param, self.sr) if volume_norm else torch.nn.Identity()
219
+
220
+ self.custom_metadata_fns = {}
221
+
222
+ for config in configs:
223
+ self.root_paths.append(config.path)
224
+ new_files = get_audio_filenames(config.path, config.keywords, filelist_path=config.filelist_path)
225
+ self.filenames.extend(new_files)
226
+ self.sample_weights.extend([config.weight] * len(new_files))
227
+ if config.custom_metadata_fn is not None:
228
+ self.custom_metadata_fns[config.path] = dill.dumps(config.custom_metadata_fn)
229
+
230
+ print(f'Found {len(self.filenames)} files')
231
+
232
+ def load_file(self, filename):
233
+ ext = filename.split(".")[-1]
234
+
235
+ audio, in_sr = torchaudio.load(filename, format=ext)
236
+
237
+ if in_sr != self.sr:
238
+ resample_tf = T.Resample(in_sr, self.sr)
239
+ audio = resample_tf(audio)
240
+
241
+ return audio
242
+
243
+ def __len__(self):
244
+ return len(self.filenames)
245
+
246
+ def __getitem__(self, idx):
247
+ audio_filename = self.filenames[idx]
248
+ try:
249
+ start_time = time.time()
250
+ audio = self.load_file(audio_filename)
251
+
252
+ audio = self.volume_norm(audio)
253
+
254
+ if self.strip_silence:
255
+ audio = strip_trailing_silence(audio, self.sr)
256
+
257
+ audio, t_start, t_end, seconds_start, seconds_total, padding_mask = self.pad_crop(audio)
258
+
259
+ # Check for silence
260
+ if is_silence(audio):
261
+ return self[random.randrange(len(self))]
262
+
263
+ # Run augmentations on this sample (including random crop)
264
+ if self.augs is not None:
265
+ audio = self.augs(audio)
266
+
267
+ audio = audio.clamp(-1, 1)
268
+
269
+ # Encode the file to assist in prediction
270
+ if self.encoding is not None:
271
+ audio = self.encoding(audio)
272
+
273
+ info = {}
274
+
275
+ info["path"] = audio_filename
276
+
277
+ for root_path in self.root_paths:
278
+ if root_path in audio_filename:
279
+ info["relpath"] = path.relpath(audio_filename, root_path)
280
+
281
+ info["timestamps"] = (t_start, t_end)
282
+ info["seconds_start"] = seconds_start
283
+ info["seconds_total"] = seconds_total
284
+ info["padding_mask"] = [padding_mask]
285
+ info["sample_rate"] = self.sr
286
+
287
+ end_time = time.time()
288
+
289
+ info["load_time"] = end_time - start_time
290
+
291
+ for custom_md_path in self.custom_metadata_fns.keys():
292
+ if custom_md_path in audio_filename:
293
+ custom_metadata_fn = dill.loads(self.custom_metadata_fns[custom_md_path])
294
+ custom_metadata = custom_metadata_fn(info, audio)
295
+ info.update(custom_metadata)
296
+
297
+ if "__reject__" in info and info["__reject__"]:
298
+ return self[random.randrange(len(self))]
299
+
300
+ # Provide audio inputs as their own dictionary to be merged into info, each audio element will be normalized in the same way as the main audio
301
+ if "__audio__" in info:
302
+ for audio_key, audio_value in info["__audio__"].items():
303
+ # Process the audio_value tensor, which should be a torch tensor
304
+ audio_value, _, _, _, _, _ = self.pad_crop(audio_value)
305
+ audio_value = audio_value.clamp(-1, 1)
306
+ if self.encoding is not None:
307
+ audio_value = self.encoding(audio_value)
308
+ info[audio_key] = audio_value
309
+
310
+ del info["__audio__"]
311
+
312
+ return (audio, info)
313
+ except Exception as e:
314
+ print(f'Couldn\'t load file {audio_filename}: {e}')
315
+ return self[random.randrange(len(self))]
316
+
317
+
318
+ class PreEncodedDataset(torch.utils.data.Dataset):
319
+ def __init__(
320
+ self,
321
+ configs: List[LatentDatasetConfig],
322
+ latent_crop_length=None,
323
+ min_length_sec=None,
324
+ max_length_sec=None,
325
+ random_crop=False,
326
+ tokenizers: Optional[dict] = None,
327
+ ):
328
+ super().__init__()
329
+ self.filenames = []
330
+ self.sample_weights = []
331
+
332
+ self.custom_metadata_fns = {}
333
+
334
+ self.silence_latents = {}
335
+
336
+ for config in configs:
337
+ new_files = get_latent_filenames(config.path, config.latent_extension, config.filelist_path)
338
+ self.filenames.extend(new_files)
339
+ self.sample_weights.extend([config.weight] * len(new_files))
340
+ if config.custom_metadata_fn is not None:
341
+ self.custom_metadata_fns[config.path] = dill.dumps(config.custom_metadata_fn)
342
+
343
+ # Load silence latent if available (for variable-length padding)
344
+ paths = config.path if isinstance(config.path, list) else [config.path]
345
+ for path in paths:
346
+ silence_path = os.path.join(path, "silence.npy")
347
+ if os.path.exists(silence_path):
348
+ self.silence_latents[path] = np.load(silence_path).squeeze(0) # [C, N]
349
+ print(f'Loaded silence latent from {silence_path}')
350
+
351
+ self.latent_crop_length = latent_crop_length
352
+ self.random_crop = random_crop
353
+
354
+ self.min_length_sec = min_length_sec
355
+ self.max_length_sec = max_length_sec
356
+
357
+ # tokenizers: dict mapping metadata key -> (tokenizer, max_length)
358
+ # If provided, text fields will be pre-tokenized in DataLoader workers
359
+ self.tokenizers = tokenizers
360
+
361
+ print(f'Found {len(self.filenames)} files')
362
+
363
+ def __len__(self):
364
+ return len(self.filenames)
365
+
366
+ def _get_silence_for_file(self, latent_filename):
367
+ """Return the silence latent for the dataset that contains this file, or None."""
368
+ for path, silence in self.silence_latents.items():
369
+ if path in latent_filename:
370
+ return silence
371
+ return None
372
+
373
+ def __getitem__(self, idx):
374
+ latent_filename, md_filename = self.filenames[idx]
375
+ try:
376
+ latents = torch.from_numpy(np.load(latent_filename)) # [C, N]
377
+
378
+ with open(md_filename, "r") as f:
379
+ try:
380
+ info = json.load(f)
381
+ except:
382
+ raise Exception(f"Couldn't load metadata file {md_filename}")
383
+
384
+ info["latent_filename"] = latent_filename
385
+
386
+ if self.latent_crop_length is not None:
387
+ stored_length = latents.shape[1]
388
+
389
+ if stored_length > self.latent_crop_length:
390
+ # Crop to latent_crop_length (existing logic)
391
+ # Get the last index from the padding mask, the index of the last 1 in the sequence
392
+ last_ix = len(info["padding_mask"]) - 1 - info["padding_mask"][::-1].index(1)
393
+
394
+ if self.random_crop and last_ix > self.latent_crop_length:
395
+ start = random.randint(0, last_ix - self.latent_crop_length)
396
+ else:
397
+ start = 0
398
+
399
+ latents = latents[:, start:start+self.latent_crop_length]
400
+ info["padding_mask"] = info["padding_mask"][start:start+self.latent_crop_length]
401
+ info["latent_crop_start"] = start
402
+
403
+ elif stored_length < self.latent_crop_length:
404
+ # Pad with silence latent to reach latent_crop_length
405
+ pad_needed = self.latent_crop_length - stored_length
406
+ silence = self._get_silence_for_file(latent_filename)
407
+
408
+ if silence is not None:
409
+ # Slice or tile silence latent to cover pad_needed frames
410
+ if silence.shape[1] >= pad_needed:
411
+ silence_pad = silence[:, :pad_needed]
412
+ else:
413
+ silence_pad = np.tile(silence, (1, (pad_needed // silence.shape[1]) + 1))[:, :pad_needed]
414
+ latents = torch.cat([latents, torch.from_numpy(silence_pad)], dim=1)
415
+ else:
416
+ # No silence latent available — zero-pad as fallback
417
+ latents = torch.nn.functional.pad(latents, (0, pad_needed))
418
+
419
+ # Build padding_mask: valid frames from stored mask, zeros for padding
420
+ info["padding_mask"] = info["padding_mask"][:stored_length] + [0] * pad_needed
421
+ info["latent_crop_start"] = 0
422
+
423
+ else:
424
+ # Exact match
425
+ info["latent_crop_start"] = 0
426
+
427
+ info["latent_crop_length"] = self.latent_crop_length
428
+
429
+ info["padding_mask"] = [torch.tensor(info["padding_mask"])]
430
+
431
+ seconds_total = info["seconds_total"]
432
+
433
+ if self.min_length_sec is not None and seconds_total < self.min_length_sec:
434
+ return self[random.randrange(len(self))]
435
+
436
+ if self.max_length_sec is not None and seconds_total > self.max_length_sec:
437
+ return self[random.randrange(len(self))]
438
+
439
+ for custom_md_path in self.custom_metadata_fns.keys():
440
+ if custom_md_path in latent_filename:
441
+ custom_metadata_fn = dill.loads(self.custom_metadata_fns[custom_md_path])
442
+ custom_metadata = custom_metadata_fn(info, latents)
443
+ info.update(custom_metadata)
444
+
445
+ if "__reject__" in info and info["__reject__"]:
446
+ return self[random.randrange(len(self))]
447
+
448
+ if "__replace__" in info and info["__replace__"] is not None:
449
+ # Replace the latents with the new latents if the custom metadata function returns a new set of latents
450
+ latents = info["__replace__"]
451
+
452
+ info["audio"] = latents
453
+
454
+ # Pre-tokenize text fields in DataLoader workers to avoid
455
+ # CPU contention with the main training thread
456
+ if self.tokenizers is not None:
457
+ for key, (tokenizer, max_length) in self.tokenizers.items():
458
+ if key in info and isinstance(info[key], str):
459
+ # Save raw text before replacing with tokens (needed by CLAP and other text-based losses)
460
+ info[f"{key}_text"] = info[key]
461
+ encoded = tokenizer(
462
+ info[key],
463
+ truncation=True,
464
+ max_length=max_length,
465
+ padding="max_length",
466
+ return_tensors="pt",
467
+ )
468
+ info[key] = {
469
+ "input_ids": encoded["input_ids"].squeeze(0),
470
+ "attention_mask": encoded["attention_mask"].squeeze(0),
471
+ }
472
+
473
+ return (latents, info)
474
+ except Exception as e:
475
+ print(f'Couldn\'t load file {latent_filename}: {e}')
476
+ return self[random.randrange(len(self))]
477
+
478
+ # get_dbmax and is_silence copied from https://github.com/drscotthawley/aeiou/blob/main/aeiou/core.py under Apache 2.0 License
479
+ # License can be found in LICENSES/LICENSE_AEIOU.txt
480
+ def get_dbmax(
481
+ audio, # torch tensor of (multichannel) audio
482
+ ):
483
+ "finds the loudest value in the entire clip and puts that into dB (full scale)"
484
+ return 20*torch.log10(torch.flatten(audio.abs()).max()).cpu().numpy()
485
+
486
+ def is_silence(
487
+ audio, # torch tensor of (multichannel) audio
488
+ thresh=-60, # threshold in dB below which we declare to be silence
489
+ ):
490
+ "checks if entire clip is 'silence' below some dB threshold"
491
+ dBmax = get_dbmax(audio)
492
+ return dBmax < thresh
493
+
494
+
495
+ def collation_fn(samples):
496
+ batched = list(zip(*samples))
497
+ result = []
498
+ for b in batched:
499
+ if isinstance(b[0], (int, float)):
500
+ b = np.array(b)
501
+ elif isinstance(b[0], torch.Tensor):
502
+ b = torch.stack(b)
503
+ elif isinstance(b[0], np.ndarray):
504
+ b = np.array(b)
505
+ else:
506
+ b = b
507
+ result.append(b)
508
+ return result
509
+
510
+
stable_audio_3/data/utils.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import random
3
+ import torch
4
+
5
+ from torch import nn
6
+ from typing import Optional, Tuple
7
+
8
+ from torchaudio import transforms as T
9
+
10
+ class PadCrop(nn.Module):
11
+ def __init__(self, n_samples, randomize=True):
12
+ super().__init__()
13
+ self.n_samples = n_samples
14
+ self.randomize = randomize
15
+
16
+ def __call__(self, signal):
17
+ n, s = signal.shape
18
+ start = 0 if (not self.randomize) else torch.randint(0, max(0, s - self.n_samples) + 1, []).item()
19
+ end = start + self.n_samples
20
+ output = signal.new_zeros([n, self.n_samples])
21
+ output[:, :min(s, self.n_samples)] = signal[:, start:end]
22
+ return output
23
+
24
+ class PadCrop_Normalized_T(nn.Module):
25
+
26
+ def __init__(self, n_samples: int, sample_rate: int, randomize: bool = True, pad: bool = True):
27
+
28
+ super().__init__()
29
+
30
+ self.n_samples = n_samples
31
+ self.sample_rate = sample_rate
32
+ self.randomize = randomize
33
+ self.pad = pad
34
+
35
+ def __call__(self, source: torch.Tensor) -> Tuple[torch.Tensor, float, float, int, int, torch.Tensor]:
36
+
37
+ n_channels, n_samples = source.shape
38
+
39
+ # Calculate bounds and offset
40
+ upper_bound = max(0, n_samples - self.n_samples)
41
+ offset = 0
42
+ if self.randomize and n_samples > self.n_samples:
43
+ offset = random.randint(0, upper_bound)
44
+
45
+ # Calculate normalized times
46
+ norm_denom = upper_bound + self.n_samples
47
+ t_start = offset / norm_denom
48
+ t_end = (offset + self.n_samples) / norm_denom
49
+
50
+ # Calculate timing info
51
+ seconds_start = math.floor(offset / self.sample_rate)
52
+ seconds_total = math.ceil(n_samples / self.sample_rate)
53
+
54
+ # Optimize for different cases
55
+ if n_samples >= self.n_samples:
56
+ # No padding needed - use view (zero-copy)
57
+ chunk = source[:, offset:offset + self.n_samples]
58
+ # Create full mask efficiently
59
+ padding_mask = torch.ones(self.n_samples, dtype=source.dtype, device=source.device)
60
+ elif not self.pad:
61
+ # No padding mode - return audio at natural length
62
+ chunk = source
63
+ padding_mask = torch.ones(n_samples, dtype=source.dtype, device=source.device)
64
+ else:
65
+ # Padding needed - create chunk and fill in-place
66
+ chunk = torch.zeros(n_channels, self.n_samples, dtype=source.dtype, device=source.device)
67
+ chunk[:, :n_samples] = source # Use in-place assignment
68
+
69
+ # Create padding mask in-place
70
+ padding_mask = torch.zeros(self.n_samples, dtype=source.dtype, device=source.device)
71
+ padding_mask[:n_samples] = 1
72
+
73
+ return (
74
+ chunk,
75
+ t_start,
76
+ t_end,
77
+ seconds_start,
78
+ seconds_total,
79
+ padding_mask
80
+ )
81
+
82
+ def strip_trailing_silence(audio, sample_rate, threshold_db=-60, min_silence_duration=0.1):
83
+ """Strip silence from the end of an audio tensor.
84
+
85
+ Args:
86
+ audio: tensor [channels, samples]
87
+ sample_rate: audio sample rate
88
+ threshold_db: dB threshold below which audio is considered silent
89
+ min_silence_duration: minimum trailing silence duration in seconds to strip
90
+ Returns:
91
+ Truncated audio tensor [channels, trimmed_samples], or original if no significant trailing silence
92
+ """
93
+ n_samples = audio.shape[-1]
94
+ hop_length = max(1, int(sample_rate * 0.01)) # 10ms frames
95
+ min_silence_samples = int(sample_rate * min_silence_duration)
96
+ n_frames = n_samples // hop_length
97
+
98
+ if n_frames == 0:
99
+ return audio
100
+
101
+ # Work in float32 for precision
102
+ audio_f = audio.float()
103
+
104
+ # Reshape into frames and compute max absolute amplitude per frame across channels
105
+ trimmed = audio_f[:, :n_frames * hop_length]
106
+ frames = trimmed.reshape(audio_f.shape[0], n_frames, hop_length)
107
+ frame_peak = frames.abs().amax(dim=(0, 2)) # [n_frames] - max across channels and samples
108
+ frame_db = 20 * torch.log10(frame_peak + 1e-10)
109
+
110
+ # Find last frame above threshold
111
+ above_thresh = (frame_db > threshold_db).nonzero(as_tuple=True)[0]
112
+
113
+ if len(above_thresh) == 0:
114
+ # Entire audio is silent
115
+ return audio[:, :0]
116
+
117
+ last_active_frame = above_thresh[-1].item()
118
+ content_end = min((last_active_frame + 1) * hop_length, n_samples)
119
+
120
+ # Only strip if trailing silence is long enough
121
+ if (n_samples - content_end) < min_silence_samples:
122
+ return audio
123
+
124
+ return audio[:, :content_end]
125
+
126
+
127
+ class PhaseFlipper(nn.Module):
128
+ "Randomly invert the phase of a signal"
129
+ def __init__(self, p=0.5):
130
+ super().__init__()
131
+ self.p = p
132
+ def __call__(self, signal):
133
+ return -signal if (random.random() < self.p) else signal
134
+
135
+ class Mono(nn.Module):
136
+ def __call__(self, signal):
137
+ return torch.mean(signal, dim=0, keepdims=True) if len(signal.shape) > 1 else signal
138
+
139
+ class Stereo(nn.Module):
140
+ def __call__(self, signal):
141
+ signal_shape = signal.shape
142
+ # Check if it's mono
143
+ if len(signal_shape) == 1: # s -> 2, s
144
+ signal = signal.unsqueeze(0).repeat(2, 1)
145
+ elif len(signal_shape) == 2:
146
+ if signal_shape[0] == 1: #1, s -> 2, s
147
+ signal = signal.repeat(2, 1)
148
+ elif signal_shape[0] > 2: #?, s -> 2,s
149
+ signal = signal[:2, :]
150
+
151
+ return signal
152
+
153
+ class VolumeNorm(nn.Module):
154
+ "Volume normalization and augmentation of a signal [LUFS standard]"
155
+ def __init__(self, params=[-16, 2], sample_rate=16000, energy_threshold=1e-6):
156
+ super().__init__()
157
+ self.loudness = T.Loudness(sample_rate)
158
+ self.value = params[0]
159
+ self.gain_range = [-params[1], params[1]]
160
+ self.energy_threshold = energy_threshold
161
+
162
+ def __call__(self, signal):
163
+ """
164
+ signal: torch.Tensor [channels, time]
165
+ """
166
+ # avoid do normalisation for silence
167
+ energy = torch.mean(signal**2)
168
+ if energy < self.energy_threshold:
169
+ return signal
170
+
171
+ input_loudness = self.loudness(signal)
172
+ # Generate a random target loudness within the specified range
173
+ target_loudness = self.value + (torch.rand(1).item() * (self.gain_range[1] - self.gain_range[0]) + self.gain_range[0])
174
+ delta_loudness = target_loudness - input_loudness
175
+ gain = torch.pow(10.0, delta_loudness / 20.0)
176
+ output = gain * signal
177
+
178
+ # Check for potentially clipped samples
179
+ if torch.max(torch.abs(output)) >= 1.0:
180
+ output = self.declip(output)
181
+
182
+ return output
183
+
184
+ def declip(self, signal):
185
+ """
186
+ Declip the signal by scaling down if any samples are clipped
187
+ """
188
+ max_val = torch.max(torch.abs(signal))
189
+ if max_val > 1.0:
190
+ signal = signal / max_val
191
+ signal *= 0.95
192
+ return signal
193
+
194
+
195
+ def create_padding_mask_from_lengths(
196
+ valid_lengths: torch.Tensor,
197
+ total_seq_len: int,
198
+ ) -> torch.Tensor:
199
+ """
200
+ Create a boolean padding mask from per-batch valid sequence lengths.
201
+
202
+ Args:
203
+ valid_lengths: Tensor of shape (batch_size,) with valid length per sample
204
+ total_seq_len: Total sequence length of the latent
205
+
206
+ Returns:
207
+ Boolean tensor of shape (batch_size, total_seq_len) where True = valid, False = padding
208
+ """
209
+ device = valid_lengths.device
210
+ positions = torch.arange(total_seq_len, device=device).unsqueeze(0) # (1, T)
211
+ padding_mask = positions < valid_lengths.unsqueeze(1) # (B, T)
212
+ return padding_mask
213
+
214
+
215
+ def compute_effective_seq_len_from_conditioning(
216
+ conditioning: list,
217
+ sample_rate: int,
218
+ downsampling_ratio: int = 1,
219
+ device: str = "cuda"
220
+ ) -> Optional[torch.Tensor]:
221
+ """
222
+ Compute effective sequence lengths from seconds_total in conditioning dicts.
223
+
224
+ Args:
225
+ conditioning: List of conditioning dicts, one per batch element
226
+ sample_rate: Audio sample rate
227
+ downsampling_ratio: Pretransform downsampling ratio (1 if no pretransform)
228
+ device: Device to place the tensor on
229
+
230
+ Returns:
231
+ Tensor of shape (batch_size,) with effective sequence lengths in latent space,
232
+ or None if seconds_total is not present in conditioning
233
+ """
234
+ if conditioning is None:
235
+ return None
236
+
237
+ # Check if seconds_total is present in any conditioning dict
238
+ if not any("seconds_total" in c for c in conditioning):
239
+ return None
240
+
241
+ effective_lengths = []
242
+ for cond_dict in conditioning:
243
+ if "seconds_total" in cond_dict:
244
+ seconds = cond_dict["seconds_total"]
245
+ # Convert seconds to latent sequence length
246
+ audio_samples = int(seconds * sample_rate)
247
+ latent_length = math.ceil(audio_samples / downsampling_ratio)
248
+ effective_lengths.append(latent_length)
249
+ else:
250
+ # If seconds_total not present for this item, use None as marker
251
+ effective_lengths.append(None)
252
+
253
+ # If any item is missing seconds_total, return None to fall back to full length
254
+ if any(l is None for l in effective_lengths):
255
+ return None
256
+
257
+ return torch.tensor(effective_lengths, dtype=torch.float32, device=device)
stable_audio_3/factory.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import typing as tp
2
+
3
+ from stable_audio_3.models.diffusion import DiTWrapper, ConditionedDiffusionModelWrapper
4
+ from stable_audio_3.models.autoencoders import (
5
+ AudioAutoencoder,
6
+ SAMEEncoder,
7
+ SAMEDecoder,
8
+ )
9
+ from stable_audio_3.models.conditioners import (
10
+ MultiConditioner,
11
+ NumberConditioner,
12
+ T5GemmaConditioner,
13
+ )
14
+ from stable_audio_3.models.bottleneck import SoftNormBottleneck
15
+ from stable_audio_3.models.pretransforms import (
16
+ PatchedPretransform,
17
+ AutoencoderPretransform,
18
+ )
19
+
20
+
21
+ def create_diffusion_cond_from_config(config: tp.Dict[str, tp.Any]):
22
+ model_config = config["model"]
23
+ diffusion_config = model_config.get("diffusion", None)
24
+ diffusion_model_config = diffusion_config.get("config", None)
25
+ diffusion_objective = diffusion_config.get("diffusion_objective", "v")
26
+ modular_local_cond_configs = diffusion_config.get("modular_local_cond_configs", [])
27
+
28
+ diffusion_model = DiTWrapper(
29
+ diffusion_objective=diffusion_objective,
30
+ modular_local_cond_configs=modular_local_cond_configs,
31
+ **diffusion_model_config,
32
+ )
33
+
34
+ io_channels = model_config.get("io_channels", None)
35
+ sample_rate = config.get("sample_rate", None)
36
+ cross_attention_ids = diffusion_config.get("cross_attention_cond_ids", [])
37
+ global_cond_ids = diffusion_config.get("global_cond_ids", [])
38
+ input_concat_ids = diffusion_config.get("input_concat_ids", [])
39
+ local_add_cond_ids = diffusion_config.get("local_add_cond_ids", [])
40
+ modular_local_cond_ids = [c["id"] for c in modular_local_cond_configs]
41
+ prepend_cond_ids = diffusion_config.get("prepend_cond_ids", [])
42
+
43
+ distribution_shift_options = diffusion_config.get(
44
+ "distribution_shift_options", None
45
+ )
46
+ sampling_distribution_shift_options = diffusion_config.get(
47
+ "sampling_distribution_shift_options", None
48
+ )
49
+ mask_padding_attention = diffusion_config.get("mask_padding_attention", False)
50
+ use_effective_length_for_schedule = diffusion_config.get(
51
+ "use_effective_length_for_schedule", False
52
+ )
53
+
54
+ pretransform = create_pretransform_from_config(model_config, sample_rate)
55
+ min_input_length = pretransform.downsampling_ratio
56
+
57
+ conditioning_config = model_config.get("conditioning", None)
58
+
59
+ conditioner = create_multi_conditioner_from_conditioning_config(conditioning_config)
60
+
61
+ min_input_length *= diffusion_model.model.patch_size
62
+
63
+ return ConditionedDiffusionModelWrapper(
64
+ diffusion_model,
65
+ conditioner,
66
+ min_input_length=min_input_length,
67
+ sample_rate=sample_rate,
68
+ cross_attn_cond_ids=cross_attention_ids,
69
+ global_cond_ids=global_cond_ids,
70
+ input_concat_ids=input_concat_ids,
71
+ local_add_cond_ids=local_add_cond_ids,
72
+ modular_local_cond_ids=modular_local_cond_ids,
73
+ prepend_cond_ids=prepend_cond_ids,
74
+ pretransform=pretransform,
75
+ io_channels=io_channels,
76
+ distribution_shift_options=distribution_shift_options,
77
+ sampling_distribution_shift_options=sampling_distribution_shift_options,
78
+ mask_padding_attention=mask_padding_attention,
79
+ use_effective_length_for_schedule=use_effective_length_for_schedule,
80
+ diffusion_objective=diffusion_objective,
81
+ )
82
+
83
+
84
+ def create_autoencoder_from_config(config, sample_rate):
85
+ # AE-only configs (SAME-S/SAME-L) have encoder/decoder at the top of config["model"].
86
+ # Full SA3 configs nest them inside config["model"]["pretransform"]["config"].
87
+ if "encoder" in config:
88
+ autoencoder_config = config
89
+ else:
90
+ autoencoder_config = config["pretransform"]["config"]
91
+ encoder = SAMEEncoder(**autoencoder_config["encoder"]["config"])
92
+ decoder = SAMEDecoder(**autoencoder_config["decoder"]["config"])
93
+
94
+ latent_dim = autoencoder_config.get("latent_dim", None)
95
+ downsampling_ratio = autoencoder_config.get("downsampling_ratio", None)
96
+ io_channels = autoencoder_config.get("io_channels", None)
97
+ in_channels = autoencoder_config.get("in_channels", None)
98
+ out_channels = autoencoder_config.get("out_channels", None)
99
+ soft_clip = autoencoder_config["decoder"].get("soft_clip", False)
100
+ pretransform = PatchedPretransform(**autoencoder_config["pretransform"]["config"])
101
+ bottleneck = SoftNormBottleneck(**autoencoder_config["bottleneck"]["config"])
102
+
103
+ return AudioAutoencoder(
104
+ encoder,
105
+ decoder,
106
+ io_channels=io_channels,
107
+ latent_dim=latent_dim,
108
+ downsampling_ratio=downsampling_ratio,
109
+ sample_rate=sample_rate,
110
+ bottleneck=bottleneck,
111
+ pretransform=pretransform,
112
+ in_channels=in_channels,
113
+ out_channels=out_channels,
114
+ soft_clip=soft_clip,
115
+ )
116
+
117
+
118
+ def create_pretransform_from_config(config, sample_rate):
119
+ pretransform_block = config["pretransform"]
120
+ autoencoder = create_autoencoder_from_config(config, sample_rate)
121
+ chunked = pretransform_block.get("chunked", False)
122
+ iterate_batch = pretransform_block.get("iterate_batch", False)
123
+ return AutoencoderPretransform(
124
+ autoencoder, chunked=chunked, iterate_batch=iterate_batch
125
+ )
126
+
127
+
128
+ def create_multi_conditioner_from_conditioning_config(
129
+ config: tp.Dict[str, tp.Any],
130
+ ) -> MultiConditioner:
131
+ """Create a MultiConditioner from a conditioning config dictionary."""
132
+ conditioners = {}
133
+ cond_dim = config["cond_dim"]
134
+
135
+ default_keys = config.get("default_keys", {})
136
+
137
+ pre_encoded_keys = config.get("pre_encoded_keys", [])
138
+
139
+ for conditioner_info in config["configs"]:
140
+ id = conditioner_info["id"]
141
+
142
+ conditioner_type = conditioner_info["type"]
143
+
144
+ conditioner_config = {"output_dim": cond_dim}
145
+
146
+ conditioner_config.update(conditioner_info["config"])
147
+
148
+ if conditioner_type == "t5gemma":
149
+ conditioners[id] = T5GemmaConditioner(**conditioner_config)
150
+ elif conditioner_type == "number":
151
+ conditioners[id] = NumberConditioner(**conditioner_config)
152
+ else:
153
+ raise ValueError(f"Unknown conditioner type: {conditioner_type}")
154
+
155
+ return MultiConditioner(
156
+ conditioners, default_keys=default_keys, pre_encoded_keys=pre_encoded_keys
157
+ )
stable_audio_3/inference/__init__.py ADDED
File without changes
stable_audio_3/inference/audio_utils.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import torch
3
+ from torch import nn
4
+ from ..data.utils import PadCrop
5
+
6
+ from torchaudio import transforms as T
7
+
8
+ def numpy_audio_to_tensor(audio):
9
+ """Convert a numpy audio array to a float32 torch tensor with shape [channels, samples]."""
10
+ if audio.dtype == np.float32:
11
+ t = torch.from_numpy(audio)
12
+ elif audio.dtype == np.int16:
13
+ t = torch.from_numpy(audio).float().div(32767)
14
+ elif audio.dtype == np.int32:
15
+ t = torch.from_numpy(audio).float().div(2147483647)
16
+ else:
17
+ raise ValueError(f"Unsupported audio dtype: {audio.dtype}")
18
+ if t.dim() == 1:
19
+ t = t.unsqueeze(0) # [n] -> [1, n]
20
+ elif t.dim() == 2:
21
+ t = t.transpose(0, 1) # [n, channels] -> [channels, n]
22
+ return t
23
+
24
+
25
+ def preprocess_audio(sr_audio, sample_rate, sample_size, half=False):
26
+ """Normalize a (sr, numpy_or_tensor) tuple into (sample_rate, tensor) ready for the model."""
27
+ in_sr, audio = sr_audio
28
+
29
+ if isinstance(audio, np.ndarray):
30
+ audio = numpy_audio_to_tensor(audio)
31
+ elif audio.dim() == 1:
32
+ audio = audio.unsqueeze(0)
33
+ elif audio.dim() == 2:
34
+ audio = audio.transpose(0, 1) # [n, 2] -> [2, n]
35
+
36
+ if half:
37
+ audio = audio.to(torch.float16)
38
+
39
+ if in_sr != sample_rate:
40
+ resample_tf = T.Resample(in_sr, sample_rate).to(audio.device).to(audio.dtype)
41
+ audio = resample_tf(audio)
42
+
43
+ if audio.shape[-1] > sample_size:
44
+ audio = audio[:, :sample_size]
45
+
46
+ return (sample_rate, audio)
47
+
48
+
49
+ def set_audio_channels(audio, target_channels):
50
+ # Add channel dim if it's missing
51
+ if audio.dim() == 2:
52
+ audio = audio.unsqueeze(1)
53
+
54
+ if target_channels == 1:
55
+ # Convert to mono
56
+ audio = audio.mean(1, keepdim=True)
57
+ elif target_channels == 2:
58
+ # Convert to stereo
59
+ if audio.shape[1] == 1:
60
+ audio = audio.repeat(1, 2, 1)
61
+ elif audio.shape[1] > 2:
62
+ audio = audio[:, :2, :]
63
+ return audio
64
+
65
+ def prepare_audio(audio, in_sr, target_sr, target_length, target_channels, device):
66
+
67
+ audio = audio.to(device)
68
+
69
+ if in_sr != target_sr:
70
+ resample_tf = T.Resample(in_sr, target_sr).to(device)
71
+ audio = resample_tf(audio)
72
+
73
+ audio = PadCrop(target_length, randomize=False)(audio)
74
+
75
+ # Add batch dimension
76
+ if audio.dim() == 1:
77
+ audio = audio.unsqueeze(0).unsqueeze(0)
78
+ elif audio.dim() == 2:
79
+ audio = audio.unsqueeze(0)
80
+
81
+ audio = set_audio_channels(audio, target_channels)
82
+
83
+ return audio
84
+
85
+
86
+ class PadCrop(nn.Module):
87
+ def __init__(self, n_samples, randomize=True):
88
+ super().__init__()
89
+ self.n_samples = n_samples
90
+ self.randomize = randomize
91
+
92
+ def __call__(self, signal):
93
+ n, s = signal.shape
94
+ start = 0 if (not self.randomize) else torch.randint(0, max(0, s - self.n_samples) + 1, []).item()
95
+ end = start + self.n_samples
96
+ output = signal.new_zeros([n, self.n_samples])
97
+ output[:, :min(s, self.n_samples)] = signal[:, start:end]
98
+ return output
stable_audio_3/inference/distribution_shift.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ import typing as tp
4
+
5
+
6
+ class IdentityDistributionShift:
7
+ """No-op distribution shift — returns timesteps unchanged."""
8
+ def shift(self, t: torch.Tensor, seq_len):
9
+ return t
10
+
11
+
12
+ class FluxDistributionShift:
13
+ """Flux/SD3/Self-Flow timestep shift: t_shifted = alpha * t / (1 + (alpha-1) * t).
14
+
15
+ Convention: t=0 is data, t=1 is noise.
16
+ alpha > 1 shifts timesteps toward noise, appropriate for longer sequences
17
+ where the critical structure-from-noise transition happens at higher noise levels.
18
+
19
+ Can be used in two ways:
20
+ - Constant alpha: set alpha_min == alpha_max. This is how the Self-Flow paper
21
+ (BFL, 2025) uses it, with alpha chosen per modality/autoencoder.
22
+ Reference values from the paper: audio sampleshift=6.93, trainshift=1.0;
23
+ video sampleshift=15.0, trainshift=2.95; images sampleshift=1.78-6.93.
24
+ - Seq_len-dependent alpha: set different alpha_min/alpha_max. Alpha is
25
+ interpolated log-linearly in seq_len space (power-law), following the
26
+ SD3 derivation where alpha ∝ sqrt(seq_len).
27
+
28
+ Args:
29
+ min_length: Minimum sequence length (alpha = alpha_min here)
30
+ max_length: Maximum sequence length (alpha = alpha_max here)
31
+ alpha_min: Shift factor at min_length (1.0 = no shift)
32
+ alpha_max: Shift factor at max_length (1.0 = no shift)
33
+ """
34
+ def __init__(self, min_length=256, max_length=4096,
35
+ alpha_min=1.0, alpha_max=1.0):
36
+ self.min_length = min_length
37
+ self.max_length = max_length
38
+ self.alpha_min = alpha_min
39
+ self.alpha_max = alpha_max
40
+ # Precompute for log-linear interpolation
41
+ self.log_alpha_min = math.log(max(alpha_min, 1e-8))
42
+ self.log_alpha_max = math.log(max(alpha_max, 1e-8))
43
+ self.log_min_seq = math.log(min_length)
44
+ self.log_max_seq = math.log(max_length)
45
+ if self.log_max_seq == self.log_min_seq:
46
+ self.log_max_seq += 1e-8 # prevent division by zero for constant alpha
47
+
48
+ def get_alpha(self, seq_len: tp.Union[int, torch.Tensor]):
49
+ """Compute alpha via log-linear interpolation in seq_len."""
50
+ if isinstance(seq_len, torch.Tensor):
51
+ seq_len = seq_len.float().clamp(self.min_length, self.max_length)
52
+ log_seq = torch.log(seq_len)
53
+ frac = (log_seq - self.log_min_seq) / (self.log_max_seq - self.log_min_seq)
54
+ log_alpha = self.log_alpha_min + frac * (self.log_alpha_max - self.log_alpha_min)
55
+ return torch.exp(log_alpha)
56
+ else:
57
+ seq_len = max(min(seq_len, self.max_length), self.min_length)
58
+ log_seq = math.log(seq_len)
59
+ frac = (log_seq - self.log_min_seq) / (self.log_max_seq - self.log_min_seq)
60
+ log_alpha = self.log_alpha_min + frac * (self.log_alpha_max - self.log_alpha_min)
61
+ return math.exp(log_alpha)
62
+
63
+ def shift(self, t: torch.Tensor, seq_len: tp.Union[int, torch.Tensor]):
64
+ """Shift timesteps based on sequence length.
65
+
66
+ Args:
67
+ t: Timesteps tensor of shape (batch_size,) or (steps,)
68
+ seq_len: Either a scalar int (same shift for all elements) or
69
+ tensor of shape (batch_size,) for per-element shifts
70
+ Returns:
71
+ Shifted timesteps. If seq_len is a tensor and t is 1D with different size,
72
+ returns shape (batch_size, steps) for per-element schedules.
73
+ """
74
+ alpha = self.get_alpha(seq_len)
75
+
76
+ if isinstance(seq_len, torch.Tensor):
77
+ alpha = alpha.to(t.device)
78
+ if t.dim() == 1 and alpha.dim() == 1 and t.shape[0] != alpha.shape[0]:
79
+ t = t.unsqueeze(0)
80
+ alpha = alpha.unsqueeze(1)
81
+
82
+ return alpha * t / (1 + (alpha - 1.0) * t)
83
+
84
+
85
+ class DistributionShift:
86
+ def __init__(self, base_shift=0.5, max_shift=1.15, max_length=4096, min_length=256, use_sine=False):
87
+ self.base_shift = base_shift
88
+ self.max_shift = max_shift
89
+ self.max_length = max_length
90
+ self.min_length = min_length
91
+ self.use_sine = use_sine
92
+
93
+ def shift(self, t: torch.Tensor, seq_len: tp.Union[int, torch.Tensor]):
94
+ """
95
+ Shift timesteps based on sequence length to adjust noise schedule.
96
+
97
+ Args:
98
+ t: Timesteps tensor of shape (batch_size,) or (steps,)
99
+ seq_len: Either a scalar int (same shift for all elements) or
100
+ tensor of shape (batch_size,) for per-element shifts
101
+ Returns:
102
+ Shifted timesteps. If seq_len is a tensor and t is 1D with different size,
103
+ returns shape (batch_size, steps) for per-element schedules.
104
+ """
105
+ if isinstance(seq_len, torch.Tensor):
106
+ # Per-element sequence lengths
107
+ # Ensure seq_len is on the same device as t
108
+ seq_len = seq_len.to(t.device)
109
+ seq_len_clamped = seq_len.float().clamp(self.min_length, self.max_length)
110
+ # Handle broadcasting when t and seq_len have different sizes
111
+ if t.dim() == 1 and seq_len_clamped.dim() == 1 and t.shape[0] != seq_len_clamped.shape[0]:
112
+ # t: (steps,) -> (1, steps), seq_len: (batch,) -> (batch, 1)
113
+ # Result: (batch, steps)
114
+ t = t.unsqueeze(0)
115
+ seq_len_clamped = seq_len_clamped.unsqueeze(1)
116
+ sigma = 1.0
117
+ mu = - (self.base_shift + (self.max_shift - self.base_shift) * (seq_len_clamped - self.min_length) / (self.max_length - self.min_length))
118
+ t_out = 1 - torch.exp(mu) / (torch.exp(mu) + (1 / (1 - t) - 1) ** sigma)
119
+ if self.use_sine:
120
+ t_out = torch.sin(t_out * math.pi / 2)
121
+ else:
122
+ # Scalar path (original behavior)
123
+ seq_len = min(max(seq_len, self.min_length), self.max_length)
124
+ sigma = 1.0
125
+ mu = - (self.base_shift + (self.max_shift - self.base_shift) * (seq_len - self.min_length) / (self.max_length - self.min_length))
126
+ t_out = 1 - math.exp(mu) / (math.exp(mu) + (1 / (1 - t) - 1) ** sigma)
127
+
128
+ if self.use_sine:
129
+ t_out = torch.sin(t_out * math.pi / 2)
130
+
131
+ return t_out
132
+
133
+
134
+ class LogSNRShift:
135
+ """Adaptive log-SNR distribution shift.
136
+
137
+ Maps t∈[0,1] to log-SNR-spaced values while preserving order (0→0, 1→1).
138
+ Equivalent to applying: logsnr = linspace(logsnr_end, logsnr_start, N)
139
+ then t = sigmoid(-logsnr), which spaces steps uniformly in log-SNR.
140
+
141
+ logsnr_start (the high-t bound) scales with sequence length following
142
+ the "-1 per doubling" rule:
143
+ logsnr_start = anchor_logsnr - rate * log₂(seq_len / anchor_length)
144
+
145
+ This captures the empirical finding that the critical log-SNR point
146
+ (where structure emerges from noise) drops by ~rate for each doubling
147
+ of sequence length. logsnr_end (the low-t bound) is fixed because
148
+ low-t refinement is purely local.
149
+ """
150
+
151
+ def __init__(self, anchor_length=2000, anchor_logsnr=-6.2,
152
+ rate=1.0, logsnr_end=2.0):
153
+ self.anchor_length = anchor_length
154
+ self.anchor_logsnr = anchor_logsnr
155
+ self.rate = rate
156
+ self.logsnr_end = logsnr_end
157
+
158
+ def get_logsnr_start(self, seq_len):
159
+ """Compute adaptive logsnr_start: drops by `rate` per doubling of seq_len."""
160
+ if isinstance(seq_len, torch.Tensor):
161
+ log2_ratio = torch.log2(seq_len.float() / self.anchor_length)
162
+ return self.anchor_logsnr - self.rate * log2_ratio
163
+ else:
164
+ log2_ratio = math.log2(seq_len / self.anchor_length)
165
+ return self.anchor_logsnr - self.rate * log2_ratio
166
+
167
+ def shift(self, t: torch.Tensor, seq_len: tp.Union[int, torch.Tensor]):
168
+ """Transform t∈[0,1] to log-SNR-spaced t with adaptive bounds.
169
+
170
+ Maps through: logsnr = logsnr_end - t * (logsnr_end - logsnr_start)
171
+ t_out = sigmoid(-logsnr)
172
+
173
+ Preserves order: 0→~0, 1→~1, with exact endpoint preservation.
174
+
175
+ Args:
176
+ t: Timesteps tensor of shape (batch_size,) or (steps,)
177
+ seq_len: Either a scalar int or tensor of shape (batch_size,)
178
+ Returns:
179
+ Log-SNR-spaced timesteps in [0, 1].
180
+ """
181
+ t_original = t
182
+ logsnr_start = self.get_logsnr_start(seq_len)
183
+
184
+ if isinstance(seq_len, torch.Tensor):
185
+ logsnr_start = logsnr_start.to(t.device)
186
+ if t.dim() == 1 and logsnr_start.dim() == 1 and t.shape[0] != logsnr_start.shape[0]:
187
+ t = t.unsqueeze(0)
188
+ logsnr_start = logsnr_start.unsqueeze(1)
189
+
190
+ # Map t through log-SNR space (monotonically: low t → high logsnr → low t_out)
191
+ logsnr = self.logsnr_end - t * (self.logsnr_end - logsnr_start)
192
+ t_out = torch.sigmoid(-logsnr)
193
+
194
+ # Preserve exact endpoints
195
+ t_out = torch.where(t_original <= 0, torch.zeros_like(t_out), t_out)
196
+ t_out = torch.where(t_original >= 1, torch.ones_like(t_out), t_out)
197
+
198
+ return t_out
stable_audio_3/inference/sampling.py ADDED
@@ -0,0 +1,523 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import typing as tp
3
+ from tqdm import trange, tqdm
4
+ import torch.distributions as dist
5
+
6
+ from ..data.utils import create_padding_mask_from_lengths, compute_effective_seq_len_from_conditioning
7
+
8
+
9
+ def build_schedule(
10
+ steps: int,
11
+ sigma_max: float = 1.0,
12
+ dist_shift = None,
13
+ effective_seq_len: tp.Union[int, torch.Tensor, None] = None,
14
+ fallback_seq_len: tp.Optional[int] = None,
15
+ include_endpoint: bool = True,
16
+ device: tp.Union[str, torch.device] = "cpu",
17
+ ) -> torch.Tensor:
18
+ """Build a timestep schedule for diffusion sampling.
19
+
20
+ Returns a 1D tensor of shape (N,) where N = steps+1 (if include_endpoint)
21
+ or steps (if not), OR a 2D tensor of shape (batch_size, N) when
22
+ effective_seq_len is a tensor and dist_shift produces per-element schedules.
23
+
24
+ Args:
25
+ steps: Number of sampling steps.
26
+ sigma_max: Starting noise level (1.0 for full generation, <1.0 for variations).
27
+ dist_shift: Optional distribution shift object (FluxDistributionShift,
28
+ DistributionShift, LogSNRShift, etc.). Applied to warp the linear schedule.
29
+ effective_seq_len: Sequence length for dist_shift. Scalar int or
30
+ tensor of shape (batch_size,) for per-element schedules.
31
+ fallback_seq_len: Fallback when effective_seq_len is None (typically x.shape[-1]).
32
+ include_endpoint: If True, schedule includes 0 as final value (RF samplers).
33
+ If False, excludes 0 (v-diffusion DDIM).
34
+ device: Device for the output tensor.
35
+ """
36
+ n_points = steps + 1 if include_endpoint else steps
37
+
38
+ if include_endpoint:
39
+ t = torch.linspace(sigma_max, 0, n_points, device=device)
40
+ else:
41
+ t = torch.linspace(sigma_max, 0, n_points + 1, device=device)[:-1]
42
+
43
+ if dist_shift is not None:
44
+ seq_len = effective_seq_len if effective_seq_len is not None else fallback_seq_len
45
+ if isinstance(seq_len, torch.Tensor):
46
+ # Clamp per-element sequence lengths to avoid zeros causing log/NaN issues
47
+ seq_len = torch.clamp(seq_len, min=1)
48
+ elif seq_len is not None:
49
+ # Clamp scalar sequence length to at least 1
50
+ seq_len = max(int(seq_len), 1)
51
+ t = dist_shift.shift(t, seq_len)
52
+
53
+ # Ensure the first timestep remains aligned with sigma_max after shifting.
54
+ # This keeps the schedule consistent with the initialization in sample_diffusion(),
55
+ # which mixes init_data using sigma_max.
56
+ if isinstance(t, torch.Tensor):
57
+ sigma_max_tensor = t.new_tensor(sigma_max)
58
+ if t.ndim == 1:
59
+ t[0] = sigma_max_tensor
60
+ else:
61
+ # For batched/per-element schedules, enforce sigma_max at the first time index.
62
+ t[..., 0] = sigma_max_tensor
63
+
64
+ return t
65
+
66
+
67
+ def sample_timesteps_logsnr(batch_size, mean_logsnr=-1.2, std_logsnr=2.0):
68
+ """
69
+ Sample timesteps for diffusion training by sampling logSNR values and converting to t.
70
+
71
+ Args:
72
+ batch_size (int): Number of timesteps to sample
73
+ mean_logsnr (float): Mean of the logSNR Gaussian distribution
74
+ std_logsnr (float): Standard deviation of the logSNR Gaussian distribution
75
+
76
+ Returns:
77
+ torch.Tensor: Tensor of shape (batch_size,) containing timestep values t in [0, 1]
78
+ """
79
+ # Sample logSNR from Gaussian distribution
80
+ logsnr = torch.randn(batch_size) * std_logsnr + mean_logsnr
81
+
82
+ # Convert logSNR to timesteps using the logistic function
83
+ # Since logSNR = ln((1-t)/t), we can solve for t:
84
+ # t = 1 / (1 + exp(logsnr))
85
+ t = torch.sigmoid(-logsnr)
86
+
87
+ # Clamp values to ensure numerical stability
88
+ t = t.clamp(1e-4, 1 - 1e-4)
89
+
90
+ return t
91
+
92
+ def sample_timesteps_logsnr_uniform(batch_size, min_logsnr=-6, max_logsnr=5.0):
93
+ """
94
+ Sample timesteps for diffusion training by sampling logSNR values and converting to t.
95
+
96
+ Args:
97
+ batch_size (int): Number of timesteps to sample
98
+ min_logsnr (float): Minimum logSNR value
99
+ max_logsnr (float): Maximum logSNR value
100
+
101
+ Returns:
102
+ torch.Tensor: Tensor of shape (batch_size,) containing timestep values t in [0, 1]
103
+ """
104
+ # Sample logSNR from uniform distribution
105
+ logsnr = torch.rand(batch_size) * (max_logsnr - min_logsnr) + min_logsnr
106
+
107
+ # Convert logSNR to timesteps using the logistic function
108
+ # Since logSNR = ln((1-t)/t), we can solve for t:
109
+ # t = 1 / (1 + exp(logsnr))
110
+ t = torch.sigmoid(-logsnr)
111
+
112
+ # Clamp values to ensure numerical stability
113
+ t = t.clamp(1e-4, 1 - 1e-4)
114
+
115
+ return t
116
+
117
+ def truncated_logistic_normal_rescaled(shape, left_trunc=0.075, right_trunc=1):
118
+ """
119
+
120
+ shape: shape of the output tensor
121
+ left_trunc: left truncation point, fraction of probability to be discarded
122
+ right_trunc: right truncation boundary, should be 1 (never seen at test time)
123
+ """
124
+
125
+ # Step 1: Sample from the logistic normal distribution (sigmoid of normal)
126
+ logits = torch.randn(shape)
127
+
128
+ # Step 2: Apply the CDF transformation of the normal distribution
129
+ normal_dist = dist.Normal(0, 1)
130
+ cdf_values = normal_dist.cdf(logits)
131
+
132
+ # Step 3: Define the truncation bounds on the CDF
133
+ lower_bound = normal_dist.cdf(torch.logit(torch.tensor(left_trunc)))
134
+ upper_bound = normal_dist.cdf(torch.logit(torch.tensor(right_trunc)))
135
+
136
+ # Step 4: Rescale linear CDF values into the truncated region (between lower_bound and upper_bound)
137
+ truncated_cdf_values = lower_bound + (upper_bound - lower_bound) * cdf_values
138
+
139
+ # Step 5: Map back to logistic-normal space using inverse CDF
140
+ truncated_samples = torch.sigmoid(normal_dist.icdf(truncated_cdf_values))
141
+
142
+ # Step 6: Rescale values so that min is 0 and max is just below 1
143
+ rescaled_samples = (truncated_samples - left_trunc) / (right_trunc - left_trunc)
144
+
145
+ return rescaled_samples
146
+
147
+ def sample_discrete_euler(model, x, sigmas, callback=None, disable_tqdm=False, **extra_args):
148
+ """Draws samples from a model given starting noise. Euler method
149
+
150
+ Args:
151
+ sigmas: Pre-computed schedule tensor. Shape (steps+1,) for global schedule
152
+ or (batch_size, steps+1) for per-element schedules.
153
+ """
154
+ t = sigmas
155
+
156
+ # Check if we have per-element schedules (batch_size, steps+1) or global schedule (steps+1,)
157
+ per_element_schedule = t.dim() == 2
158
+
159
+ t = t.to(x.device)
160
+ num_steps = t.shape[-1] - 1
161
+
162
+ for i in tqdm(range(num_steps), disable=disable_tqdm):
163
+ if per_element_schedule:
164
+ # Per-element schedules: t has shape (batch_size, steps+1)
165
+ t_curr_tensor = t[:, i].to(x.dtype) # (batch_size,)
166
+ t_prev = t[:, i + 1].to(x.dtype) # (batch_size,)
167
+ dt = t_prev - t_curr_tensor # (batch_size,)
168
+ # Reshape for broadcasting with x: (batch_size,) -> (batch_size, 1, 1)
169
+ dt_broadcast = dt.view(-1, 1, 1)
170
+ else:
171
+ # Global schedule: t has shape (steps+1,)
172
+ t_curr = t[i]
173
+ t_prev = t[i + 1]
174
+ t_curr_tensor = t_curr * torch.ones((x.shape[0],), dtype=x.dtype, device=x.device)
175
+ dt = t_prev - t_curr
176
+ dt_broadcast = dt
177
+
178
+ v = model(x, t_curr_tensor, **extra_args)
179
+
180
+ if callback is not None:
181
+ denoised = x - t_curr_tensor[:, None, None] * v
182
+ callback({'x': x, 't': t_curr_tensor, 'sigma': t_curr_tensor, 'i': i, 'denoised': denoised})
183
+
184
+ x = x + dt_broadcast * v
185
+
186
+ # If we are on the last timestep, output the denoised data
187
+ return x
188
+
189
+ def sample_rk4(model, x, sigmas, callback=None, disable_tqdm=False, **extra_args):
190
+ """Draws samples from a model given starting noise. 4th-order Runge-Kutta
191
+
192
+ Args:
193
+ sigmas: Pre-computed schedule tensor of shape (steps+1,).
194
+ Per-element schedules not supported for RK4.
195
+ """
196
+ # Make tensor of ones to broadcast the single t values
197
+ ts = x.new_ones([x.shape[0]])
198
+
199
+ t = sigmas
200
+
201
+ t = t.to(x.device)
202
+
203
+ for i, (t_curr, t_prev) in enumerate(tqdm(zip(t[:-1], t[1:]), disable=disable_tqdm)):
204
+ # Broadcast the current timestep to the correct shape
205
+ t_curr_tensor = t_curr * ts
206
+ dt = t_prev - t_curr # we solve backwards in our formulation
207
+
208
+ k1 = model(x, t_curr_tensor, **extra_args)
209
+
210
+ if callback is not None:
211
+ denoised = x - t_curr * k1
212
+ callback({'x': x, 't': t_curr, 'sigma': t_curr, 'i': i, 'denoised': denoised})
213
+
214
+ k2 = model(x + dt / 2 * k1, (t_curr + dt / 2) * ts, **extra_args)
215
+ k3 = model(x + dt / 2 * k2, (t_curr + dt / 2) * ts, **extra_args)
216
+
217
+ # Clamp t_prev to avoid evaluating model at exactly t=0
218
+ # (models aren't trained at t=0 and may return garbage/NaN)
219
+ t_prev_eval = t_prev.clamp(min=1e-5)
220
+ k4 = model(x + dt * k3, t_prev_eval * ts, **extra_args)
221
+
222
+ x = x + dt / 6 * (k1 + 2 * k2 + 2 * k3 + k4)
223
+
224
+ # If we are on the last timestep, output the denoised data
225
+ return x
226
+
227
+ def sample_flow_dpmpp(model, x, sigmas, callback=None, disable_tqdm=False, **extra_args):
228
+ """Draws samples from a model given starting noise. DPM-Solver++ for RF models
229
+
230
+ Args:
231
+ sigmas: Pre-computed schedule tensor. Shape (steps+1,) for global schedule
232
+ or (batch_size, steps+1) for per-element schedules.
233
+ """
234
+ t = sigmas
235
+
236
+ # Check if we have per-element schedules (batch_size, steps+1) or global schedule (steps+1,)
237
+ per_element_schedule = t.dim() == 2
238
+
239
+ t = t.to(x.device)
240
+ num_steps = t.shape[-1] - 1
241
+
242
+ old_denoised = None
243
+
244
+ # Clamp t to avoid numerical issues with log(0) and division by zero
245
+ # This prevents inf/-inf values that can cause NaN propagation
246
+ log_snr = lambda t: ((1-t).clamp(min=1e-10) / t.clamp(min=1e-10)).log()
247
+
248
+ for i in trange(num_steps, disable=disable_tqdm):
249
+ if per_element_schedule:
250
+ # Per-element schedules: t has shape (batch_size, steps+1)
251
+ t_curr = t[:, i] # (batch_size,)
252
+ t_next = t[:, i + 1] # (batch_size,)
253
+ t_prev = t[:, i - 1] if i > 0 else None
254
+ # Reshape for broadcasting with x: (batch_size,) -> (batch_size, 1, 1)
255
+ t_curr_broadcast = t_curr.view(-1, 1, 1)
256
+ t_next_broadcast = t_next.view(-1, 1, 1)
257
+ t_curr_tensor = t_curr # already (batch_size,)
258
+ else:
259
+ # Global schedule: t has shape (steps+1,)
260
+ t_curr = t[i]
261
+ t_next = t[i + 1]
262
+ t_prev = t[i - 1] if i > 0 else None
263
+ t_curr_broadcast = t_curr
264
+ t_next_broadcast = t_next
265
+ t_curr_tensor = t_curr.expand(x.shape[0])
266
+
267
+ model_output = model(x, t_curr_tensor, **extra_args)
268
+ denoised = x - t_curr_broadcast * model_output
269
+
270
+ if callback is not None:
271
+ callback({'x': x, 'i': i, 't': t_curr, 'sigma': t_curr, 'denoised': denoised})
272
+
273
+ alpha_t = 1 - t_next_broadcast
274
+
275
+ # For rectified flow, compute the DPM++ coefficient directly without log_snr
276
+ # to avoid numerical issues at t=0 or t=1
277
+ # The formula is: (-h).expm1() = (t_next - t_curr) / [(1 - t_next) * t_curr]
278
+ # Note: t_next < t_curr, so this is negative
279
+ # We'll compute this directly instead of going through log_snr
280
+ dt = t_next_broadcast - t_curr_broadcast
281
+ # Clamp to avoid division by zero when t_curr or t_next are at boundaries
282
+ dpmpp_coeff = dt / ((1 - t_next_broadcast).clamp(min=1e-10) * t_curr_broadcast.clamp(min=1e-10))
283
+
284
+ # Check if this is the first step or the last step (t_next == 0)
285
+ is_first_step = old_denoised is None
286
+ is_last_step = (t_next_broadcast == 0).all() if per_element_schedule else (t_next == 0)
287
+
288
+ if is_first_step or is_last_step:
289
+ # First-order update using the directly computed coefficient
290
+ x = (t_next_broadcast / t_curr_broadcast.clamp(min=1e-10)) * x - alpha_t * dpmpp_coeff * denoised
291
+ else:
292
+ # Second-order update with Richardson extrapolation
293
+ if per_element_schedule:
294
+ t_prev_broadcast = t_prev.view(-1, 1, 1)
295
+ else:
296
+ t_prev_broadcast = t_prev
297
+ # Compute r = h_last / h in log-SNR space for second-order correction
298
+ # h = log_snr(t_next) - log_snr(t_curr), h_last = log_snr(t_curr) - log_snr(t_prev)
299
+ h = log_snr(t_next_broadcast) - log_snr(t_curr_broadcast)
300
+ h_last = log_snr(t_curr_broadcast) - log_snr(t_prev_broadcast)
301
+ r = h_last / h
302
+ denoised_d = (1 + 1 / (2 * r)) * denoised - (1 / (2 * r)) * old_denoised
303
+ x = (t_next_broadcast / t_curr_broadcast.clamp(min=1e-10)) * x - alpha_t * dpmpp_coeff * denoised_d
304
+
305
+ old_denoised = denoised
306
+ return x
307
+
308
+ def sample_flow_pingpong(model, x, sigmas, callback=None, disable_tqdm=False, **extra_args):
309
+ """Draws samples from a model given starting noise. Ping-pong sampling for distilled models
310
+
311
+ Args:
312
+ sigmas: Pre-computed schedule tensor. Shape (steps+1,) for global schedule
313
+ or (batch_size, steps+1) for per-element schedules.
314
+ """
315
+ t = sigmas
316
+
317
+ # Check if we have per-element schedules (batch_size, steps+1) or global schedule (steps+1,)
318
+ per_element_schedule = t.dim() == 2
319
+
320
+ t = t.to(x.device)
321
+ num_steps = t.shape[-1] - 1
322
+
323
+ for i in trange(num_steps, disable=disable_tqdm):
324
+ if per_element_schedule:
325
+ # Per-element schedules: t has shape (batch_size, steps+1)
326
+ t_curr = t[:, i].to(x.dtype) # (batch_size,)
327
+ t_next = t[:, i + 1].to(x.dtype) # (batch_size,)
328
+ # Reshape for broadcasting with x: (batch_size,) -> (batch_size, 1, 1)
329
+ t_curr_broadcast = t_curr.view(-1, 1, 1)
330
+ t_next_broadcast = t_next.view(-1, 1, 1)
331
+ else:
332
+ # Global schedule: t has shape (steps+1,)
333
+ t_curr = t[i].to(x.dtype)
334
+ t_next = t[i + 1].to(x.dtype)
335
+ t_curr_broadcast = t_curr
336
+ t_next_broadcast = t_next
337
+
338
+ # Model forward
339
+ if per_element_schedule:
340
+ t_curr_tensor = t_curr # already (batch_size,)
341
+ else:
342
+ t_curr_tensor = t_curr * torch.ones((x.shape[0],), dtype=x.dtype, device=x.device)
343
+
344
+ denoised = x - t_curr_broadcast * model(x, t_curr_tensor, **extra_args)
345
+
346
+ if callback is not None:
347
+ callback({'x': x, 'i': i, 't': t_curr, 'sigma': t_curr, 'sigma_hat': t_curr, 'denoised': denoised})
348
+
349
+ x = (1 - t_next_broadcast) * denoised + t_next_broadcast * torch.randn_like(x)
350
+
351
+ return x
352
+
353
+
354
+
355
+ @torch.no_grad()
356
+ def sample_diffusion(
357
+ model,
358
+ noise: torch.Tensor,
359
+ cond_inputs: dict,
360
+ diffusion_objective: str,
361
+ steps: int,
362
+ cfg_scale: float = 1.0,
363
+ # Varlen support
364
+ conditioning: tp.Optional[tp.List[dict]] = None,
365
+ sample_rate: int = 44100,
366
+ pretransform = None,
367
+ mask_padding_attention: bool = False,
368
+ use_effective_length_for_schedule: bool = False,
369
+ headroom_seconds: float = 5.0,
370
+ padding_mask: tp.Optional[torch.Tensor] = None,
371
+ # Timestep schedule
372
+ dist_shift = None,
373
+ # Sampler options
374
+ sampler_type: str = None,
375
+ batch_cfg: bool = True,
376
+ rescale_cfg: bool = False,
377
+ # CFG options
378
+ apg_scale: float = 1.0,
379
+ # Init data (variation / img2img)
380
+ init_data: tp.Optional[torch.Tensor] = None,
381
+ init_noise_level: float = 1.0,
382
+ # Other
383
+ callback = None,
384
+ disable_tqdm: bool = False,
385
+ decode: bool = True,
386
+ chunked_decode: tp.Optional[bool] = None,
387
+ **sampler_kwargs
388
+ ) -> torch.Tensor:
389
+ """
390
+ Unified sampling function for diffusion models. Handles all diffusion objectives,
391
+ varlen support (padding_mask + effective_seq_len), timestep scheduling, and init_data
392
+ for variation/img2img.
393
+
394
+ Args:
395
+ model: The diffusion model backbone (model.model, not the wrapper)
396
+ noise: Initial noise tensor of shape (B, C, T)
397
+ cond_inputs: Pre-processed conditioning inputs dict (merged positive + negative)
398
+ diffusion_objective: One of "v", "rectified_flow", "rf_denoiser"
399
+ steps: Number of sampling steps
400
+ cfg_scale: Classifier-free guidance scale
401
+ conditioning: List of conditioning dicts (for computing varlen from seconds_total)
402
+ sample_rate: Audio sample rate
403
+ pretransform: Optional pretransform for decoding latents and computing downsampling_ratio
404
+ mask_padding_attention: Whether to create padding_mask for attention
405
+ use_effective_length_for_schedule: Whether to use effective_seq_len for dist_shift
406
+ padding_mask: Optional pre-computed padding mask (B, T). If provided, skips
407
+ internal mask computation. Use this to ensure consistency with training masks.
408
+ headroom_seconds: Extra seconds beyond seconds_total for valid region
409
+ dist_shift: Distribution shift object for warping the timestep schedule, or None
410
+ sampler_type: Sampler type. For RF: "euler", "rk4", "dpmpp", "pingpong".
411
+ For v-diffusion: "v-ddim", "v-ddim-cfgpp", or k-diffusion types like "dpmpp-2m-sde".
412
+ batch_cfg: Whether to use batched CFG
413
+ rescale_cfg: Whether to use rescaled CFG
414
+ apg_scale: APG (Adaptive Projected Guidance) scale. 1.0 = full APG, 0.0 = vanilla CFG
415
+ init_data: Optional pre-encoded latent tensor for variation/img2img (shape: B, C, T)
416
+ init_noise_level: Noise level (sigma_max) when using init_data. 1.0 = full noise (no variation).
417
+ callback: Optional callback for progress reporting
418
+ disable_tqdm: Whether to disable progress bar
419
+ decode: Whether to decode latents using pretransform
420
+ **sampler_kwargs: Additional kwargs passed to sampler
421
+
422
+ Returns:
423
+ Generated samples (decoded audio if decode=True, else latents)
424
+ """
425
+ device = noise.device
426
+ batch_size = noise.shape[0]
427
+ latent_seq_len = noise.shape[-1]
428
+
429
+ # Compute downsampling ratio
430
+ downsampling_ratio = pretransform.downsampling_ratio if pretransform is not None else 1
431
+
432
+ # Default sampler_type per objective
433
+ if sampler_type is None:
434
+ sampler_type = "pingpong" if diffusion_objective == "rf_denoiser" else "euler"
435
+
436
+
437
+ # Compute effective_seq_len for dist_shift if enabled
438
+ effective_seq_len = None
439
+ if use_effective_length_for_schedule and conditioning is not None:
440
+ effective_seq_len = compute_effective_seq_len_from_conditioning(
441
+ conditioning, sample_rate, downsampling_ratio, device
442
+ )
443
+
444
+ # Create padding_mask for attention if enabled (skip if pre-computed mask provided)
445
+ if padding_mask is None and mask_padding_attention and conditioning is not None:
446
+ raw_effective_len = compute_effective_seq_len_from_conditioning(
447
+ conditioning, sample_rate, downsampling_ratio, device
448
+ )
449
+ if raw_effective_len is not None:
450
+ headroom_tokens = int(headroom_seconds * sample_rate / downsampling_ratio)
451
+ valid_lengths = (raw_effective_len + headroom_tokens).clamp(max=latent_seq_len).long()
452
+ padding_mask = create_padding_mask_from_lengths(valid_lengths, latent_seq_len)
453
+
454
+ # Determine sigma_max for schedule
455
+ sigma_max = init_noise_level if init_data is not None else 1.0
456
+
457
+ # Mix init_data with noise for variation/img2img
458
+ # For k-diffusion v-diffusion samplers, init_data is passed through to sample_k
459
+ # which handles mixing internally with its own sigma scaling
460
+ k_diff_sampler_types = {"k-heun", "k-lms", "k-dpmpp-2s-ancestral", "k-dpm-2",
461
+ "k-dpm-fast", "k-dpm-adaptive", "dpmpp-2m-sde", "dpmpp-3m-sde", "dpmpp-2m"}
462
+
463
+ if init_data is not None:
464
+ noise = init_data * (1 - sigma_max) + noise * sigma_max
465
+
466
+ # Build common sampler kwargs (conditioning + model-level params only).
467
+ # disable_tqdm and callback are passed explicitly to samplers that use them,
468
+ # not included here, to avoid leaking into model forward() calls.
469
+ common_kwargs = {
470
+ **cond_inputs,
471
+ "cfg_scale": cfg_scale,
472
+ "batch_cfg": batch_cfg,
473
+ "rescale_cfg": rescale_cfg,
474
+ "padding_mask": padding_mask,
475
+ "apg_scale": apg_scale,
476
+ **sampler_kwargs
477
+ }
478
+
479
+
480
+ if diffusion_objective in ["rectified_flow", "rf_denoiser"]:
481
+ # Remove v-diffusion-specific kwargs that don't apply to RF
482
+ common_kwargs.pop("sigma_min", None)
483
+ common_kwargs.pop("sigma_max", None)
484
+ common_kwargs.pop("rho", None)
485
+
486
+ # Build schedule
487
+ sigmas = build_schedule(
488
+ steps=steps, sigma_max=sigma_max,
489
+ dist_shift=dist_shift, effective_seq_len=effective_seq_len,
490
+ fallback_seq_len=latent_seq_len, include_endpoint=True, device=device
491
+ )
492
+
493
+ # Route to sampler
494
+ if sampler_type == "euler":
495
+ sampled = sample_discrete_euler(model, noise, sigmas=sigmas, callback=callback, disable_tqdm=disable_tqdm, **common_kwargs)
496
+ elif sampler_type == "rk4":
497
+ sampled = sample_rk4(model, noise, sigmas=sigmas, callback=callback, disable_tqdm=disable_tqdm, **common_kwargs)
498
+ elif sampler_type == "dpmpp":
499
+ sampled = sample_flow_dpmpp(model, noise, sigmas=sigmas, callback=callback, disable_tqdm=disable_tqdm, **common_kwargs)
500
+ elif sampler_type == "pingpong":
501
+ sampled = sample_flow_pingpong(model, noise, sigmas=sigmas, callback=callback, disable_tqdm=disable_tqdm, **common_kwargs)
502
+ else:
503
+ raise ValueError(f"Unknown sampler_type for {diffusion_objective}: {sampler_type}")
504
+
505
+ else:
506
+ raise ValueError(f"Unknown diffusion_objective: {diffusion_objective}")
507
+
508
+ # Decode if requested
509
+ if decode and pretransform is not None:
510
+ sampled = sampled.to(next(pretransform.parameters()).dtype)
511
+ sampled = pretransform.decode(sampled, chunked=chunked_decode)
512
+
513
+ # Zero out audio beyond valid region (padding positions decode to garbage)
514
+ if padding_mask is not None:
515
+ audio_mask = padding_mask.unsqueeze(1).repeat_interleave(downsampling_ratio, dim=-1)
516
+ # Trim or pad to match sampled length
517
+ if audio_mask.shape[-1] > sampled.shape[-1]:
518
+ audio_mask = audio_mask[..., :sampled.shape[-1]]
519
+ elif audio_mask.shape[-1] < sampled.shape[-1]:
520
+ audio_mask = torch.nn.functional.pad(audio_mask, (0, sampled.shape[-1] - audio_mask.shape[-1]), value=False)
521
+ sampled = sampled * audio_mask.to(sampled.dtype)
522
+
523
+ return sampled
stable_audio_3/interface/__init__.py ADDED
File without changes
stable_audio_3/interface/aeiou.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Modified from https://github.com/drscotthawley/aeiou/blob/main/aeiou/viz.py under Apache 2.0 License
2
+ # License can be found in LICENSES/LICENSE_AEIOU.txt
3
+
4
+ from matplotlib.backends.backend_agg import FigureCanvasAgg
5
+ import matplotlib.cm as cm
6
+ from matplotlib.colors import Normalize
7
+ from matplotlib.figure import Figure
8
+ import numpy as np
9
+ from PIL import Image
10
+
11
+ import torch
12
+
13
+ import torchaudio.transforms as T
14
+ from einops import rearrange
15
+
16
+ import numpy as np
17
+
18
+ def embeddings_table(tokens):
19
+ from wandb import Table
20
+ from pandas import DataFrame
21
+
22
+ "make a table of embeddings for use with wandb"
23
+ features, labels = [], []
24
+ embeddings = rearrange(tokens, 'b d n -> b n d') # each demo sample is n vectors in d-dim space
25
+ for i in range(embeddings.size()[0]): # nested for's are slow but sure ;-)
26
+ for j in range(embeddings.size()[1]):
27
+ features.append(embeddings[i,j].detach().cpu().numpy())
28
+ labels.append([f'demo{i}']) # labels does the grouping / color for each point
29
+ features = np.array(features)
30
+ labels = np.concatenate(labels, axis=0)
31
+ cols = [f"dim_{i}" for i in range(features.shape[1])]
32
+ df = DataFrame(features, columns=cols)
33
+ df['LABEL'] = labels
34
+ return Table(columns=df.columns.to_list(), data=df.values)
35
+
36
+ def project_down(tokens, # batched high-dimensional data with dims (b,d,n)
37
+ proj_dims=3, # dimensions to project to
38
+ method='pca', # projection method: 'pca'|'umap'
39
+ n_neighbors=10, # umap parameter for number of neighbors
40
+ min_dist=0.3, # umap param for minimum distance
41
+ debug=False, # print more info while running
42
+ **kwargs, # other params to pass to umap, cf. https://umap-learn.readthedocs.io/en/latest/parameters.html
43
+ ):
44
+ "this projects to lower dimenions, grabbing the first _`proj_dims`_ dimensions"
45
+ method = method.lower()
46
+ A = rearrange(tokens, 'b d n -> (b n) d') # put all the vectors into the same d-dim space
47
+ if A.shape[-1] > proj_dims:
48
+ if method=='umap':
49
+ from umap import UMAP
50
+ proj_data = UMAP(n_components=proj_dims, n_neighbors=n_neighbors, min_dist=min_dist,
51
+ metric='correlation', **kwargs).fit_transform(A.cpu().numpy())
52
+ proj_data = torch.from_numpy(proj_data).to(tokens.device)
53
+ else: # pca
54
+ (U, S, V) = torch.pca_lowrank(A)
55
+ proj_data = torch.matmul(A, V[:, :proj_dims]) # this is the actual PCA projection step
56
+ else:
57
+ proj_data = A
58
+ if debug: print("proj_data.shape =",proj_data.shape)
59
+ return torch.reshape(proj_data, (tokens.size()[0], -1, proj_dims)) # put it in shape [batch, n, proj_dims]
60
+
61
+
62
+ def proj_pca(tokens, proj_dims=3):
63
+ return project_down(do_proj, method='pca', proj_dims=proj_dims)
64
+
65
+ def point_cloud(
66
+ tokens, # embeddings / latent vectors. shape = (b, d, n)
67
+ method='pca', # projection method for 3d mapping: 'pca' | 'umap'
68
+ color_scheme='batch', # 'batch': group by sample; integer n: n groups, sequentially, otherwise color sequentially by time step
69
+ output_type='wandbobj', # plotly | points | wandbobj. NOTE: WandB can do 'plotly' directly!
70
+ mode='markers', # plotly scatter mode. 'lines+markers' or 'markers'
71
+ size=3, # size of the dots
72
+ line=dict(color='rgba(10,10,10,0.01)'), # if mode='lines+markers', plotly line specifier. cf. https://plotly.github.io/plotly.py-docs/generated/plotly.graph_objects.scatter3d.html#plotly.graph_objects.scatter3d.Line
73
+ ds_preproj=1, # EXPERIMENTAL: downsampling factor before projecting (1=no downsampling). Could screw up colors
74
+ ds_preplot=1, # EXPERIMENTAL: downsampling factor before plotting (1=no downsampling). Could screw up colors
75
+ debug=False, # print more info
76
+ colormap=None, # valid color map to use, None=defaults
77
+ darkmode=False, # dark background, white fonts
78
+ layout_dict=None, # extra plotly layout options such as camera orientation
79
+ rgb_float = False, # if True, color_scheme is RGB float values
80
+ **kwargs, # anything else to pass along
81
+ ):
82
+ "returns a 3D point cloud of the tokens"
83
+ if ds_preproj != 1:
84
+ tokens = tokens[torch.randperm(tokens.size()[0])] # EXPERIMENTAL: to 'correct' for possible weird effects of downsampling
85
+ tokens = tokens[::ds_preproj]
86
+ if debug: print("tokens.shape =",tokens.shape)
87
+
88
+ data = project_down(tokens, method=method, debug=debug, **kwargs).cpu().numpy()
89
+ if debug: print("data.shape =",data.shape)
90
+ if data.shape[-1] < 3: # for data less than 3D, embed it in 3D
91
+ data = np.pad(data, ((0,0),(0,0),(0, 3-data.shape[-1])), mode='constant', constant_values=0)
92
+
93
+ bytime = False
94
+ points = []
95
+ if color_scheme=='batch': # all dots in same batch index same color, each batch-index unique (almost)
96
+ ncolors = data.shape[0]
97
+ cmap, norm = cm.tab20, Normalize(vmin=0, vmax=ncolors)
98
+ elif isinstance(color_scheme, int) or color_scheme.isnumeric(): # n groups, by batch-indices, sequentially
99
+ ncolors = int(color_scheme)
100
+ cmap, norm = cm.tab20, Normalize(vmin=0, vmax=ncolors)
101
+ else: # time steps match up
102
+ bytime, ncolors = True, data.shape[1]
103
+ cmap, norm = cm.viridis, Normalize(vmin=0, vmax=ncolors)
104
+
105
+ cmap = cmap if colormap is None else colormap # overwrite default cmap with user choice if given
106
+
107
+ points = []
108
+ for bi in range(data.shape[0]): # batch index
109
+ if color_scheme=='batch':
110
+ [r, g, b, _] = [int(255*x) for x in cmap(norm(bi+1))]
111
+ elif isinstance(color_scheme, int) or color_scheme.isnumeric():
112
+ grouplen = data.shape[0]//(ncolors)
113
+ #if debug: print(f"bi, grouplen, bi//grouplen = ",bi, grouplen, bi//grouplen)
114
+ [r, g, b, _] = [int(255*x) for x in cmap(norm(bi//grouplen))]
115
+ #if debug: print("r,g,b = ",r,g,b)
116
+
117
+ if rgb_float: [r, g, b] = [x/255 for x in [r, g, b]]
118
+
119
+ for n in range(data.shape[1]): # across time
120
+ if bytime: [r, g, b, _] = [int(255*x) for x in cmap(norm(n))]
121
+ points.append([data[bi,n,0], data[bi,n,1], data[bi,n,2], r, g, b]) # include dot colors with point coordinates
122
+
123
+ point_cloud = np.array(points)
124
+
125
+ if output_type == 'points':
126
+ return point_cloud
127
+ elif output_type =='plotly':
128
+ import plotly.graph_objects as go
129
+
130
+ fig = go.Figure(data=[go.Scatter3d(
131
+ x=point_cloud[::ds_preplot,0], y=point_cloud[::ds_preplot,1], z=point_cloud[::ds_preplot,2],
132
+ marker=dict(size=size, color=point_cloud[:,3:6]),
133
+ mode=mode,
134
+ # show batch index and time index in tooltips:
135
+ text=[ f'bi: {i*ds_preplot}, ti: {j}' for i in range(data.shape[0]//ds_preplot) for j in range(data.shape[1]) ],
136
+ line=line,
137
+ )])
138
+ fig.update_layout(margin=dict(l=0, r=0, b=0, t=0)) # tight layout
139
+ if darkmode:
140
+ fig.layout.template = 'plotly_dark'
141
+ if isinstance(darkmode, str): # 'rgb(12,15,24)'gradio margins in dark mode
142
+ fig.update_layout( paper_bgcolor=darkmode)
143
+ if layout_dict:
144
+ fig.update_layout( **layout_dict )
145
+
146
+ if debug: print("point_cloud: fig made. returning")
147
+ return fig
148
+ else:
149
+ from wandb import Object3D
150
+ return Object3D(point_cloud)
151
+
152
+ def pca_point_cloud(
153
+ tokens, # embeddings / latent vectors. shape = (b, d, n)
154
+ color_scheme='batch', # 'batch': group by sample, otherwise color sequentially
155
+ output_type='wandbobj', # plotly | points | wandbobj. NOTE: WandB can do 'plotly' directly!
156
+ mode='markers', # plotly scatter mode. 'lines+markers' or 'markers'
157
+ size=3, # size of the dots
158
+ line=dict(color='rgba(10,10,10,0.01)'), # if mode='lines+markers', plotly line specifier. cf. https://plotly.github.io/plotly.py-docs/generated/plotly.graph_objects.scatter3d.html#plotly.graph_objects.scatter3d.Line
159
+ **kwargs,
160
+ ):
161
+ return point_cloud(tokens, method='pca', color_scheme=color_scheme, output_type=output_type,
162
+ mode=mode, size=size, line=line, **kwargs)
163
+
164
+ def power_to_db(spec, *, amin = 1e-10):
165
+ magnitude = np.asarray(spec)
166
+
167
+ log_spec = 10.0 * np.log10(np.maximum(amin, magnitude))
168
+ log_spec -= 10.0 * np.log10(np.maximum(amin, 1))
169
+
170
+ log_spec = np.maximum(log_spec, log_spec.max() - 80)
171
+
172
+ return log_spec
173
+
174
+ def mel_spectrogram(waveform, power=2.0, sample_rate=48000, db=False, n_fft=1024, n_mels=128, debug=False):
175
+ "calculates data array for mel spectrogram (in however many channels)"
176
+ win_length = None
177
+ hop_length = n_fft//2 # 512
178
+
179
+ mel_spectrogram_op = T.MelSpectrogram(
180
+ sample_rate=sample_rate, n_fft=n_fft, win_length=win_length,
181
+ hop_length=hop_length, center=True, pad_mode="reflect", power=power,
182
+ norm='slaney', onesided=True, n_mels=n_mels, mel_scale="htk")
183
+
184
+ melspec = mel_spectrogram_op(waveform.float())
185
+ if db:
186
+ amp_to_db_op = T.AmplitudeToDB()
187
+ melspec = amp_to_db_op(melspec)
188
+ if debug:
189
+ print_stats(melspec, print=print)
190
+ print(f"torch.max(melspec) = {torch.max(melspec)}")
191
+ print(f"melspec.shape = {melspec.shape}")
192
+ return melspec
193
+
194
+ def spectrogram_image(
195
+ spec,
196
+ title=None,
197
+ ylabel='freq_bin',
198
+ aspect='auto',
199
+ xmax=None,
200
+ db_range=[35,120],
201
+ justimage=False,
202
+ figsize=(5, 4), # size of plot (if justimage==False)
203
+ context_mask=None, # 1D inpaint mask: 1 = context/kept (tinted), 0 = inpainted/generated
204
+ context_color=(0.8, 0.2, 0.2, 0.3), # RGBA color for context region overlay (red, visible against viridis)
205
+ ):
206
+ "Modified from PyTorch tutorial https://pytorch.org/tutorials/beginner/audio_feature_extractions_tutorial.html"
207
+ fig = Figure(figsize=figsize, dpi=100) if not justimage else Figure(figsize=(4.145, 4.145), dpi=100, tight_layout=True)
208
+ canvas = FigureCanvasAgg(fig)
209
+ axs = fig.add_subplot()
210
+ spec = spec.squeeze()
211
+ im = axs.imshow(power_to_db(spec), origin='lower', aspect=aspect, vmin=db_range[0], vmax=db_range[1])
212
+
213
+ # Overlay tint on context (kept) regions where mask == 1
214
+ if context_mask is not None:
215
+ context_mask = np.asarray(context_mask).flatten()
216
+ num_frames = spec.shape[-1]
217
+ # Resize mask to match spectrogram frames if needed
218
+ if len(context_mask) != num_frames:
219
+ indices = np.linspace(0, len(context_mask) - 1, num_frames).astype(int)
220
+ context_mask = context_mask[indices]
221
+ # Find contiguous context regions (mask > 0.5) and draw axvspan overlays
222
+ in_span = False
223
+ span_start = 0
224
+ for j in range(len(context_mask)):
225
+ if context_mask[j] > 0.5 and not in_span:
226
+ span_start = j
227
+ in_span = True
228
+ elif context_mask[j] <= 0.5 and in_span:
229
+ axs.axvspan(span_start - 0.5, j - 0.5, color=context_color)
230
+ in_span = False
231
+ if in_span:
232
+ axs.axvspan(span_start - 0.5, len(context_mask) - 0.5, color=context_color)
233
+
234
+ if xmax:
235
+ axs.set_xlim((0, xmax))
236
+ if justimage:
237
+ import matplotlib.pyplot as plt
238
+ axs.axis('off')
239
+ plt.tight_layout()
240
+ else:
241
+ axs.set_ylabel(ylabel)
242
+ axs.set_xlabel('frame')
243
+ axs.set_title(title or 'Spectrogram (dB)')
244
+ fig.colorbar(im, ax=axs)
245
+ canvas.draw()
246
+ rgba = np.asarray(canvas.buffer_rgba())
247
+ im = Image.fromarray(rgba)
248
+ if justimage: # remove tiny white border
249
+ b = 15 # border size
250
+ im = im.crop((b,b, im.size[0]-b, im.size[1]-b))
251
+ #print(f"im.size = {im.size}")
252
+ return im
253
+
254
+ def audio_spectrogram_image(waveform, power=2.0, sample_rate=48000, print=print, db=False, db_range=[35,120], justimage=False, log=False, figsize=(5, 4), context_mask=None):
255
+ "Wrapper for calling above two routines at once, does Mel scale; Modified from PyTorch tutorial https://pytorch.org/tutorials/beginner/audio_feature_extractions_tutorial.html"
256
+ melspec = mel_spectrogram(waveform, power=power, db=db, sample_rate=sample_rate, debug=log)
257
+ melspec = melspec[0] # TODO: only left channel for now
258
+ return spectrogram_image(melspec, title="MelSpectrogram", ylabel='mel bins (log freq)', db_range=db_range, justimage=justimage, figsize=figsize, context_mask=context_mask)
259
+
260
+ from matplotlib.ticker import AutoLocator
261
+ def tokens_spectrogram_image(
262
+ tokens, # the embeddings themselves (in some diffusion codes these are called 'tokens')
263
+ aspect='auto', # aspect ratio of plot
264
+ title='Embeddings', # title to put on top
265
+ ylabel='index', # label for y axis of plot
266
+ cmap='coolwarm', # colormap to use. (default used to be 'viridis')
267
+ symmetric=True, # make color scale symmetric about zero, i.e. +/- same extremes
268
+ figsize=(8, 4), # matplotlib size of the figure
269
+ dpi=100, # dpi of figure
270
+ mark_batches=False, # separate batches with dividing lines
271
+ debug=False, # print debugging info
272
+ ):
273
+ "for visualizing embeddings in a spectrogram-like way"
274
+ batch_size, dim, samples = tokens.shape
275
+ embeddings = rearrange(tokens, 'b d n -> (b n) d') # expand batches in time
276
+ vmin, vmax = None, None
277
+ if symmetric:
278
+ vmax = torch.abs(embeddings).max()
279
+ vmin = -vmax
280
+
281
+ fig = Figure(figsize=figsize, dpi=dpi)
282
+ canvas = FigureCanvasAgg(fig)
283
+ ax = fig.add_subplot()
284
+ if symmetric:
285
+ subtitle = f'min={embeddings.min():0.4g}, max={embeddings.max():0.4g}'
286
+ ax.set_title(title+'\n')
287
+ ax.text(x=0.435, y=0.9, s=subtitle, fontsize=11, ha="center", transform=fig.transFigure)
288
+ else:
289
+ ax.set_title(title)
290
+ ax.set_ylabel(ylabel)
291
+ ax.set_xlabel('time frame (samples, in batches)')
292
+ if mark_batches:
293
+ intervals = np.arange(batch_size)*samples
294
+ if debug: print("intervals = ",intervals)
295
+ ax.vlines(intervals, -10, dim+10, color='black', linestyle='dashed', linewidth=1)
296
+
297
+ im = ax.imshow(embeddings.cpu().numpy().T, origin='lower', aspect=aspect, interpolation='none', cmap=cmap, vmin=vmin,vmax=vmax) #.T because numpy is x/y 'backwards'
298
+ fig.colorbar(im, ax=ax)
299
+ fig.tight_layout()
300
+ canvas.draw()
301
+ rgba = np.asarray(canvas.buffer_rgba())
302
+ return Image.fromarray(rgba)
303
+
stable_audio_3/interface/diffusion_cond.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import numpy as np
3
+ import gradio as gr
4
+ import re
5
+ import subprocess
6
+ import torch
7
+ import torchaudio
8
+ import threading
9
+ import os, time, math
10
+
11
+ from einops import rearrange
12
+
13
+ from stable_audio_3.interface.aeiou import audio_spectrogram_image
14
+ from stable_audio_3.inference.distribution_shift import LogSNRShift, FluxDistributionShift, DistributionShift, IdentityDistributionShift
15
+ from stable_audio_3.models.lora import has_lora
16
+ from stable_audio_3.interface.reprompt import reprompt as _reprompt_fn, get_model as _reprompt_get_model, is_model_cached as _reprompt_is_model_cached
17
+
18
+ stable_audio_3_model = None
19
+ sample_size = 5324800
20
+ sample_rate = 44100
21
+ n_loras = 0
22
+ _LENGTH_EXTRACT_RE = re.compile(r' Length: (\d+) seconds\.?\s*$')
23
+
24
+
25
+ # when using a prompt in a filename
26
+ def condense_prompt(prompt):
27
+ pattern = r'[\\/:*?"<>|]'
28
+ # Replace special characters with hyphens
29
+ prompt = re.sub(pattern, '-', prompt)
30
+ # set a character limit
31
+ prompt = prompt[:150]
32
+ # zero length prompts may lead to filenames (ie ".wav") which seem cause problems with gradio
33
+ if len(prompt)==0:
34
+ prompt = "_"
35
+ return prompt
36
+
37
+ def generate_cond(
38
+ prompt,
39
+ negative_prompt=None,
40
+ seconds_total=30,
41
+ cfg_scale=6.0,
42
+ steps=250,
43
+ preview_every=None,
44
+ seed=-1,
45
+ sampler_type="dpmpp-3m-sde",
46
+ sigma_max=1000,
47
+ cfg_interval_min=0.0,
48
+ cfg_interval_max=1.0,
49
+ cfg_rescale=0.0,
50
+ cfg_norm_threshold=0.0,
51
+ apg_scale=1.0,
52
+ file_format="wav",
53
+ file_naming="verbose",
54
+ cut_to_seconds_total=False,
55
+ init_audio=None,
56
+ init_noise_level=1.0,
57
+ mask_maskstart=None,
58
+ mask_maskend=None,
59
+ inpaint_audio=None,
60
+ init_audio_type="Init audio",
61
+ inversion_steps=100,
62
+ inversion_gamma=0.3,
63
+ inversion_unconditional=False,
64
+ duration_padding_sec=6.0,
65
+ batch_size=1,
66
+ dist_shift=None,
67
+ *lora_args
68
+ ):
69
+
70
+ if torch.cuda.is_available():
71
+ torch.cuda.empty_cache()
72
+ gc.collect()
73
+
74
+ print(f"Prompt: {prompt}")
75
+
76
+ global preview_images
77
+ preview_images = []
78
+ if preview_every == 0:
79
+ preview_every = None
80
+
81
+ # Parse per-LoRA controls from trailing args
82
+ # Each LoRA has 5 controls: strength, interval_min, interval_max, layer_filter
83
+ lora_configs = None
84
+ if n_loras > 0 and len(lora_args) >= n_loras * 4:
85
+ lora_configs = []
86
+ for i in range(n_loras):
87
+ off = i * 4
88
+ strength = lora_args[off]
89
+ interval_min = lora_args[off + 1]
90
+ interval_max = lora_args[off + 2]
91
+ layer_filter = lora_args[off + 3]
92
+ stable_audio_3_model.set_lora_strength(strength, lora_index=i)
93
+ lora_configs.append({
94
+ "lora_index": i,
95
+ "interval": (interval_min, interval_max),
96
+ "layer_filter": layer_filter,
97
+ })
98
+
99
+ input_sample_size = sample_size
100
+
101
+ def progress_callback(callback_info):
102
+ global preview_images
103
+ denoised = callback_info["denoised"]
104
+ current_step = callback_info["i"]
105
+ t = callback_info["t"]
106
+ sigma = callback_info["sigma"]
107
+
108
+ # Extract scalar from tensor if needed (samplers pass tensors to avoid GPU sync)
109
+ if isinstance(t, torch.Tensor):
110
+ t = t[0].item() if t.dim() > 0 else t.item()
111
+ if isinstance(sigma, torch.Tensor):
112
+ sigma = sigma[0].item() if sigma.dim() > 0 else sigma.item()
113
+
114
+ log_snr = math.log(((1 - sigma) / sigma) + 1e-6)
115
+
116
+ if (current_step - 1) % preview_every == 0:
117
+ if stable_audio_3_model.model.pretransform is not None:
118
+ denoised = stable_audio_3_model.model.pretransform.decode(denoised)
119
+ denoised = rearrange(denoised, "b d n -> d (b n)")
120
+ denoised = denoised.clamp(-1, 1).mul(32767).to(torch.int16).cpu()
121
+ audio_spectrogram = audio_spectrogram_image(denoised, sample_rate=sample_rate)
122
+ preview_images.append((audio_spectrogram, f"Step {current_step} sigma={sigma:.3f} logSNR={log_snr:.3f}"))
123
+
124
+ if init_audio_type == "RF-Inversion":
125
+ inversion_params = {
126
+ "inversion_steps": inversion_steps,
127
+ "inversion_gamma": inversion_gamma,
128
+ "inversion_unconditional": inversion_unconditional,
129
+ "inversion_cfg_scale": 1.0,
130
+ "inversion_sigma_max": 1.0
131
+ }
132
+ else:
133
+ inversion_params = None
134
+
135
+ generate_args = {
136
+ "prompt": prompt,
137
+ "negative_prompt": negative_prompt,
138
+ "duration": seconds_total,
139
+ "steps": steps,
140
+ "cfg_scale": cfg_scale,
141
+ "cfg_interval": (cfg_interval_min, cfg_interval_max),
142
+ "lora_configs": lora_configs,
143
+ "batch_size": batch_size,
144
+ "sample_size": input_sample_size,
145
+ "seed": seed,
146
+ "sampler_type": sampler_type,
147
+ "sigma_max": sigma_max,
148
+ "init_audio": init_audio,
149
+ "init_noise_level": init_noise_level,
150
+ "callback": progress_callback if preview_every is not None else None,
151
+ "scale_phi": cfg_rescale,
152
+ "cfg_norm_threshold": cfg_norm_threshold,
153
+ "apg_scale": apg_scale,
154
+ "duration_padding_sec": duration_padding_sec,
155
+ "dist_shift": dist_shift,
156
+ }
157
+
158
+ # If inpainting, send mask args
159
+ # This will definitely change in the future
160
+ if inpaint_audio is not None:
161
+ generate_args.update({
162
+ "inpaint_audio": inpaint_audio,
163
+ "inpaint_mask_start_seconds": mask_maskstart,
164
+ "inpaint_mask_end_seconds": mask_maskend,
165
+ })
166
+
167
+ audio = stable_audio_3_model.generate(**generate_args)
168
+
169
+ # Filenaming convention
170
+ prompt_condensed = condense_prompt(prompt)
171
+ if file_naming=="verbose":
172
+ basename = prompt_condensed
173
+ if negative_prompt:
174
+ basename += ".neg-%s" % condense_prompt(negative_prompt)
175
+ basename += ".cfg%s" % (cfg_scale)
176
+ if sigma_max not in [1.0, 100.0]:
177
+ # this is a common parameter to tweak, if it's not a default value, put it in the verbose filename
178
+ basename += ".smx%s" % sigma_max
179
+ basename += ".%s" % seed
180
+ elif file_naming=="prompt":
181
+ basename = prompt_condensed
182
+ else:
183
+ # simple e.g. "output.wav"
184
+ basename = "output"
185
+
186
+ if file_format:
187
+ filename_extension = file_format.split(" ")[0].lower()
188
+ else:
189
+ filename_extension = "wav"
190
+ output_filename = "%s.%s" % (basename, filename_extension)
191
+ output_wav = "%s.wav" % basename
192
+
193
+ # Cut the extra silence off the end, if the user requested a smaller seconds_total
194
+ if cut_to_seconds_total:
195
+ audio = audio[:,:,:seconds_total*sample_rate]
196
+
197
+ # Encode the audio to WAV format
198
+ audio = rearrange(audio, "b d n -> d (b n)")
199
+ audio = audio.to(torch.float32).clamp(-1, 1).mul(32767).to(torch.int16).cpu()
200
+
201
+ # save as wav file
202
+ torchaudio.save(output_wav, audio, sample_rate)
203
+
204
+ # If file_format is other than wav, convert to other file format
205
+ cmd = ""
206
+ if file_format == "m4a aac_he_v2 32k":
207
+ # note: need to compile ffmpeg with --enable-libfdk_aac
208
+ cmd = f"ffmpeg -i \"{output_wav}\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 32k -y \"{output_filename}\""
209
+ elif file_format == "m4a aac_he_v2 64k":
210
+ cmd = f"ffmpeg -i \"{output_wav}\" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 64k -y \"{output_filename}\""
211
+ elif file_format == "flac":
212
+ cmd = f"ffmpeg -i \"{output_wav}\" -y \"{output_filename}\""
213
+ elif file_format == "mp3 320k":
214
+ cmd = f"ffmpeg -i \"{output_wav}\" -b:a 320k -y \"{output_filename}\""
215
+ elif file_format == "mp3 128k":
216
+ cmd = f"ffmpeg -i \"{output_wav}\" -b:a 128k -y \"{output_filename}\""
217
+ elif file_format == "mp3 v0":
218
+ cmd = f"ffmpeg -i \"{output_wav}\" -q:a 0 -y \"{output_filename}\""
219
+ else: # wav
220
+ pass
221
+ if cmd:
222
+ cmd += " -loglevel error" # make output less verbose in the cmd window
223
+ subprocess.run(cmd, shell=True, check=True)
224
+
225
+ # Let's look at a nice spectrogram too
226
+ audio_spectrogram = audio_spectrogram_image(audio, sample_rate=sample_rate)
227
+
228
+ # Asynchronously delete the files after returning the output file, so as to prevent clutter in the directory
229
+ delete_files_async([output_wav, output_filename], 30)
230
+
231
+ return (output_filename, [audio_spectrogram, *preview_images])
232
+
233
+ # Asynchronously delete the given list of filenames after delay seconds. Sets up thread that sleeps for delay then deletes.
234
+ def delete_files_async(filenames, delay):
235
+ def delete_files_after_delay(filenames, delay):
236
+ time.sleep(delay) # Wait for the specified delay
237
+ for filename in filenames:
238
+ if os.path.exists(filename):
239
+ os.remove(filename) # Delete the file
240
+ threading.Thread(target=delete_files_after_delay, args=(filenames, delay)).start()
241
+
242
+ def create_sampling_ui(stable_audio_3_model, default_prompt=None):
243
+ global n_loras
244
+ diffusion_objective = stable_audio_3_model.model.diffusion_objective
245
+ is_rf = diffusion_objective == "rectified_flow"
246
+ is_rf_denoiser = diffusion_objective == "rf_denoiser" # includes ARC models
247
+
248
+ # Extract default dist_shift params from model's sampling_dist_shift
249
+ default_sampling_dist_shift = getattr(stable_audio_3_model.model, 'sampling_dist_shift', None)
250
+ default_dist_shift_type = "LogSNR"
251
+ default_logsnr_params = {"anchor_length": 2000, "anchor_logsnr": -6.2, "rate": 0.0, "logsnr_end": 2.0}
252
+ default_flux_params = {"min_length": 256, "max_length": 4096, "alpha_min": 6.93, "alpha_max": 6.93}
253
+ default_full_params = {"base_shift": 0.5, "max_shift": 1.15, "min_length": 256, "max_length": 4096}
254
+
255
+ if isinstance(default_sampling_dist_shift, LogSNRShift):
256
+ default_dist_shift_type = "LogSNR"
257
+ default_logsnr_params = {
258
+ "anchor_length": getattr(default_sampling_dist_shift, 'anchor_length', 2000),
259
+ "anchor_logsnr": getattr(default_sampling_dist_shift, 'anchor_logsnr', -6.2),
260
+ "rate": getattr(default_sampling_dist_shift, 'rate', 0.0),
261
+ "logsnr_end": getattr(default_sampling_dist_shift, 'logsnr_end', 2.0),
262
+ }
263
+ elif isinstance(default_sampling_dist_shift, FluxDistributionShift):
264
+ default_dist_shift_type = "Flux"
265
+ default_flux_params = {
266
+ "min_length": default_sampling_dist_shift.min_length,
267
+ "max_length": default_sampling_dist_shift.max_length,
268
+ "alpha_min": default_sampling_dist_shift.alpha_min,
269
+ "alpha_max": default_sampling_dist_shift.alpha_max,
270
+ }
271
+ elif isinstance(default_sampling_dist_shift, DistributionShift):
272
+ default_dist_shift_type = "Full"
273
+ default_full_params = {
274
+ "base_shift": default_sampling_dist_shift.base_shift,
275
+ "max_shift": default_sampling_dist_shift.max_shift,
276
+ "min_length": default_sampling_dist_shift.min_length,
277
+ "max_length": default_sampling_dist_shift.max_length,
278
+ }
279
+ elif default_sampling_dist_shift is None:
280
+ default_dist_shift_type = "None"
281
+
282
+ has_seconds_total = True
283
+
284
+ use_lora = has_lora(stable_audio_3_model.model)
285
+ lora_names = getattr(stable_audio_3_model.model, 'lora_names', [])
286
+ n_loras = len(lora_names)
287
+
288
+ if default_prompt is None:
289
+ default_prompt = ""
290
+
291
+ _reprompt_model_id = "Qwen/Qwen3.5-2B"
292
+ _reprompt_cached = _reprompt_is_model_cached(_reprompt_model_id)
293
+
294
+ with gr.Row():
295
+ with gr.Column(scale=6):
296
+ prompt = gr.Textbox(show_label=False, placeholder="Prompt", value=default_prompt)
297
+ negative_prompt = gr.Textbox(show_label=False, placeholder="Negative prompt")
298
+ prompt_assistant_button = gr.Button(
299
+ "Prompt Assistant" if _reprompt_cached else "Download Prompt Assistant (~4.2 GB)",
300
+ scale=1
301
+ )
302
+ generate_button = gr.Button("Generate", variant='primary', scale=1)
303
+
304
+ with gr.Row(equal_height=False):
305
+ with gr.Column():
306
+ with gr.Row(visible = True):
307
+ # Timing controls
308
+ seconds_total_slider = gr.Slider(minimum=0, maximum=sample_size//sample_rate, step=1, value=sample_size//sample_rate, label="Seconds total", visible=has_seconds_total)
309
+
310
+ with gr.Row():
311
+ # Steps slider
312
+ if is_rf:
313
+ default_steps = 50
314
+ elif is_rf_denoiser:
315
+ default_steps = 8
316
+
317
+ steps_slider = gr.Slider(minimum=1, maximum=500, step=1, value=default_steps, label="Steps")
318
+ # CFG scale
319
+ default_cfg_scale = 1.0 if is_rf_denoiser else 7.0
320
+ cfg_scale_slider = gr.Slider(minimum=0.0, maximum=25.0, step=0.1, value=default_cfg_scale, label="CFG scale")
321
+
322
+ # Per-LoRA controls (dynamic based on number of loaded LoRAs)
323
+ lora_ui_inputs = []
324
+ if use_lora and lora_names:
325
+ for i, lora_name in enumerate(lora_names):
326
+ with gr.Accordion("LoRA {}: {}".format(i + 1, lora_name), open=(i == 0)):
327
+ with gr.Row():
328
+ strength = gr.Slider(minimum=0.0, maximum=10.0, step=0.1, value=1.0, label="strength")
329
+ with gr.Row():
330
+ int_min = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=0.0, label="Interval min")
331
+ int_max = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, value=1.0, label="Interval max")
332
+ lyr_filt = gr.Textbox(label="Layer filter", placeholder="")
333
+ lora_ui_inputs.extend([strength, int_min, int_max, lyr_filt])
334
+
335
+ with gr.Accordion("Sampler params", open=False):
336
+ with gr.Row():
337
+ # Seed
338
+ seed_textbox = gr.Number(label="Seed (set to -1 for random seed)", value=-1, precision=0)
339
+
340
+ cfg_interval_min_slider = gr.Slider(minimum=0.0, maximum=1, step=0.01, value=0.0, label="CFG interval min")
341
+ cfg_interval_max_slider = gr.Slider(minimum=0.0, maximum=1, step=0.01, value=1.0, label="CFG interval max")
342
+
343
+ with gr.Row():
344
+ cfg_rescale_slider = gr.Slider(minimum=0.0, maximum=1, step=0.01, value=0.0, label="CFG rescale amount")
345
+ cfg_norm_threshold = gr.Slider(minimum=0.0, maximum=100, step=0.1, value=0.0, label="CFG norm threshold")
346
+ apg_scale_slider = gr.Slider(minimum=0.0, maximum=1.0, step=0.1, value=1.0, label="APG scale", info="1.0=full APG, 0.0=vanilla CFG")
347
+
348
+ with gr.Row():
349
+ # Sampler params
350
+ if is_rf:
351
+ sampler_types = ["euler", "rk4", "dpmpp"]
352
+ default_sampler_type = "euler"
353
+ sigma_max_max = 1.0
354
+ sigma_max_default = 1.0
355
+ elif is_rf_denoiser:
356
+ sampler_types = ["pingpong"]
357
+ default_sampler_type = "pingpong"
358
+ sigma_max_max = 1.0
359
+ sigma_max_default = 1.0
360
+ else:
361
+ sampler_types = ["dpmpp-2m-sde", "dpmpp-3m-sde", "dpmpp-2m", "k-heun", "k-lms", "k-dpmpp-2s-ancestral", "k-dpm-2", "k-dpm-adaptive", "k-dpm-fast", "v-ddim", "v-ddim-cfgpp"]
362
+ default_sampler_type = "dpmpp-3m-sde"
363
+ sigma_max_max = 1000.0
364
+ sigma_max_default = 100.0
365
+
366
+ sampler_type_dropdown = gr.Dropdown(sampler_types, label="Sampler type", value=default_sampler_type)
367
+ sigma_max_slider = gr.Slider(minimum=0.0, maximum=sigma_max_max, step=0.1, value=sigma_max_default, label="Sigma max", visible=True)
368
+
369
+ with gr.Row():
370
+ duration_padding_slider = gr.Slider(minimum=0.0, maximum=30.0, step=0.5, value=6.0, label="Duration padding (sec)")
371
+
372
+ def build_dist_shift(shift_type, p1, p2, p3, p4):
373
+ """Build dist_shift from type + 4 params (meaning depends on type)."""
374
+ if shift_type == "LogSNR":
375
+ return LogSNRShift(anchor_length=int(p1), anchor_logsnr=p2, rate=p3, logsnr_end=p4)
376
+ elif shift_type == "Flux":
377
+ return FluxDistributionShift(min_length=int(p1), max_length=int(p2), alpha_min=p3, alpha_max=p4)
378
+ elif shift_type == "Full":
379
+ return DistributionShift(base_shift=p1, max_shift=p2, min_length=int(p3), max_length=int(p4))
380
+ return IdentityDistributionShift() # "None" = no shift
381
+
382
+ dist_shift_state = gr.State(value=default_sampling_dist_shift)
383
+
384
+ with gr.Row(visible=is_rf or is_rf_denoiser):
385
+ dist_shift_type_dropdown = gr.Dropdown(
386
+ ["LogSNR", "Flux", "Full", "None"],
387
+ label="Sampling schedule shift",
388
+ value=default_dist_shift_type,
389
+ info="Distribution shift applied to sampling timesteps"
390
+ )
391
+ with gr.Row(visible=(is_rf or is_rf_denoiser) and default_dist_shift_type == "LogSNR") as logsnr_params_row:
392
+ logsnr_anchor_length_slider = gr.Slider(minimum=100, maximum=10000, step=100, value=default_logsnr_params["anchor_length"], label="Anchor length")
393
+ logsnr_anchor_logsnr_slider = gr.Slider(minimum=-12.0, maximum=0.0, step=0.1, value=default_logsnr_params["anchor_logsnr"], label="Anchor log-SNR")
394
+ logsnr_rate_slider = gr.Slider(minimum=-2.0, maximum=2.0, step=0.1, value=default_logsnr_params["rate"], label="Rate")
395
+ logsnr_end_slider = gr.Slider(minimum=-2.0, maximum=6.0, step=0.1, value=default_logsnr_params["logsnr_end"], label="log-SNR end")
396
+ with gr.Row(visible=(is_rf or is_rf_denoiser) and default_dist_shift_type == "Flux") as flux_params_row:
397
+ flux_min_length_slider = gr.Slider(minimum=1, maximum=10000, step=1, value=default_flux_params["min_length"], label="Min seq len")
398
+ flux_max_length_slider = gr.Slider(minimum=1, maximum=10000, step=1, value=default_flux_params["max_length"], label="Max seq len")
399
+ flux_alpha_min_slider = gr.Slider(minimum=0.1, maximum=20.0, step=0.1, value=default_flux_params["alpha_min"], label="Alpha min")
400
+ flux_alpha_max_slider = gr.Slider(minimum=0.1, maximum=20.0, step=0.1, value=default_flux_params["alpha_max"], label="Alpha max")
401
+ with gr.Row(visible=(is_rf or is_rf_denoiser) and default_dist_shift_type == "Full") as full_params_row:
402
+ full_base_shift_slider = gr.Slider(minimum=0.0, maximum=5.0, step=0.05, value=default_full_params["base_shift"], label="Base shift")
403
+ full_max_shift_slider = gr.Slider(minimum=0.0, maximum=5.0, step=0.05, value=default_full_params["max_shift"], label="Max shift")
404
+ full_min_length_slider = gr.Slider(minimum=1, maximum=10000, step=1, value=default_full_params["min_length"], label="Min length")
405
+ full_max_length_slider = gr.Slider(minimum=1, maximum=10000, step=1, value=default_full_params["max_length"], label="Max length")
406
+
407
+ # Per-type slider groups for wiring to state
408
+ logsnr_sliders = [logsnr_anchor_length_slider, logsnr_anchor_logsnr_slider, logsnr_rate_slider, logsnr_end_slider]
409
+ flux_sliders = [flux_min_length_slider, flux_max_length_slider, flux_alpha_min_slider, flux_alpha_max_slider]
410
+ full_sliders = [full_base_shift_slider, full_max_shift_slider, full_min_length_slider, full_max_length_slider]
411
+ all_dist_shift_inputs = [dist_shift_type_dropdown] + logsnr_sliders + flux_sliders + full_sliders
412
+
413
+ def update_dist_shift_state(shift_type, *params):
414
+ """Route the 4 relevant params to build_dist_shift based on type."""
415
+ type_to_slice = {"LogSNR": params[0:4], "Flux": params[4:8], "Full": params[8:12]}
416
+ p = type_to_slice.get(shift_type, (0, 0, 0, 0))
417
+ return (
418
+ build_dist_shift(shift_type, *p),
419
+ gr.update(visible=((is_rf or is_rf_denoiser) and (shift_type == "LogSNR"))),
420
+ gr.update(visible=((is_rf or is_rf_denoiser) and (shift_type == "Flux"))),
421
+ gr.update(visible=((is_rf or is_rf_denoiser) and (shift_type == "Full"))),
422
+ )
423
+
424
+ for component in all_dist_shift_inputs:
425
+ component.change(
426
+ update_dist_shift_state,
427
+ inputs=all_dist_shift_inputs,
428
+ outputs=[dist_shift_state, logsnr_params_row, flux_params_row, full_params_row],
429
+ )
430
+
431
+ # Hidden state for batch_size (no UI control, but needed for function signature)
432
+ batch_size_state = gr.State(value=1)
433
+
434
+ with gr.Accordion("Output params", open=False):
435
+ # Output params
436
+ with gr.Row():
437
+ file_format_dropdown = gr.Dropdown(["wav", "flac", "mp3 320k", "mp3 v0", "mp3 128k", "m4a aac_he_v2 64k", "m4a aac_he_v2 32k"], label="File format", value="wav")
438
+ file_naming_dropdown = gr.Dropdown(["verbose", "prompt", "output.wav"], label="File naming", value="verbose") # ,"prompt","verbose"
439
+ preview_every_slider = gr.Slider(minimum=0, maximum=100, step=1, value=0, label="Spec Preview Every")
440
+
441
+ cut_to_seconds_total_checkbox = gr.Checkbox(label="Cut to seconds total", value=True)
442
+ autoplay_checkbox = gr.Checkbox(label="Autoplay", value=False, elem_id="autoplay")
443
+ infinite_radio_checkbox = gr.Checkbox(label="Infinite Radio", value=False, elem_id="infinite-radio")
444
+ automatic_download_checkbox = gr.Checkbox(label="Auto Download", value=False, elem_id="automatic-download")
445
+
446
+ # Default generation tab
447
+ with gr.Accordion("Init audio", open=False):
448
+ init_audio_input = gr.Audio(label="Init audio", waveform_options=gr.WaveformOptions(show_recording_waveform=False))
449
+ min_noise_level = 0.01
450
+ max_noise_level = 1.0
451
+ default_noise_level = 0.9 # roughly halfway style transfer values
452
+ if is_rf:
453
+ choices = ["Init audio","RF-Inversion"]
454
+ else:
455
+ choices = ["Init audio"]
456
+
457
+ init_audio_type_radio = gr.Radio(label="Techniques", choices=choices, value=choices[0], visible=len(choices)>1)
458
+ with gr.Column(visible=True) as interface_a:
459
+ init_noise_level_slider = gr.Slider(minimum=min_noise_level, maximum=max_noise_level, step=0.01, value=default_noise_level, label="Init noise level")
460
+ with gr.Column(visible=False) as interface_b:
461
+ inversion_steps_slider = gr.Slider(minimum=1, maximum=500, step=1, value=100, label="Inversion Steps")
462
+ inversion_gamma_slider = gr.Slider(minimum=0, maximum=1, step=0.1, value=0, label="Gamma", visible=True)
463
+ inversion_unconditional_checkbox = gr.Checkbox(label="Unconditional", value=False)
464
+ gr.HTML("<div style='opacity: 0.5; padding: 0px'>For reproduction, try empty prompt, cfg 1, gamma .3<br>\
465
+ For prompt re-stylization, try cfg 1-7, gamma 0-.15, unconditional</div>")
466
+ def init_audio_type_switch(choice):
467
+ return (
468
+ gr.update(visible=(choice == "Init audio")),
469
+ gr.update(visible=(choice == "RF-Inversion"))
470
+ )
471
+ init_audio_type_radio.change(init_audio_type_switch, inputs=init_audio_type_radio, outputs=[interface_a, interface_b])
472
+
473
+ with gr.Accordion("Inpainting", open=False):
474
+ inpaint_audio_input = gr.Audio(label="Inpaint audio", waveform_options=gr.WaveformOptions(show_recording_waveform=False))
475
+ mask_maskstart_slider = gr.Slider(minimum=0.0, maximum=sample_size//sample_rate, step=0.1, value=0, label="Mask Start (sec)")
476
+ mask_maskend_slider = gr.Slider(minimum=0.0, maximum=sample_size//sample_rate, step=0.1, value=sample_size//sample_rate, label="Mask End (sec)")
477
+
478
+ # Update inpainting slider ranges when seconds_total changes.
479
+ # Only seconds_total is an input — reading the mask sliders here would cause
480
+ # validation errors since their values may exceed the about-to-be-reduced maximum.
481
+ def update_inpaint_sliders(seconds_total):
482
+ max_val = max(seconds_total, 1)
483
+ return (
484
+ gr.update(maximum=max_val),
485
+ gr.update(maximum=max_val, value=max_val),
486
+ )
487
+ seconds_total_slider.change(update_inpaint_sliders, inputs=[seconds_total_slider], outputs=[mask_maskstart_slider, mask_maskend_slider])
488
+
489
+ inputs = [
490
+ prompt,
491
+ negative_prompt,
492
+ seconds_total_slider,
493
+ cfg_scale_slider,
494
+ steps_slider,
495
+ preview_every_slider,
496
+ seed_textbox,
497
+ sampler_type_dropdown,
498
+ sigma_max_slider,
499
+ cfg_interval_min_slider,
500
+ cfg_interval_max_slider,
501
+ cfg_rescale_slider,
502
+ cfg_norm_threshold,
503
+ apg_scale_slider,
504
+ file_format_dropdown,
505
+ file_naming_dropdown,
506
+ cut_to_seconds_total_checkbox,
507
+ init_audio_input,
508
+ init_noise_level_slider,
509
+ mask_maskstart_slider,
510
+ mask_maskend_slider,
511
+ inpaint_audio_input,
512
+ init_audio_type_radio,
513
+ inversion_steps_slider,
514
+ inversion_gamma_slider,
515
+ inversion_unconditional_checkbox,
516
+ duration_padding_slider,
517
+ batch_size_state,
518
+ dist_shift_state,
519
+ ] + lora_ui_inputs
520
+
521
+ with gr.Column():
522
+ audio_output = gr.Audio(label="Output audio", interactive=False,
523
+ waveform_options=gr.WaveformOptions(show_recording_waveform=False))
524
+ audio_spectrogram_output = gr.Gallery(label="Output spectrogram", show_label=False)
525
+ send_to_init_button = gr.Button("Send to init audio", scale=1)
526
+ send_to_init_button.click(fn=lambda audio: audio, inputs=[audio_output], outputs=[init_audio_input])
527
+
528
+ send_to_inpaint_button = gr.Button("Send to inpaint audio", scale=1)
529
+ send_to_inpaint_button.click(fn=lambda audio: audio, inputs=[audio_output], outputs=[inpaint_audio_input])
530
+
531
+ generate_button.click(fn=generate_cond,
532
+ inputs=inputs,
533
+ outputs=[
534
+ audio_output,
535
+ audio_spectrogram_output
536
+ ],
537
+ api_name="generate")
538
+
539
+ def _prompt_assistant_or_download(text, progress=gr.Progress(track_tqdm=True)):
540
+ if not _reprompt_is_model_cached(_reprompt_model_id):
541
+ _reprompt_get_model(_reprompt_model_id)
542
+ return text, gr.update(), gr.update(value="Prompt Assistant")
543
+ _, result, _ = _reprompt_fn(text, "Auto", "", _reprompt_model_id, 128, 1.11)
544
+ m = _LENGTH_EXTRACT_RE.search(result)
545
+ if m:
546
+ max_seconds = sample_size // sample_rate
547
+ seconds = min(int(m.group(1)), max_seconds)
548
+ result = result[:m.start()]
549
+ else:
550
+ seconds = gr.update()
551
+ return result, seconds, gr.update()
552
+
553
+ prompt_assistant_button.click(
554
+ fn=_prompt_assistant_or_download,
555
+ inputs=[prompt],
556
+ outputs=[prompt, seconds_total_slider, prompt_assistant_button],
557
+ concurrency_limit=1,
558
+ )
559
+
560
+ def create_diffusion_cond_ui(model, gradio_title="", default_prompt=None):
561
+ global sample_size, sample_rate, stable_audio_3_model
562
+
563
+ sample_size = model.model_config["sample_size"]
564
+ sample_rate = model.model_config["sample_rate"]
565
+ stable_audio_3_model = model
566
+
567
+
568
+ js ="""function run_javascript_on_page_load(){
569
+ const generateBtn = Array.from(document.querySelectorAll('button'))
570
+ .find(btn => btn.innerText.trim() === 'Generate');
571
+ function getAudioOutputPlayer () {
572
+ return [...document.querySelectorAll('label')].find(label => label.textContent.trim() === 'Output audio')?.parentElement.querySelector('audio');
573
+ }
574
+ const infiniteRadio = document.querySelector('#infinite-radio input[type="checkbox"]');
575
+ const autoplay = document.querySelector('#autoplay input[type="checkbox"]');
576
+ const automaticDownload = document.querySelector('#automatic-download input[type="checkbox"]');
577
+ let radioAutoStart = false;
578
+ let listenersSetup = false;
579
+ const setupListeners = () => {
580
+ const audioEl = getAudioOutputPlayer();
581
+ if (!audioEl) return;
582
+ audioEl.addEventListener('loadedmetadata', () => {
583
+ if(automaticDownload.checked){
584
+ downloadAudio(audioEl);
585
+ }
586
+ if(autoplay.checked || radioAutoStart){
587
+ audioEl.play();
588
+ radioAutoStart = false;
589
+ }
590
+ if(infiniteRadio.checked){
591
+ audioEl.addEventListener('timeupdate', function checkAudioEnd() {
592
+ // Can set window.headstart (seconds) in the dev console if you want to start generating before the song is over
593
+ let headstart = 1;
594
+ if(window.headstart) headstart = window.headstart;
595
+ if (audioEl.duration - audioEl.currentTime <= headstart) {
596
+ generateBtn.click();
597
+ radioAutoStart = true;
598
+ audioEl.removeEventListener('timeupdate', checkAudioEnd);
599
+ }
600
+ });
601
+ }
602
+ });
603
+ listenersSetup = true;
604
+ };
605
+ generateBtn.addEventListener('click', () => {
606
+ if(listenersSetup) return;
607
+ const interval = setInterval(() => {
608
+ console.log("...")
609
+ const audioEl = document.querySelector('audio');
610
+ if (audioEl?.src && audioEl.src !== window.location.href) {
611
+ setupListeners();
612
+ clearInterval(interval);
613
+ }
614
+ }, 100);
615
+ });
616
+ // Respond to >> button on MacBookPro and on steering wheel during CarPlay
617
+ if ('mediaSession' in navigator) {
618
+ navigator.mediaSession.setActionHandler('nexttrack', () => generateBtn.click());
619
+ navigator.mediaSession.setActionHandler('play', () => getAudioOutputPlayer()?.play());
620
+ navigator.mediaSession.setActionHandler('pause', () => getAudioOutputPlayer()?.pause());
621
+ }
622
+ // Automatic Download
623
+ function downloadAudio(audioEl) {
624
+ const audioSrc = audioEl.src;
625
+ const link = document.createElement('a');
626
+ link.href = audioSrc;
627
+ link.download = audioSrc.substring(audioSrc.lastIndexOf('/') + 1);
628
+ document.body.appendChild(link);
629
+ link.click();
630
+ document.body.removeChild(link);
631
+ }
632
+ }
633
+ """
634
+
635
+ with gr.Blocks() as ui:
636
+ ui._sao_js = js
637
+ ui._sao_theme = gr.themes.Base()
638
+ if gradio_title:
639
+ gr.Markdown("### %s" % gradio_title)
640
+ with gr.Tab("Generation"):
641
+ create_sampling_ui(model, default_prompt=default_prompt)
642
+
643
+ # JavaScript to autoplay audio immediately after generation (if autoplay enabled)
644
+ return ui
stable_audio_3/interface/reprompt.py ADDED
@@ -0,0 +1,481 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import torch
3
+ from transformers import AutoTokenizer, AutoModelForCausalLM
4
+ from huggingface_hub import scan_cache_dir
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # System prompts
8
+ # ---------------------------------------------------------------------------
9
+
10
+ SYSTEM_PROMPTS = {
11
+ "Classifier": """You are a classification assistant.
12
+
13
+ Your task is to classify the given input into ONE of the following categories:
14
+
15
+ music_genre
16
+
17
+ instrument
18
+
19
+ sound
20
+
21
+ one_shot
22
+
23
+ Definitions:
24
+
25
+ music_genre: A style or category of music.
26
+
27
+ instrument: A musical instrument used to produce sound, or with words solo or stem.
28
+
29
+ sound: A non-instrumental or general sound.
30
+
31
+ one_shot: A single, isolated audio sample of an instrument or sound, usually short and intended for use in music production.
32
+
33
+ Rules:
34
+
35
+ Output ONLY one label: music_genre, instrument, sound, or one_shot.
36
+
37
+ Do not explain your answer.
38
+
39
+ If unsure, choose the closest category.
40
+
41
+ EXAMPLES
42
+
43
+ Music Genres:
44
+ rock, jazz, pop, blues, reggae, techno, hip hop, classical, country, metal, folk, trance, house, dubstep, funk, soul, disco, punk, ambient, indie, grunge, gospel, bluegrass, R&B, salsa, samba, bossa nova, dance, electro, industrial, emo, ska, swing, swing jazz, hard rock, soft rock, progressive rock, psychedelic rock, acid jazz, latin, afrobeat, trap, chillhop, lo-fi, synthwave, vaporwave, EDM, drum and bass, hardcore, dub, experimental, alternative, post punk, new wave, indie pop, indie rock, glam rock, hair metal, death metal, black metal, metalcore, screamo, post rock, ambient techno, house deep, house progressive, minimal, chillout, lounge, trip hop, breakbeat, moombahton, cumbia, merengue, polka, zydeco, bhangra, k-pop, J-pop, Chinese pop, folk rock, psychedelic, surf rock, emo pop, synth pop, power pop, baroque pop, future bass, hardstyle, happy hardcore, gabber, jungle, UK garage, tropical house, moody trap, cinematic, soundtrack, orchestral
45
+
46
+ Instruments:
47
+ guitar, piano, violin, drums, bass, saxophone, flute, trumpet, cello, harp, trombone, clarinet, organ, synth, synthesizer, banjo, ukulele, sitar, marimba, accordion, tuba, oboe, bassoon, vibraphone, conga, bongo, djembe, cajon, xylophone, harmonica, dobro, mandolin, lute, koto, shamisen, erhu, bagpipes, shakuhachi, recorder, piccolo, euphonium, glockenspiel, steel drum, tabla, timpani, castanets, tambourine, triangle, dulcimer, zither, hurdy gurdy, melodica, pan flute, ocarina, didgeridoo, jaw harp, kora, santur, balalaika, sousaphone, lyre, clavinet, celesta, fife, cornet, contrabass, electric guitar, acoustic guitar, bass guitar, upright bass, electric piano, synth pad, modular synth, maracas, guiro, washboard, kalimba, udu, bodhran, tonbak, darbuka, tabla tarang, bongos, congas, cajon box, log drum, talking drum, steelpan, rainstick, dhol, ghatam, tambura, tambour, bamboo flute, reed organ, hurdy-gurdy, cimbalom, santur, psaltery, kantele, nyckelharpa, viola
48
+
49
+ Sounds:
50
+ rain, thunder, wind, footsteps, glass, explosion, ocean, beep, alarm, door, crowd, birds, fire, traffic, siren, clap, whistle, rustle, engine, bell, clock, heartbeat, dripping, typing, gunshot, gun reload, cannon, splash, pouring, chewing, sneezing, coughing, whisper, scream, giggle, laugh, growl, roar, howl, bark, meow, chirp, moo, neigh, oink, quack, frog, cricket, buzzing, hissing, airplane, helicopter, train, subway, car horn, tires, skateboard, skateboard wheels, zipper, button, rustling leaves, branches, river, waterfall, waves, snow crunch, wind chime, church bell, church organ, rainstick, wind gust, echo, footsteps gravel, footsteps wood, footsteps carpet, footsteps stairs, punch, slap, slapstick, slapstick sound, ricochet, splash water, dripping water, boiling, boiling kettle, frying, sizzling, microwave, fridge, fan, air conditioner, vacuum, typing keyboard, mouse click, door slam, gate, chain, rope, paper tearing, book closing, page flip, sewing machine, hammer, saw, drill, car engine, truck horn, airplane engine
51
+
52
+ One-Shots:
53
+ kick, snare, hi-hat, tom, crash, clap, 808, piano stab, guitar pluck, synth stab, triangle, noise burst, cymbal hit, bass hit, rimshot, sound fx, percussion hit, kick drum, snare drum, open hi-hat, closed hi-hat, ride cymbal, splash cymbal, reverse cymbal, tom hit, rim click, shaker, tambourine hit, hand clap, finger snap, cowbell, bell hit, bell toll, bell ring, church bell hit, gong hit, woodblock, claves, conga hit, bongo hit, djembe hit, tabla hit, rim tap, crash cymbal hit, metallic hit, metallic clang, glass hit, glass break, scratch vinyl, scratch record, vinyl scratch, tape stop, pitch down, pitch up, reverse hit, laser zap, laser shot, explosion one-shot, gunshot one-shot, shotgun, pistol, ricochet, swoosh, whoosh, wind gust one-shot, thunder clap, rain drop, water drip, water splash, water hit, ocean hit, fire hit, fire crackle, lava hit, lava splash, air hit, balloon pop, pop, click, snap, digital blip, 8-bit sound, synth hit, synth burst, lead stab, pad hit, chord stab, organ hit, piano chord hit, piano note hit, guitar chord hit, guitar note hit, bass stab, bass note hit, sub bass hit""",
54
+
55
+ "One-shot": """You are a music metadata expert. Given an instrument or sound, generate a descriptive prompt for a short, isolated one-shot audio sample for music production.
56
+
57
+ 1. Identify the instrument or sound source.
58
+ 2. Describe the playing technique or hit type (e.g., pluck, slam, tap, stab).
59
+ 3. Include details about material, timbre, or texture.
60
+ 4. Add spatial or production qualities (dry/wet, room, close-mic).
61
+ 5. Specify length: short integer in seconds (1–11 s).
62
+
63
+ Examples:
64
+ - Piano key hit with bright percussive attack and resonant wooden body. Length: 2 seconds
65
+ - Kick drum punchy low-end hit with warm skin resonance. Length: 2 seconds
66
+ - Snare drum rimshot accent with crisp snare wires. Length: 2 seconds
67
+ - Acoustic guitar fingerstyle note with warm spruce tone. Length: 3 seconds
68
+ - Bass pluck with jazzy tone and resonant wooden body. Length: 3 seconds
69
+ - Electric guitar power chord with distortion. Length: 3 seconds
70
+ - Metallic glitch percussion hit with sharp metallic texture. Length: 2 seconds
71
+ - Tabla resonant tone hit with natural skin timbre. Length: 2 seconds
72
+ - Djembe slap accent with dry wooden resonance. Length: 2 seconds
73
+ - Synth stab with reverb tail. Length: 3 seconds
74
+ - Violin expressive note with vibrato and rich wooden resonance. Length: 3 seconds
75
+ - Cello legato note, cinematic, with warm resonant body. Length: 3 seconds
76
+ - Trumpet bright accent with slightly brassy overtones. Length: 2 seconds
77
+ - Melodic saxophone jazz riff with smooth reed timbre and a slight vibrato bend. Length: 3 seconds
78
+ - Harp pluck with airy tone and resonant strings. Length: 2 seconds
79
+ - Glockenspiel bell-like note with bright metallic clarity. Length: 2 seconds
80
+ - Metallic clang sound design hit. Length: 2 seconds
81
+ - Granular texture hit. Length: 3 seconds
82
+ - Reversed piano hit. Length: 2 seconds
83
+ - Synth riser effect. Length: 6 seconds
84
+ - Percussion impact hit. Length: 2 seconds
85
+ - Cinematic hit. Length: 2 seconds
86
+ - Dry clap, a crisp, natural single hand clap recorded in a dead room with an extremely sharp transient and no room reflections. Length: 1 second
87
+ - Studio hat, a classic, natural recording of 14-inch hi-hats played tightly closed, zero ring, very fast decay. Length: 1 second
88
+ - Disco open hat, bright 14-inch open hi-hat with long, shimmering decay, perfect for disco or dance grooves. Length: 1 second
89
+ - Pillow kick, acoustic kick drum muffled with a heavy blanket, producing a short, dry "thump" with almost zero resonance. Length: 1 second
90
+ - Short 808, punchy 808 kick with sharp, distorted transient and fast-decaying sub-tail. Length: 1 second
91
+ - Egg shaker, classic plastic egg shaker recorded with a small-diaphragm condenser mic, producing a light, consistent "tick" with very short sustain. Length: 1 second
92
+ - African drums, dynamic African drums and percussion ensemble with natural acoustic textures. Length: 3 seconds
93
+ - Latin drums, dynamic Latin drums and percussion ensemble featuring authentic rhythmic patterns. Length: 3 seconds
94
+ - String quartet, euphoric string quartet with dynamic and emotional playing, full of expressive harmonies and movement. Length: 3 seconds
95
+ - Piano, nostalgic, atmospheric piano piece with dynamic and emotional performance, intimate and resonant. Length: 3 seconds
96
+ - Analogue drift pad, warm polyphonic pad with three detuned oscillators (saw + triangle), subtle pitch drift, and lush bucket-brigade chorus for wide, nostalgic stereo image. Length: 11 seconds
97
+ - Phase distortion bass, Casio CZ-style phase-distorted sine wave warped into a jagged sawtooth for retro synth bass tone. Length: 11 seconds
98
+ - Vibrato saxophone, bright lyrical alto sax with fast fluttery vibrato, reedy vintage tone, captured with ribbon mic for warm nostalgic sound. Length: 11 seconds
99
+ - Lofi upright bass, upright bass recorded with ribbon mic in a wooden room, natural air with slightly boxy resonance, tape-saturated for dusty 1950s jazz feel. Length: 2 seconds""",
100
+
101
+ "Instrument": """You are a music metadata expert. Given an instrument, generate a descriptive prompt for a generative audio model.
102
+
103
+ 1. Identify the instrument.
104
+ 2. Add playing style or technique.
105
+ 3. Include details about material, timbre, or texture.
106
+ 4. Add musical style or mood. Specify the genre, context, or emotional character.
107
+ 5. Add spatial or production qualities.
108
+ 6. Specify BPM: Always include a BPM appropriate to the style and context.
109
+ 7. Specify length: Provide an integer in seconds (6–20 s for loops, 20–180 s for stems).
110
+
111
+ Examples:
112
+ - Synth arpeggio loop with bright detuned oscillators. BPM: 120. Length: 8 seconds
113
+ - Chord stab loop with sharp percussive attack. BPM: 90. Length: 6 seconds
114
+ - Guitar muted strum loop with tight rhythmic feel. BPM: 100. Length: 8 seconds
115
+ - Pluck sequence loop with bright resonant tone. BPM: 128. Length: 10 seconds
116
+ - Marimba and vibraphone percussive loop with resonant wooden and metallic tones. BPM: 110. Length: 12 seconds
117
+ - Drum loop with deep muffled kick on beat one, snappy rimshot snare on beats two and four with rolling ghost note fills, and tight closed hi-hats with subtle open accents. BPM: 85. Length: 10 seconds
118
+ - Drum groove loop with brushed snare swinging on the ride, soft feathered kick on downbeats, and light closed hi-hat taps on the upbeats. BPM: 130. Length: 12 seconds
119
+ - Kick and hi-hat loop with four-on-the-floor punchy kick, tight closed hi-hats on every eighth note, and a sharp dry snare on beats two and four. BPM: 130. Length: 15 seconds
120
+ - Vinyl crackle drum loop with warm low-pass filtered kick, dusty snare with tape saturation, and shuffled closed hi-hats with subtle vinyl crackle ambiance. BPM: 80. Length: 10 seconds
121
+ - Ambient pad loop with evolving texture. BPM: 80. Length: 12 seconds
122
+ - Melodic synth bass groove loop with pumping sidechain feel. BPM: 122. Length: 10 seconds
123
+ - Melodic Bass slap and pop rhythm loop. BPM: 100. Length: 8 seconds
124
+ - Acoustic bass walking line loop with natural wooden resonance. BPM: 120. Length: 12 seconds
125
+ - String pizzicato motif loop, suspenseful, with tight string texture. BPM: 90. Length: 8 seconds
126
+ - Brass staccato riff loop with sharp bright attack. BPM: 130. Length: 10 seconds
127
+ - Flute airy melodic loop with wooden headjoint resonance. BPM: 100. Length: 6 seconds
128
+ - Pan flute ambient loop with breathy timbre. BPM: 75. Length: 8 seconds
129
+ - Clarinet riff loop with warm smooth reed tone. BPM: 120. Length: 10 seconds
130
+ - Oboe motif loop, orchestral, with rich double reed resonance. BPM: 80. Length: 8 seconds
131
+ - Recorder Renaissance motif loop with soft wooden timbre. BPM: 100. Length: 6 seconds
132
+ - Electric sitar riff loop with buzzing resonant tone. BPM: 90. Length: 10 seconds
133
+ - Koto plucked motif loop with resonant wooden strings. BPM: 90. Length: 8 seconds
134
+ - Shamisen folk melody loop with percussive twang. BPM: 100. Length: 8 seconds
135
+ - Banjo fingerpicking loop with metallic string resonance. BPM: 110. Length: 10 seconds
136
+ - Mandolin tremolo loop with crisp wooden body tone. BPM: 120. Length: 10 seconds
137
+ - Acoustic guitar chord vamp loop with natural room resonance. BPM: 110. Length: 12 seconds
138
+ - Nylon string guitar arpeggio loop with warm, soft timbre. BPM: 90. Length: 15 seconds
139
+ - Electric guitar riff loop with driven distorted tone. BPM: 130. Length: 10 seconds
140
+ - Slide guitar melody loop with warm resonant glide. BPM: 100. Length: 12 seconds
141
+ - Steel guitar slide loop with bright pedal steel tone. BPM: 95. Length: 12 seconds
142
+ - Harpsichord arpeggio loop with crisp plucked attack. BPM: 120. Length: 10 seconds
143
+ - Rhodes chord vamp loop with warm electric piano tone. BPM: 100. Length: 12 seconds
144
+ - Clavinet funky rhythm loop. BPM: 105. Length: 10 seconds
145
+ - Organ chord vamp loop with full drawbar warmth. BPM: 90. Length: 12 seconds
146
+ - Drum loop with booming 808 kick on beat one, crisp snare on beat three, and rapid triplet hi-hat rolls with open hat accents for aggressive high-energy feel. BPM: 140. Length: 8 seconds
147
+ - Breakbeat drum loop with chopped Amen-style snare flurries, driving kick on the one, fast sixteenth-note closed hi-hats, and syncopated open hat accents. BPM: 170. Length: 10 seconds
148
+ - Glitch percussion loop with stuttered kick transients, randomised snare hits processed with bit-crushing, and erratic hi-hat patterns with pitch-shifted metallic ticks. BPM: 120. Length: 12 seconds
149
+ - Metallic hits loop with distorted kick impacts, processed metal-plate snare slams, and grinding hi-hat noise bursts for aggressive mechanical texture. BPM: 120. Length: 10 seconds
150
+ - Timpani hits loop, cinematic, with deep resonant kick-like timpani strikes on beat one, rolling snare-style timpani fills, and no hi-hats for a grand orchestral feel. BPM: 70. Length: 8 seconds
151
+ - Snare roll loop, dramatic, with accelerating snare drum rolls building from soft to crashing, deep supporting kick pulses, and no hi-hats for maximum impact. BPM: 100. Length: 8 seconds
152
+ - Accordion motif loop with bright reedy bellows tone. BPM: 100. Length: 10 seconds
153
+ - Harmonica blues riff loop with expressive reed timbre. BPM: 90. Length: 10 seconds
154
+ - Trombone riff loop with warm sliding brass tone. BPM: 120. Length: 10 seconds
155
+ - French horn melodic loop, cinematic. BPM: 80. Length: 12 seconds
156
+ - Soprano sax ballad loop. BPM: 70. Length: 12 seconds
157
+ - Alto sax bebop riff loop. BPM: 200. Length: 10 seconds
158
+ - Electric violin melodic loop with reverb. BPM: 90. Length: 10 seconds
159
+ - String pad loop with cinematic texture. BPM: 70. Length: 15 seconds
160
+ - Granular synth evolving texture loop. BPM: 90. Length: 15 seconds
161
+ - Piano motif loop with soft felt hammer tone. BPM: 80. Length: 10 seconds
162
+ - Pad and synth loop with lush detuned shimmer. BPM: 85. Length: 12 seconds
163
+ - Synth lead loop with sidechain pumping compression. BPM: 128. Length: 10 seconds
164
+ - Analog synth bassline loop with deep warm low-end. BPM: 122. Length: 12 seconds
165
+ - FM synth lead motif loop with bright metallic shimmer. BPM: 110. Length: 10 seconds
166
+ - Bass groove loop with tight rhythmic two-bar pattern. BPM: 100. Length: 16 seconds
167
+ - Acoustic guitar fingerstyle motif loop with warm wood resonance. BPM: 90. Length: 45 seconds
168
+ - Sombre acoustic guitar motif loop with cavernous reverb, delicate fingerpicking, and expressive melancholic tone. BPM: 70. Length: 45 seconds
169
+ - Electric guitar rock riff motif loop. BPM: 130. Length: 40 seconds
170
+ - Vintage electric guitar motif loop, live-recorded in a vintage studio, with expressive and dynamic solo performance. BPM: 90. Length: 40 seconds
171
+ - Piano chord progression motif loop with rich harmonic movement. BPM: 120. Length: 60 seconds
172
+ - String ensemble cinematic motif loop with rich wooden resonance. BPM: 80. Length: 120 seconds
173
+ - Brass ensemble cinematic motif loop with bright metallic timbre. BPM: 90. Length: 90 seconds
174
+ - Ethnic percussion ensemble motif loop with deep resonant djembe kick tones, slapped snare-like rim hits on congas, and layered shakers and bells providing hi-hat-like rhythmic texture with polyrhythmic patterns. BPM: 100. Length: 90 seconds
175
+ - Synth ambient motif loop with evolving textures. BPM: 80. Length: 180 seconds
176
+ - Motif loop with warm dusty vinyl crackle and tape saturation. BPM: 80. Length: 60 seconds
177
+ - Synth lead and bass motif loop with bright punchy energy. BPM: 128. Length: 90 seconds
178
+ - Funk band motif loop: bass, drums, guitar. BPM: 100. Length: 90 seconds
179
+ - Ethnic flute motif for cinematic use. BPM: 80. Length: 30 seconds
180
+ - Steel drum melodic motif loop with bright metallic resonance. BPM: 110. Length: 20 seconds
181
+ - Marimba percussive motif loop with resonant wooden tone. BPM: 100. Length: 20 seconds
182
+ - Vibraphone melodic motif loop with metallic shimmer. BPM: 90. Length: 25 seconds
183
+ - Piano cinematic motif loop with resonant wooden tone. BPM: 80. Length: 30 seconds
184
+ - Violin expressive cinematic motif loop with rich wooden resonance. BPM: 75. Length: 25 seconds
185
+ - Cello expressive motif loop with deep wooden resonance. BPM: 70. Length: 30 seconds
186
+ - Trumpet expressive motif loop with brassy overtones. BPM: 100. Length: 25 seconds
187
+ - Sax expressive motif loop with warm reed timbre. BPM: 95. Length: 25 seconds
188
+ - Ethnic drum ensemble motif loop with booming natural-skin bass drum kicks, sharp hand-slap snare accents on djembes and talking drums, and layered wooden and metal percussion providing rhythmic hi-hat-like patterns. BPM: 95. Length: 30 seconds
189
+ - Ambient drone motif loop. BPM: 60. Length: 180 seconds
190
+ - Orchestral tension motif loop. BPM: 90. Length: 150 seconds
191
+ - Electronic track motif loop with drums, bass, synth. BPM: 128. Length: 180 seconds""",
192
+
193
+ "Music": """You are an expert musician and musicologist and prompt engineer. Transform the user's input into a detailed, vivid music prompt for a full instrumental track.
194
+
195
+ 1. Start with the genre or style and optional adjectives (e.g., upbeat, dreamy, aggressive).
196
+ 2. List the main instruments that define the track.
197
+ 3. Add supporting elements or layers such as pads, harmonics, effects, or field recordings.
198
+ 4. Include rhythm or percussion elements like drums, hi-hats, congas, brushes, or polyrhythms.
199
+ 5. Integrate mood and energy naturally in the sentence (e.g., "creating suspenseful tension" or "bright and uplifting").
200
+ 6. Specify the BPM.
201
+ 7. Specify the track length as an integer in seconds. Use ranges: energetic/dance 120-180s, pop/rock 180-210s, cinematic/ambient 240-300s.
202
+ 8. Combine all elements into one natural, fluid sentence. Avoid semicolons.
203
+
204
+ Template:
205
+ Genre/Style with main instruments, supporting instruments/layers, and rhythm/percussion creating mood/energy. BPM: X. Length: Y seconds
206
+
207
+ Examples:
208
+ - Jazz ballad with smooth saxophone lead, piano chords, upright bass, brushed drums, and soft strings that swing gently for a warm and cozy evening. BPM: 85. Length: 180 seconds
209
+ - EDM festival track with pulsing synth leads, plucked arpeggios, layered pads, side-chained bass, punchy kick and snare, and hi-hat rolls creating bright, energetic, and uplifting dance energy. BPM: 128. Length: 150 seconds
210
+ - Lo-fi hip-hop chill track with mellow electric piano, soft vinyl crackle, subtle synth pads, low-pass filtered drums, percussion loops, and soft plucked bass for a relaxed, dreamy vibe. BPM: 75. Length: 150 seconds
211
+ - Heavy metal anthem with distorted electric guitars, bass guitar, double bass drums, and cymbal crashes with fast palm-muted riffs creating intense, aggressive energy. BPM: 160. Length: 180 seconds
212
+ - Melancholic piano piece with soft piano lead, string pads, subtle atmospheric synths, and minimal brush percussion evoking a reflective rainy-day feeling. BPM: 60. Length: 240 seconds
213
+ - Suspenseful electronic thriller with pulsing bass synth, arpeggiated lead synth, cinematic pads, glitchy percussion, and high string stabs creating dark and tense energy. BPM: 100. Length: 200 seconds
214
+ - Dreamy ambient soundscape with layered pads, soft bell textures, gentle drones, and wind and water field recordings for ethereal and spacious meditation. BPM: 40. Length: 300 seconds
215
+ - Fingerpicking acoustic guitar solo with harmonics, subtle reverb, occasional shaker and soft stomp percussion, and soft pad layers for warm intimate storytelling. BPM: 70. Length: 120 seconds
216
+ - Synthwave 80s retro track with arpeggiated synth leads, analog pads, electric bass, punchy electronic drums, gated reverb snares, and atmospheric FX for nostalgic and vibrant energy. BPM: 110. Length: 180 seconds
217
+ - Tribal percussion ensemble with congas, djembes, bongos, shakers, and frame drums layered with deep synthetic sub-bass in complex polyrhythms. BPM: 100. Length: 140 seconds
218
+ - 1920s swing jazz with brass section, upright bass, piano, brushed drums, banjo, clarinet, and soft strings that swing lively for energetic dance vibes. BPM: 110. Length: 180 seconds
219
+ - Futuristic electronic sci-fi track with pulsing bass synth, evolving lead synths, layered pads, glitch percussion, robotic FX, and sub-bass for tense cinematic energy. BPM: 125. Length: 200 seconds
220
+ - Ambient underwater soundscape with flowing water textures, soft piano motifs, synth drones, distant bells, and underwater reverb for spacious meditative immersion. BPM: 45. Length: 300 seconds
221
+ - Horror cinematic track with dissonant strings, eerie piano stabs, cinematic percussion including taiko and low toms, and synth FX producing suspenseful creepy tension. BPM: 90. Length: 240 seconds
222
+ - Reggae track with offbeat guitar, warm basslines, snare, kick, congas, and horn stabs giving laid-back groovy energy. BPM: 85. Length: 150 seconds
223
+ - Blues track with soulful electric guitar solos, walking bass, piano, and shuffle drums creating expressive and emotive storytelling. BPM: 90. Length: 180 seconds
224
+ - Latin salsa with congas, timbales, horns, piano montunos, bass, and layered percussion for vibrant danceable energy. BPM: 120. Length: 210 seconds
225
+ - Afrobeat track with electric guitar stabs, horns, layered percussion, congas, shakers, bass groove, and synth pads for vibrant rhythmic energy. BPM: 105. Length: 200 seconds
226
+ - Indie rock track with electric guitar riffs, bass, live drum kit, layered synths, and subtle strings for energetic yet emotional feel. BPM: 110. Length: 180 seconds
227
+ - Funk groove with slap bass, electric guitar chords, brass stabs, drums, congas, and rhythmic keyboards creating high-energy danceable rhythm. BPM: 105. Length: 180 seconds
228
+ - Drum and bass track with fast breakbeat drums, deep sub-bass, sharp synth leads, pads, and atmospheric FX for high-energy club motion. BPM: 175. Length: 150 seconds
229
+ - Dark ambient track with drones, distant bells, low rumbles, soft wind textures, and synth pads producing eerie immersive tension. BPM: 50. Length: 300 seconds
230
+ - Tropical house track with marimba, steel drums, soft synths, smooth bass, layered percussion, and light piano riffs for sunny chill dance vibes. BPM: 110. Length: 180 seconds
231
+ - Progressive rock track with electric guitar leads, organ, bass, drum kit, synth layers, and occasional strings for epic layered energy. BPM: 100. Length: 220 seconds
232
+ - Music box melody with delicate metallic tones and soft resonance, lullaby style, with gentle ambient reverb. BPM: 60. Length: 20 seconds
233
+ - Soft piano arpeggio with warm felted tone and slow attack, lullaby style, with intimate room ambience. BPM: 60. Length: 30 seconds
234
+ - Harp gentle plucked pattern with airy resonance, lullaby style, with dreamy reverb tail. BPM: 65. Length: 25 seconds
235
+ - Acoustic guitar fingerstyle pattern with warm nylon strings and soft dynamics, lullaby style, with subtle room resonance. BPM: 60. Length: 30 seconds
236
+ - Ambient synth pad with smooth evolving texture and soft harmonics, lullaby style, with wide stereo ambience. BPM: 50. Length: 40 seconds
237
+ - Early rock piano with walking left-hand bass line, shuffle rhythms, and blues scale improvisations in energetic 1950s boogie-woogie style. BPM: 160. Length: 180 seconds
238
+ - Trip Hop track with jazzy sampled vibraphone, mid-tempo breakbeat drums, harp, Latin ethnic percussion, and sweeping cinematic strings creating airy, relaxing, soulful lounge vibes. BPM: 90. Length: 180 seconds
239
+ - Country outlaw cinematic instrumental with blues pedal steel guitar, rustic mandolin, fiddle call-and-response, tape-driven rattly drum kit, autoharp, and soaring accordion solo for raw, emotional southern blues expression. BPM: 85. Length: 200 seconds
240
+ - Neo Classical track with sweeping string section, elegant horns, and delicate piano creating soothing, hypnotic, modern, soft, and classic mood. BPM: 70. Length: 180 seconds
241
+ - Art Rock desert track with desolate piano chords, western-themed rhythm guitars, unique lead guitars, rattly vintage drum kit, and supporting bass creating lonely, expansive, beautiful, and strange atmospheres. BPM: 95. Length: 180 seconds
242
+ - Cinematic Sci-Fi score with dramatic horn section, building marcato strings, gliding bassoon, thunderous cymbals, subdued timpani, and subtle synth drones producing awe-inspiring, uplifting, epic intergalactic energy. BPM: 100. Length: 220 seconds
243
+ - West Coast Hip Hop instrumental with cascading harp melodies, smooth Rhodes piano chops, vintage boom bap drums, and walking double bass producing raw, street, and soulful block-party vibes. BPM: 92. Length: 180 seconds
244
+ - Synthwave futuristic track with pulsating synth bass, exciting chords, soaring leads, and reverberating drum machine patterns creating gritty, pounding, and cool energy. BPM: 110. Length: 180 seconds
245
+ - Breakbeat track with complex percussion, intricate breakbeats, gritty synths, lush pads, and 808 bassline producing fresh, modern, futuristic, and rave-ready energy. BPM: 140. Length: 160 seconds
246
+ - Lounge Jazz 1960s smooth track with laid-back drums, piano chords, double bass, soft electric piano, subtle flute, and unique percussion creating beautiful, atmospheric, eclectic, retro, and chill vibes. BPM: 85. Length: 180 seconds
247
+ - Latin Jazz 1950s blissful track with laid-back Latin drums, euphoric piano chords, double bass, orchestral accompaniment, acoustic guitar, and vibraphone producing nostalgic, beautiful, atmospheric, cinematic, and chill mood. BPM: 95. Length: 180 seconds
248
+ - Acid Jazz 1970s summertime track with smooth electric piano, trippy synth leads, laid-back vintage drum kit, fuzzy electric bass, and uplifting violin producing retro, psychedelic, jazzy, relaxing energy. BPM: 100. Length: 180 seconds
249
+ - Progressive Soul 1970s track with feel-good piano, psychedelic organ, groovy vintage drum kit with percussion, fuzzy electric bass, and synth strings producing retro, raw, soulful, joyous atmosphere. BPM: 90. Length: 180 seconds
250
+ - Discotheque 1970s French-inspired track with sultry piano, psychedelic guitars, groovy drum kit, fuzzy electric bass, and melancholic organ producing retro, raw, laid-back, and relaxing mood. BPM: 105. Length: 180 seconds
251
+ - Soul Jazz 1970s track with expressive saxophone, smooth piano, groovy drum kit, rhythmic upright bass, sweeping strings, and minimal vibraphone producing retro, raw, laid-back, and epic energy. BPM: 95. Length: 180 seconds
252
+ - Vintage R&B 1970s live studio track with subtle brass, smooth piano, sweeping strings, and minimal drums producing retro, beautiful, uplifting, nostalgic mood. BPM: 85. Length: 180 seconds
253
+ - 50s Pop track with Latin influence, string section, bold brass, vibraphone, acoustic guitar, flute, ethnic percussion, and brushed drums creating sexy, epic, vintage, retro, melancholic, jazzy, dramatic energy. BPM: 100. Length: 180 seconds
254
+ - A piece of calm, quiet, mellow, serene music perfect for a peaceful film score, featuring soft modulating piano, ambient sfx and foley, beautiful vibraphone, and subtle synthesizer drones. The mood is cinematic, thoughtful, serene and nostalgic. BPM: 55. Length: 300 seconds""",
255
+
256
+ "SFX": """You are a professional sound design expert. Convert the user's input into a precise, vivid sound effects description suitable for generative audio models.
257
+
258
+ Describe clearly:
259
+ - Sound source
260
+ - Physical character (texture, timbre, material: metal, wood, glass, concrete, etc.)
261
+ - Spatial qualities (indoor/outdoor, cave/open field/underwater, dry/reverberant, close-up/distant, echoing/muffled)
262
+ - Temporal evolution (attack, decay, movement, transitions over time)
263
+ - Include motion or spatial movement if applicable (passing, approaching, stereo movement)
264
+
265
+ Audio length rules:
266
+ - Very short sounds (impacts, clicks, gunshots): 1–3 seconds
267
+ - Medium actions (footsteps, object movement, transitions): 3–6 seconds
268
+ - Ambience / environments: 6–15 seconds
269
+ - Always append: Length: X seconds (integer only, no decimals).
270
+
271
+ Output constraints:
272
+ - Length: 1–2 dense sentences maximum
273
+ - Output ONLY the final rewritten prompt
274
+ - No explanations, no formatting, no quotes
275
+ - Use concise but dense technical language
276
+ - Focus strictly on sound effects or ambience
277
+ - Always append: Length: X seconds (integer only, no decimals).
278
+
279
+ Quality guidelines:
280
+ - Be specific and avoid vague terms
281
+ - Prioritize clarity and realism
282
+ - Combine elements into one coherent scene
283
+ - Avoid redundancy
284
+
285
+ Examples:
286
+ - Heavy rain hitting a metal roof during a thunderstorm, distant thunder rumbles, stereo, realistic ambience. Length: 45 seconds
287
+ - Quiet forest at dawn with birds chirping, soft wind through leaves, distant stream flowing. Length: 60 seconds
288
+ - Busy city street at night, cars passing, muffled conversations, occasional horn, urban ambience. Length: 50 seconds
289
+ - Ocean waves crashing against rocky cliffs, strong wind, dramatic and cinematic. Length: 70 seconds
290
+ - Wooden door creaking open slowly in an old house, echoing interior, eerie tone. Length: 3 seconds
291
+ - Glass bottle shattering on concrete, sharp impact, scattered fragments. Length: 2 seconds
292
+ - Footsteps on gravel, steady walking pace, close perspective. Length: 8 seconds
293
+ - Typing rapidly on a mechanical keyboard, crisp tactile clicks. Length: 5 seconds
294
+ - Punch impact with deep bass hit, cinematic trailer style. Length: 2 seconds
295
+ - Car speeding past at high velocity, doppler effect, realistic whoosh. Length: 3 seconds
296
+ - Object falling from height and hitting ground with a heavy thud. Length: 2 seconds
297
+ - Sword swing whooshing through air, fast motion, clean metallic tone. Length: 2 seconds
298
+ - Futuristic laser blast, clean energy pulse, high-tech sound design. Length: 1 seconds
299
+ - Spaceship engine humming, low frequency rumble, interior perspective. Length: 90 seconds
300
+ - Magical spell casting, shimmering particles, rising tonal energy. Length: 8 seconds
301
+ - Teleportation effect, glitchy digital distortion with a soft whoosh. Length: 5 seconds
302
+ - Dark eerie drone with distant whispers, creepy, slow build tension. Length: 120 seconds
303
+ - Sudden horror jump scare sting, sharp violin hit, cinematic. Length: 1 second
304
+ - Metal scraping slowly in a dark tunnel, echoing and ominous. Length: 20 seconds
305
+ - Explosion with debris scattering, deep bass, cinematic realism. Length: 4 seconds
306
+ - Building collapsing, rumbling concrete, dust and debris falling. Length: 25 seconds
307
+ - Fire crackling intensely, wood burning, close-up detail. Length: 80 seconds
308
+ - Gunshot in a large empty warehouse, loud echo decay. Length: 2 seconds
309
+ - Retro arcade coin insert sound, 8-bit style. Length: 1 second
310
+ - Level up chime, bright, rewarding, fantasy RPG style. Length: 2 seconds
311
+ - Error buzzer, short, digital, UI feedback. Length: 1 second
312
+ - Menu navigation clicks, soft futuristic interface sounds. Length: 3 seconds
313
+ - Layered soundscape: rain, thunder, footsteps, and distant sirens all blending naturally. Length: 90 seconds
314
+ - Rapid sequence of three impacts: metal hit, glass break, wood crack, spaced evenly. Length: 4 seconds
315
+ - Sound moving from left to right stereo field: passing motorcycle. Length: 5 seconds
316
+ - Close vs far perspective transition: footsteps approaching then fading away. Length: 6 seconds
317
+ - Tape stop sub drop, a massive sub-bass note that mimics a vinyl record or tape machine being turned off, the pitch and speed drop simultaneously, causing the high-end harmonics to smear and thicken as the sound grinds to a halt at a sub-sonic frequency. Length: 11 seconds
318
+ - Gravel and leaves footsteps, the sound of a hard boot stepping onto dry leaves or gravel, crisp and natural with detailed texture. Length: 11 seconds
319
+ - Ghostship moan, a massive, deep wooden groan with a low-frequency moan, like heavy timber under immense structural tension, swaying slowly, processed with long, dark wooden room reverb for a sense of scale. Length: 11 seconds
320
+ - Bicycle chain, a continuous metallic whirring sound of a chain moving over sprockets, with individual teeth catching the links, processed with resonant band-pass filter to emphasize metallic singing. Length: 11 seconds
321
+ - Warp drive, a sound that starts with a massive suck-back of ambient noise, followed by a supersonic crack and high-pitched zing that disappears into the distance, giving the sense of stretching space-time. Length: 11 seconds
322
+ - Ice cubes, high-pitched musical clinking of hard ice hitting a thin glass, bright resonant ring with subtle liquid sloshing around the edges. Length: 11 seconds
323
+ - Paper shuffle, the sound of a thick stack of heavy bond paper being squared up on a desk, dry papery thud with a quick fanning sound as air moves between the pages. Length: 11 seconds
324
+ - Drawer slam, a blunt, powerful thud made by slamming a wooden desk drawer shut, pronounced low-mid body, slightly distorted for aggressive character. Length: 3 seconds""",
325
+ }
326
+
327
+ # ---------------------------------------------------------------------------
328
+ # Track UI prefixes
329
+ # ---------------------------------------------------------------------------
330
+
331
+ TRACK_TYPE_PREFIXES = {
332
+ "music": "TrackType: Music, VocalType: Instrumental, ",
333
+ "instrument": "TrackType: Instrument, ",
334
+ "sfx": "TrackType: SFX, ",
335
+ }
336
+
337
+ # ---------------------------------------------------------------------------
338
+ # Model cache
339
+ # ---------------------------------------------------------------------------
340
+
341
+ _cache = {"model_id": None, "tokenizer": None, "model": None}
342
+
343
+
344
+ _WEIGHT_SUFFIXES = (".safetensors", ".bin")
345
+
346
+ def is_model_cached(model_id):
347
+ if _cache["model_id"] == model_id:
348
+ return True
349
+ try:
350
+ for repo in scan_cache_dir().repos:
351
+ if repo.repo_id != model_id:
352
+ continue
353
+ for rev in repo.revisions:
354
+ if any(f.file_name.endswith(_WEIGHT_SUFFIXES) for f in rev.files):
355
+ return True
356
+ except Exception:
357
+ pass
358
+ return False
359
+
360
+
361
+ def get_model(model_id):
362
+ if model_id != _cache["model_id"]:
363
+ print(f"[Loading] {model_id}")
364
+ _cache["tokenizer"] = AutoTokenizer.from_pretrained(model_id)
365
+ _cache["model"] = AutoModelForCausalLM.from_pretrained(
366
+ model_id, torch_dtype=torch.bfloat16, device_map="auto"
367
+ )
368
+ _cache["model_id"] = model_id
369
+ return _cache["tokenizer"], _cache["model"]
370
+
371
+ # ---------------------------------------------------------------------------
372
+ # Helpers
373
+ # ---------------------------------------------------------------------------
374
+
375
+ def _postprocess(prompt, raw, tags_mode=True):
376
+ source = raw.split("</think>", 1)[1].strip() if "</think>" in raw else raw
377
+ if source.startswith("- "):
378
+ source = source[2:]
379
+
380
+ result = source.strip()
381
+
382
+ result = re.sub(r'((?:^|(?<=\.\s))\w)', lambda m: m.group().upper(), result)
383
+ if result:
384
+ result = result[0].upper() + result[1:]
385
+ return raw, result
386
+
387
+
388
+ def _extract_examples(system_prompt: str) -> list:
389
+ return [m.group(1).strip() for m in re.finditer(r'^- (.+)', system_prompt, re.MULTILINE)]
390
+
391
+
392
+ _ARTIFACT_RE = re.compile(r'[\[\]*#]')
393
+ _VOCAL_RE = re.compile(
394
+ r'\b(vocals?|singing|singer|female|male|voice|voices|chorus|rap|rapper|chant(ing)?|lyrics?)\b',
395
+ re.IGNORECASE,
396
+ )
397
+ _LENGTH_RE = re.compile(r'\. Length: \d+ seconds\.?\s*$')
398
+
399
+
400
+ def _has_artifacts(text: str) -> bool:
401
+ return (
402
+ bool(_ARTIFACT_RE.search(text))
403
+ or bool(_VOCAL_RE.search(text))
404
+ or not bool(_LENGTH_RE.search(text))
405
+ or len(text.split()) > 45
406
+ )
407
+
408
+
409
+ def _run_model(tokenizer, model, system_prompt, prompt, max_new_tokens, temperature):
410
+ messages = [
411
+ {"role": "system", "content": system_prompt},
412
+ {"role": "user", "content": f"Input: {prompt}\nOutput: "},
413
+ ]
414
+ text = tokenizer.apply_chat_template(
415
+ messages, tokenize=False, add_generation_prompt=True, enable_thinking=False,
416
+ )
417
+ inputs = tokenizer(text, return_tensors="pt").to(model.device)
418
+ with torch.no_grad():
419
+ output_ids = model.generate(
420
+ **inputs, max_new_tokens=int(max_new_tokens),
421
+ do_sample=True, temperature=float(temperature),
422
+ )
423
+ new_ids = output_ids[0][inputs["input_ids"].shape[-1]:]
424
+ return tokenizer.decode(new_ids, skip_special_tokens=True).strip()
425
+
426
+ # ---------------------------------------------------------------------------
427
+ # Main entry point
428
+ # ---------------------------------------------------------------------------
429
+
430
+ def reprompt(prompt, preset, system_prompt, model_id, max_new_tokens, temperature):
431
+ if not prompt.strip():
432
+ import random
433
+ pool = _extract_examples(SYSTEM_PROMPTS["Music"])
434
+ if not pool:
435
+ return "", "", None
436
+ example = random.choice(pool)
437
+ print(f"[Random] picked: {example}")
438
+ category = "music"
439
+ example = TRACK_TYPE_PREFIXES.get(category, "") + example
440
+ return example, example, category
441
+
442
+ tokenizer, model = get_model(model_id)
443
+
444
+ category = None
445
+ if preset == "Auto":
446
+ prompt_lower = prompt.lower()
447
+ if "tracktype: music" in prompt_lower and "vocaltype: instrumental" in prompt_lower:
448
+ label = "music_genre"
449
+ elif "tracktype: instrument" in prompt_lower:
450
+ label = "instrument"
451
+ elif "tracktype: sfx" in prompt_lower:
452
+ label = "sound"
453
+ else:
454
+ label = _run_model(tokenizer, model, SYSTEM_PROMPTS["Classifier"], prompt, 16, 0.1)
455
+ label = label.strip().lower()
456
+ if "music_genre" in label:
457
+ system_prompt = SYSTEM_PROMPTS["Music"]
458
+ category = "music"
459
+ elif "one_shot" in label:
460
+ system_prompt = SYSTEM_PROMPTS["One-shot"]
461
+ category = "one_shot"
462
+ elif "instrument" in label:
463
+ system_prompt = SYSTEM_PROMPTS["Instrument"]
464
+ category = "instrument"
465
+ else:
466
+ system_prompt = SYSTEM_PROMPTS["SFX"]
467
+ category = "sfx"
468
+ print(f"[Auto] classified as: {label}")
469
+
470
+ MAX_RETRIES = 5
471
+ for attempt in range(MAX_RETRIES):
472
+ raw = _run_model(tokenizer, model, system_prompt, prompt, max_new_tokens, temperature)
473
+ print(f"[Reprompt] attempt {attempt+1} IN: {prompt} OUT: {raw}")
474
+ if not _has_artifacts(raw):
475
+ break
476
+ if attempt < MAX_RETRIES - 1:
477
+ print("[Retry] artifacts detected, retrying…")
478
+ raw_out, result = _postprocess(prompt, raw)
479
+ if category:
480
+ result = TRACK_TYPE_PREFIXES.get(category, "") + result
481
+ return raw_out, result, category
stable_audio_3/loading_utils.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import torch
4
+ from safetensors import safe_open
5
+ from safetensors.torch import load_file
6
+ from stable_audio_3.factory import (
7
+ create_autoencoder_from_config,
8
+ create_diffusion_cond_from_config,
9
+ )
10
+
11
+
12
+ def copy_state_dict(model, state_dict):
13
+ model_state_dict = model.state_dict()
14
+ state_dict = remap_state_dict_keys(state_dict, model_state_dict)
15
+ for key in state_dict:
16
+ if (
17
+ key in model_state_dict
18
+ and state_dict[key].shape == model_state_dict[key].shape
19
+ ):
20
+ model_state_dict[key] = state_dict[key]
21
+ else:
22
+ print(
23
+ f"Key {key} not found in target state_dict or shape mismatch. Skipping."
24
+ )
25
+
26
+ model.load_state_dict(model_state_dict, strict=False)
27
+
28
+
29
+ def load_autoencoder(config_path: str, ckpt_path: str, device: str = "cpu"):
30
+ """Load only the autoencoder from a combined DiT+autoencoder checkpoint.
31
+
32
+ Only pretransform tensors are read from disk, directly onto the target device.
33
+ Standalone AE-only checkpoints (e.g. stabilityai/SAME-L / SAME-S) have no prefix.
34
+ """
35
+
36
+ with open(config_path) as f:
37
+ config = json.load(f)
38
+
39
+ autoencoder = create_autoencoder_from_config(config["model"], config["sample_rate"])
40
+
41
+ # Full DiT checkpoints nest the AE under pretransform.model.*;
42
+ # standalone AE-only checkpoints have no prefix.
43
+ nested_prefix = "pretransform.model."
44
+ with safe_open(ckpt_path, framework="pt", device=device) as f:
45
+ all_keys = list(f.keys())
46
+ if any(k.startswith(nested_prefix) for k in all_keys):
47
+ effective_prefix = nested_prefix
48
+ else:
49
+ effective_prefix = "" # standalone AE — keys are already bare
50
+ with safe_open(ckpt_path, framework="pt", device=device) as f:
51
+ state_dict = {
52
+ k[len(effective_prefix) :]: f.get_tensor(k)
53
+ for k in all_keys
54
+ if k.startswith(effective_prefix)
55
+ }
56
+
57
+ copy_state_dict(autoencoder, state_dict)
58
+ return autoencoder.to(device)
59
+
60
+
61
+ def load_diffusion_cond(
62
+ model_config,
63
+ ckpt_path: str,
64
+ device: str = "cuda",
65
+ model_half: bool = False,
66
+ ):
67
+ model = create_diffusion_cond_from_config(model_config)
68
+ copy_state_dict(model, load_file(ckpt_path))
69
+ model.to(device).eval().requires_grad_(False)
70
+ if model_half:
71
+ model.to(torch.float16)
72
+ return model
73
+
74
+
75
+ def remap_state_dict_keys(state_dict, model_state_dict):
76
+ remapped = {}
77
+ for key, value in state_dict.items():
78
+ if key not in model_state_dict:
79
+ parts = key.split(".")
80
+ for i in range(1, len(parts)):
81
+ candidate = ".".join(parts[:i]) + "." + ".".join(parts[i + 1 :])
82
+ if candidate in model_state_dict:
83
+ key = candidate
84
+ break
85
+ remapped[key] = value
86
+ return remapped
stable_audio_3/model.py ADDED
@@ -0,0 +1,554 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import numpy as np
3
+ import torch
4
+ import typing as tp
5
+ from torch.nn.functional import interpolate
6
+
7
+ from stable_audio_3.inference.audio_utils import prepare_audio, numpy_audio_to_tensor
8
+ from stable_audio_3.inference.sampling import sample_diffusion
9
+ from stable_audio_3.loading_utils import load_autoencoder, load_diffusion_cond
10
+ from stable_audio_3.model_configs import ae_models, all_models
11
+ from stable_audio_3.models.lora import (
12
+ set_lora_strength as _set_lora_strength,
13
+ load_and_apply_loras,
14
+ )
15
+
16
+
17
+ class StableAudioModel:
18
+ def __init__(self, model, model_config, device, model_half):
19
+ self.model = model
20
+ self.model_config = model_config
21
+ self.device = device
22
+ self.model_half = model_half
23
+ self.same = self.model.pretransform
24
+ self.dit = self.model.model
25
+ torch.backends.cuda.matmul.allow_tf32 = False
26
+ torch.backends.cudnn.allow_tf32 = False
27
+ torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = False
28
+ torch.backends.cudnn.benchmark = False
29
+
30
+ @staticmethod
31
+ def from_pretrained(model_name_or_path, device=None, model_half=True):
32
+ # Load the model and any necessary components here
33
+ if device is None and torch.cuda.is_available():
34
+ device = "cuda"
35
+ elif device is None and torch.backends.mps.is_available():
36
+ device = "mps"
37
+ elif device is None:
38
+ device = "cpu"
39
+
40
+ if not torch.cuda.is_available():
41
+ if model_name_or_path in ("medium", "medium-base"):
42
+ print(
43
+ f"Warning: You are loading the {model_name_or_path} model without a GPU. This model is not designed to run on cpu"
44
+ )
45
+ model_half = False
46
+
47
+ if model_name_or_path not in all_models:
48
+ raise ValueError(
49
+ f"Unknown model '{model_name_or_path}'. Valid models: {list(all_models)}"
50
+ )
51
+
52
+ model_cfg = all_models[model_name_or_path]
53
+ local_config, local_ckpt = model_cfg.resolve()
54
+ with open(local_config) as f:
55
+ model_config = json.load(f)
56
+
57
+ model = load_diffusion_cond(
58
+ model_config, local_ckpt, device=device, model_half=model_half
59
+ )
60
+ model.use_lora = False
61
+ model.lora_names = []
62
+ return StableAudioModel(model, model_config, device, model_half)
63
+
64
+ def load_lora(self, lora_ckpt_paths):
65
+ """Load LoRA checkpoints onto the model after construction."""
66
+ model_type = self.model_config["model_type"]
67
+ svd_bases_path = self.model_config.get("svd_bases_path")
68
+ load_and_apply_loras(
69
+ self.model, lora_ckpt_paths, model_type, svd_bases_path=svd_bases_path
70
+ )
71
+
72
+ def set_lora_strength(self, strength: float, lora_index: int | None = None):
73
+ _set_lora_strength(self.model.model, strength, lora_index=lora_index)
74
+ _set_lora_strength(self.model.conditioner, strength, lora_index=lora_index)
75
+
76
+ @torch.inference_mode()
77
+ def generate(
78
+ self,
79
+ # Simple path: pass a prompt string and duration
80
+ prompt: str | list = None,
81
+ negative_prompt: str | list = None,
82
+ duration: float | list = 120,
83
+ # Generation parameters
84
+ steps: int = 8,
85
+ cfg_scale: float = 1.0,
86
+ batch_size: int = 1,
87
+ sample_size: int = 5292032,
88
+ truncate_output_to_duration: bool = True,
89
+ # Low-level path: pass pre-built conditioning dicts
90
+ conditioning: tp.Optional[tp.List[dict]] = None,
91
+ conditioning_tensors: tp.Optional[dict] = None,
92
+ negative_conditioning: tp.Optional[tp.List[dict]] = None,
93
+ negative_conditioning_tensors: tp.Optional[dict] = None,
94
+ seed: int = -1,
95
+ # Audio inputs
96
+ init_audio: tp.Optional[tp.Tuple[int, torch.Tensor]] = None,
97
+ init_noise_level: float = 1.0,
98
+ inpaint_audio: tp.Optional[tp.Tuple[int, torch.Tensor]] = None,
99
+ inpaint_mask=None,
100
+ inpaint_mask_start_seconds: tp.Optional[tp.Union[float, tp.List[float]]] = None,
101
+ inpaint_mask_end_seconds: tp.Optional[tp.Union[float, tp.List[float]]] = None,
102
+ # Schedule options
103
+ duration_padding_sec: float = 6.0,
104
+ apg_scale: float = 1.0,
105
+ dist_shift=None,
106
+ return_latents: bool = False,
107
+ chunked_decode: tp.Optional[bool] = None,
108
+ **sampler_kwargs,
109
+ ) -> torch.Tensor:
110
+ """
111
+ Generate audio.
112
+
113
+ Simple path:
114
+ model.generate(prompt="...", duration=30, steps=100)
115
+
116
+ Low-level path (pre-built conditioning):
117
+ model.generate(conditioning=[{"prompt": "...", "seconds_total": 30}], steps=100, ...)
118
+
119
+ Args:
120
+ prompt: The text prompt to condition on. Ignored if conditioning dicts are provided directly.
121
+ negative_prompt: The negative text prompt for classifier-free guidance. Ignored if negative_conditioning dicts are provided directly.
122
+ duration: The duration of the generated audio in seconds. Only used if conditioning dicts with "seconds_total" are not provided.
123
+ steps: The number of diffusion steps to use.
124
+ cfg_scale: Classifier-free guidance scale
125
+ batch_size: The batch size to use for generation.
126
+ sample_size: The length of the audio to generate, in samples.
127
+ truncate_output_to_duration: If True, truncate the output audio to the specified duration.
128
+ conditioning: A dictionary of conditioning parameters to use for generation.
129
+ conditioning_tensors: A dictionary of precomputed conditioning tensors to use for generation.
130
+ negative_conditioning: A dictionary of negative conditioning parameters for classifier-free guidance.
131
+ negative_conditioning_tensors: A dictionary of precomputed negative conditioning tensors for classifier-free guidance
132
+ seed: The random seed to use for generation, or -1 to use a random seed.
133
+ init_audio: A tuple of (sample_rate, audio) to use as the initial audio for generation.
134
+ init_noise_level: The noise level to use when generating from an initial audio sample.
135
+ inpaint_audio: A tuple of (sample_rate, audio) to use as the source audio for inpainting. The inpaint region will be determined by the inpaint_mask or inpaint_mask_start_seconds/inpaint_mask_end_seconds parameters.
136
+ inpaint_mask: A prebuilt mask tensor for inpainting. Shape should be [batch_size, sample_size].
137
+ Ignored if inpaint_mask_start_seconds/inpaint_mask_end_seconds are provided.
138
+ inpaint_mask_start_seconds: Start of the inpaint region in seconds. Can be a float
139
+ for a single region, or a list of floats for multiple non-contiguous regions.
140
+ inpaint_mask_end_seconds: End of the inpaint region in seconds. Can be a float
141
+ for a single region, or a list of floats matching inpaint_mask_start_seconds.
142
+ duration_padding_sec: Extra seconds to add when adapting duration (default 6.0).
143
+ apg_scale: APG (Adaptive Projected Guidance) scale. 1.0 = full APG, 0.0 = vanilla CFG.
144
+ dist_shift: Optional distribution shift override for sampling. If None, uses model.sampling_dist_shift.
145
+ return_latents: Whether to return the latents used for generation instead of the decoded audio.
146
+ chunked_decode: Whether to decode latents in overlapping chunks to reduce peak VRAM. True forces
147
+ chunked decoding on, False forces it off, None (default) uses the value set in the model config.
148
+ **sampler_kwargs: Additional keyword arguments to pass to the sampler.
149
+ """
150
+
151
+ device = str(self.device)
152
+
153
+ # Build conditioning from prompt string if not provided directly
154
+ if conditioning is None and conditioning_tensors is None:
155
+ assert prompt is not None, "Must provide either prompt or conditioning"
156
+ conditioning, negative_conditioning = self._build_conditioning_dicts(
157
+ prompt, negative_prompt, duration, batch_size
158
+ )
159
+
160
+ # Adapt sample size based on seconds_total in conditioning
161
+ audio_sample_size = sample_size
162
+ if conditioning is not None:
163
+ audio_sample_size = self._adapt_sample_size(
164
+ conditioning,
165
+ sample_size,
166
+ duration_padding_sec,
167
+ )
168
+
169
+ # Convert audio sample size to latent size
170
+ latent_sample_size = audio_sample_size
171
+ if self.model.pretransform is not None:
172
+ latent_sample_size = (
173
+ audio_sample_size // self.model.pretransform.downsampling_ratio
174
+ )
175
+
176
+ # Build inpaint mask from seconds if provided
177
+ if (
178
+ inpaint_mask_start_seconds is not None
179
+ and inpaint_mask_end_seconds is not None
180
+ ):
181
+ start_is_list = isinstance(inpaint_mask_start_seconds, list)
182
+ end_is_list = isinstance(inpaint_mask_end_seconds, list)
183
+ if start_is_list != end_is_list:
184
+ raise ValueError(
185
+ "inpaint_mask_start_seconds and inpaint_mask_end_seconds must both be "
186
+ "scalars or both be lists, got "
187
+ f"{type(inpaint_mask_start_seconds).__name__} and "
188
+ f"{type(inpaint_mask_end_seconds).__name__}."
189
+ )
190
+ starts = (
191
+ inpaint_mask_start_seconds
192
+ if start_is_list
193
+ else [inpaint_mask_start_seconds]
194
+ )
195
+ ends = (
196
+ inpaint_mask_end_seconds if end_is_list else [inpaint_mask_end_seconds]
197
+ )
198
+ if len(starts) != len(ends):
199
+ raise ValueError(
200
+ f"inpaint_mask_start_seconds and inpaint_mask_end_seconds must have the same "
201
+ f"length, got {len(starts)} and {len(ends)}."
202
+ )
203
+ inpaint_mask = torch.ones(1, audio_sample_size, device=device)
204
+ for start_sec, end_sec in zip(starts, ends):
205
+ mask_start_samples = min(
206
+ int(start_sec * self.model.sample_rate),
207
+ audio_sample_size,
208
+ )
209
+ mask_end_samples = min(
210
+ int(end_sec * self.model.sample_rate),
211
+ audio_sample_size,
212
+ )
213
+ inpaint_mask[:, mask_start_samples:mask_end_samples] = 0
214
+
215
+ # If the caller passed a prebuilt mask sized to the un-adapted sample_size (or
216
+ # anything longer than audio_sample_size), truncate to audio_sample_size so the
217
+ # downstream nearest-neighbor interpolation preserves the mask's time-domain
218
+ # positions instead of squashing the mask region.
219
+ if inpaint_mask is not None and inpaint_mask.shape[-1] > audio_sample_size:
220
+ inpaint_mask = inpaint_mask[:, :audio_sample_size]
221
+
222
+ # Match training: when mask_padding_attention is used, random_inpaint_mask
223
+ # zeroes the mask past real_sequence_length. Apply the
224
+ # same convention here so the mask matches the training distribution, whether
225
+ # it was built from seconds above or passed in by the caller.
226
+ if inpaint_mask is not None and conditioning is not None:
227
+ max_seconds = max(
228
+ (c.get("seconds_total", 0.0) for c in conditioning), default=0.0
229
+ )
230
+ if max_seconds > 0:
231
+ effective_audio_len = int(max_seconds * self.model.sample_rate)
232
+ mask_len = inpaint_mask.shape[-1]
233
+ if effective_audio_len < mask_len:
234
+ inpaint_mask = inpaint_mask.clone()
235
+ inpaint_mask[:, effective_audio_len:] = 0
236
+
237
+ if inpaint_mask is not None:
238
+ inpaint_mask = inpaint_mask.float()
239
+
240
+ # Seed and noise
241
+ seed = seed if seed != -1 else np.random.randint(0, 99999)
242
+ torch.manual_seed(seed)
243
+ noise = torch.randn(
244
+ [batch_size, self.model.io_channels, latent_sample_size], device=device
245
+ )
246
+
247
+ # Encode conditioning
248
+ if conditioning_tensors is None:
249
+ conditioning_tensors = self.model.conditioner(conditioning, device)
250
+ if (
251
+ negative_conditioning is not None
252
+ or negative_conditioning_tensors is not None
253
+ ):
254
+ if negative_conditioning_tensors is None:
255
+ negative_conditioning_tensors = self.model.conditioner(
256
+ negative_conditioning, device
257
+ )
258
+ else:
259
+ negative_conditioning_tensors = {}
260
+
261
+ # Process init audio
262
+ if init_audio is not None:
263
+ init_audio, inpaint_mask = self._encode_audio_input(
264
+ init_audio, audio_sample_size, inpaint_mask
265
+ )
266
+ init_audio = init_audio.repeat(batch_size, 1, 1)
267
+
268
+ # Process inpaint audio
269
+ if inpaint_audio is not None:
270
+ inpaint_audio, inpaint_mask = self._encode_audio_input(
271
+ inpaint_audio, audio_sample_size, inpaint_mask
272
+ )
273
+ inpaint_audio = inpaint_audio.repeat(batch_size, 1, 1)
274
+ else:
275
+ if inpaint_mask is not None:
276
+ inpaint_mask = interpolate(
277
+ inpaint_mask.unsqueeze(1), size=latent_sample_size, mode="nearest"
278
+ ).squeeze(1)
279
+
280
+ # Build inpaint mask tensor and masked input
281
+ if inpaint_mask is None:
282
+ mask = torch.zeros((batch_size, 1, latent_sample_size), device=device)
283
+ else:
284
+ mask = inpaint_mask.unsqueeze(1)
285
+ mask = mask.to(device)
286
+
287
+ inpaint_input = (
288
+ inpaint_audio * mask.expand_as(inpaint_audio)
289
+ if inpaint_audio is not None
290
+ else torch.zeros(
291
+ (batch_size, self.model.io_channels, latent_sample_size), device=device
292
+ )
293
+ )
294
+
295
+ conditioning_tensors["inpaint_mask"] = [mask]
296
+ conditioning_tensors["inpaint_masked_input"] = [inpaint_input]
297
+ conditioning_inputs = self.model.get_conditioning_inputs(conditioning_tensors)
298
+
299
+ if negative_conditioning_tensors:
300
+ negative_conditioning_tensors["inpaint_mask"] = [mask]
301
+ negative_conditioning_tensors["inpaint_masked_input"] = [inpaint_input]
302
+ negative_conditioning_tensors = self.model.get_conditioning_inputs(
303
+ negative_conditioning_tensors, negative=True
304
+ )
305
+
306
+ model_dtype = next(self.model.model.parameters()).dtype
307
+ noise = noise.type(model_dtype)
308
+ conditioning_inputs = {
309
+ k: v.type(model_dtype) if v is not None else v
310
+ for k, v in conditioning_inputs.items()
311
+ }
312
+
313
+ cond_inputs = {**conditioning_inputs, **negative_conditioning_tensors}
314
+
315
+ sampler_type = sampler_kwargs.pop("sampler_type", None)
316
+
317
+ result = sample_diffusion(
318
+ model=self.model.model,
319
+ noise=noise,
320
+ cond_inputs=cond_inputs,
321
+ diffusion_objective=self.model.diffusion_objective,
322
+ steps=steps,
323
+ cfg_scale=cfg_scale,
324
+ conditioning=conditioning,
325
+ sample_rate=self.model.sample_rate,
326
+ pretransform=self.model.pretransform,
327
+ mask_padding_attention=True,
328
+ use_effective_length_for_schedule=True,
329
+ headroom_seconds=duration_padding_sec,
330
+ dist_shift=dist_shift
331
+ if dist_shift is not None
332
+ else self.model.sampling_dist_shift,
333
+ sampler_type=sampler_type,
334
+ batch_cfg=True,
335
+ rescale_cfg=True,
336
+ apg_scale=apg_scale,
337
+ init_data=init_audio,
338
+ init_noise_level=init_noise_level,
339
+ decode=not return_latents,
340
+ chunked_decode=chunked_decode,
341
+ **sampler_kwargs,
342
+ )
343
+
344
+ if not return_latents:
345
+ result = result.to(torch.float32).clamp(-1, 1)
346
+
347
+ if not return_latents and truncate_output_to_duration:
348
+ if isinstance(duration, (int, float)):
349
+ max_length_samples = int(duration * self.model.sample_rate)
350
+ result = result[:, :, :max_length_samples]
351
+ else:
352
+ if torch.all(torch.tensor(duration) == duration[0]):
353
+ max_length_samples = int(duration[0] * self.model.sample_rate)
354
+ result = result[:, :, :max_length_samples]
355
+ else:
356
+ # Warn that we can't truncate to a single duration if the durations are different, and return the full length output
357
+ print(
358
+ "Warning: Cannot truncate output to a single duration when passing a list of different durations"
359
+ )
360
+
361
+ return result
362
+
363
+ # --- generate() helpers ---
364
+
365
+ @staticmethod
366
+ def _build_conditioning_dicts(prompt, negative_prompt, duration, batch_size):
367
+ """Returns (conditioning, negative_conditioning) lists of dicts."""
368
+
369
+ def _to_list(value, name):
370
+ """Broadcast a scalar or validate a sequence to length batch_size."""
371
+ if isinstance(value, (list, tuple)):
372
+ assert len(value) == batch_size, (
373
+ f"Length of {name} ({len(value)}) must match batch_size ({batch_size})"
374
+ )
375
+ return list(value)
376
+ return [value] * batch_size
377
+
378
+ prompts = _to_list(prompt, "prompt")
379
+ durations = _to_list(duration, "duration")
380
+ conditioning = [
381
+ {"prompt": p, "seconds_total": d} for p, d in zip(prompts, durations)
382
+ ]
383
+
384
+ negative_conditioning = None
385
+ if negative_prompt is not None:
386
+ neg_prompts = _to_list(negative_prompt, "negative_prompt")
387
+ negative_conditioning = [
388
+ {"prompt": p, "seconds_total": d}
389
+ for p, d in zip(neg_prompts, durations)
390
+ ]
391
+
392
+ return conditioning, negative_conditioning
393
+
394
+ def _adapt_sample_size(self, conditioning, sample_size, duration_padding_sec):
395
+ """Returns audio_sample_size adapted from conditioning, clamped to sample_size."""
396
+ max_seconds = 0.0
397
+ for cond_dict in conditioning:
398
+ if "seconds_total" in cond_dict:
399
+ max_seconds = max(max_seconds, cond_dict["seconds_total"])
400
+
401
+ if max_seconds <= 0:
402
+ return sample_size
403
+
404
+ target_audio_samples = int(
405
+ (max_seconds + duration_padding_sec) * self.model.sample_rate
406
+ )
407
+ if self.model.pretransform is not None:
408
+ ds_ratio = self.model.pretransform.downsampling_ratio
409
+ # Round up to nearest multiple of downsampling ratio
410
+ target_audio_samples = (
411
+ (target_audio_samples + ds_ratio - 1) // ds_ratio
412
+ ) * ds_ratio
413
+ encoder_config = self.model_config["model"]["pretransform"]["config"][
414
+ "encoder"
415
+ ]["config"]
416
+ chunk_size = encoder_config.get("chunk_size", 32)
417
+ stride = encoder_config["strides"][0] # or min(strides) if multiple
418
+ # For chunked attention with latent space, align to chunk size after downsampling
419
+ latent_align = chunk_size // stride
420
+ align = ds_ratio * latent_align
421
+ target_audio_samples = ((target_audio_samples + align - 1) // align) * align
422
+
423
+ return min(target_audio_samples, sample_size)
424
+
425
+ def _encode_audio_input(self, audio_input, audio_sample_size, inpaint_mask=None):
426
+ """
427
+ Converts a (sample_rate, audio) tuple to an encoded latent tensor.
428
+ If model has a pretransform, encodes to latent space and downsamples inpaint_mask to match.
429
+ Returns (encoded_audio, updated_inpaint_mask). encoded_audio is not yet repeated to batch size.
430
+ """
431
+ device = str(self.device)
432
+ in_sr, audio_data = audio_input
433
+ if isinstance(audio_data, np.ndarray):
434
+ audio_data = numpy_audio_to_tensor(audio_data)
435
+ io_channels = (
436
+ self.model.pretransform.io_channels
437
+ if self.model.pretransform is not None
438
+ else self.model.io_channels
439
+ )
440
+ audio = prepare_audio(
441
+ audio_data,
442
+ in_sr=in_sr,
443
+ target_sr=self.model.sample_rate,
444
+ target_length=audio_sample_size,
445
+ target_channels=io_channels,
446
+ device=device,
447
+ )
448
+ if self.model.pretransform is not None:
449
+ audio = audio.to(next(self.model.pretransform.parameters()).dtype)
450
+ audio = self.model.pretransform.encode(audio)
451
+ if inpaint_mask is not None:
452
+ inpaint_mask = interpolate(
453
+ inpaint_mask.unsqueeze(1),
454
+ size=audio.shape[-1],
455
+ mode="nearest",
456
+ ).squeeze(1)
457
+ return audio, inpaint_mask
458
+
459
+
460
+ class AutoencoderModel:
461
+ def __init__(self, autoencoder, sample_rate, device):
462
+ self.autoencoder = autoencoder
463
+ self.sample_rate = sample_rate
464
+ self.device = device
465
+
466
+ @staticmethod
467
+ def from_pretrained(model_name, device=None):
468
+ if device is None:
469
+ if torch.cuda.is_available():
470
+ device = "cuda"
471
+ elif torch.backends.mps.is_available():
472
+ device = "mps"
473
+ else:
474
+ device = "cpu"
475
+
476
+ if not torch.cuda.is_available():
477
+ if model_name == "same-l":
478
+ print(
479
+ f"Warning: You are loading the {model_name} model without a GPU. This model is not designed to run on cpu"
480
+ )
481
+
482
+ if model_name not in ae_models:
483
+ raise ValueError(
484
+ f"Unknown autoencoder '{model_name}'. Valid models: {list(ae_models)}"
485
+ )
486
+
487
+ cfg = ae_models[model_name]
488
+ local_config, local_ckpt = cfg.resolve()
489
+
490
+ with open(local_config) as f:
491
+ sample_rate = json.load(f)["sample_rate"]
492
+
493
+ autoencoder = load_autoencoder(local_config, local_ckpt, device=device)
494
+ autoencoder.eval().requires_grad_(False)
495
+
496
+ return AutoencoderModel(autoencoder, sample_rate, device)
497
+
498
+ @torch.inference_mode()
499
+ def encode(self, audio, sr, chunked=False, chunk_size=128, overlap=32):
500
+ """Encode audio to latents.
501
+
502
+ Args:
503
+ audio: A single waveform tensor (C, T), a list of waveform tensors,
504
+ or a pre-batched tensor (B, C, T). Resampling, channel conversion,
505
+ and padding are handled automatically; passing sr=ae.sample_rate
506
+ for already-preprocessed audio skips resampling.
507
+ sr: Sample rate of the input audio, or a list of sample rates when
508
+ audio is a list.
509
+ chunked: If True, encode in overlapping chunks to save memory.
510
+ chunk_size: Chunk size in latent frames (only used when chunked=True).
511
+ overlap: Overlap in latent frames between chunks (only used when chunked=True).
512
+
513
+ Returns:
514
+ Latent tensor of shape (B, latent_dim, latent_time).
515
+ """
516
+ if isinstance(audio, list):
517
+ preprocessed = self.autoencoder.preprocess_audio_list_for_encoder(
518
+ audio, in_sr_list=sr
519
+ )
520
+ elif isinstance(audio, torch.Tensor) and audio.dim() == 3:
521
+ sr_list = sr if isinstance(sr, list) else [sr] * audio.shape[0]
522
+ preprocessed = self.autoencoder.preprocess_audio_list_for_encoder(
523
+ list(audio), in_sr_list=sr_list
524
+ )
525
+ else:
526
+ preprocessed = self.autoencoder.preprocess_audio_for_encoder(
527
+ audio, in_sr=sr
528
+ )
529
+ return self.autoencoder.encode_audio(
530
+ preprocessed.to(self.device),
531
+ chunked=chunked,
532
+ chunk_size=chunk_size,
533
+ overlap=overlap,
534
+ )
535
+
536
+ @torch.inference_mode()
537
+ def decode(self, latents, chunked=False, chunk_size=128, overlap=32):
538
+ """Decode latents to audio.
539
+
540
+ Args:
541
+ latents: Latent tensor of shape (B, latent_dim, latent_time).
542
+ chunked: If True, decode in overlapping chunks to save memory.
543
+ chunk_size: Chunk size in latent frames (only used when chunked=True).
544
+ overlap: Overlap in latent frames between chunks (only used when chunked=True).
545
+
546
+ Returns:
547
+ Audio tensor of shape (B, channels, samples).
548
+ """
549
+ return self.autoencoder.decode_audio(
550
+ latents,
551
+ chunked=chunked,
552
+ chunk_size=chunk_size,
553
+ overlap=overlap,
554
+ )
stable_audio_3/model_configs.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+
3
+ from huggingface_hub import hf_hub_download, try_to_load_from_cache
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class ModelConfig:
8
+ repo_id: str
9
+ config_path: str
10
+ ckpt_path: str
11
+
12
+ def resolve(self):
13
+ """Download files from HuggingFace Hub and return local cached paths."""
14
+ local_config = hf_hub_download(repo_id=self.repo_id, filename=self.config_path)
15
+ local_ckpt = hf_hub_download(repo_id=self.repo_id, filename=self.ckpt_path)
16
+ return local_config, local_ckpt
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class AutoencoderModelConfig:
21
+ """Config for a standalone autoencoder HF repo (e.g. stabilityai/SAME-S).
22
+
23
+ resolve() first checks whether any full Stable Audio 3 checkpoint that contains the same
24
+ autoencoder is already cached locally. If one is found it is used as-is
25
+ (load_autoencoder strips the pretransform.* prefix automatically), avoiding a
26
+ redundant download. Otherwise the lightweight AE-only repo is fetched instead.
27
+ """
28
+
29
+ ae_repo_id: str
30
+ ae_config_path: str
31
+ ae_ckpt_path: str
32
+ stable_audio_3: tuple[ModelConfig, ...]
33
+
34
+ def resolve(self):
35
+ """Return (config_path, ckpt_path), preferring an already-cached Stable Audio 3 checkpoint."""
36
+ for fallback in self.stable_audio_3:
37
+ cached_config = try_to_load_from_cache(
38
+ fallback.repo_id, fallback.config_path
39
+ )
40
+ cached_ckpt = try_to_load_from_cache(fallback.repo_id, fallback.ckpt_path)
41
+ if isinstance(cached_config, str) and isinstance(cached_ckpt, str):
42
+ return cached_config, cached_ckpt
43
+
44
+ # No Stable Audio 3 checkpoint found in local cache — download the AE-only repo.
45
+ local_config = hf_hub_download(
46
+ repo_id=self.ae_repo_id, filename=self.ae_config_path
47
+ )
48
+ local_ckpt = hf_hub_download(
49
+ repo_id=self.ae_repo_id, filename=self.ae_ckpt_path
50
+ )
51
+ return local_config, local_ckpt
52
+
53
+
54
+ models: dict[str, ModelConfig] = {
55
+ "small-music": ModelConfig(
56
+ "stabilityai/stable-audio-3-small-music",
57
+ "model_config.json",
58
+ "model.safetensors",
59
+ ),
60
+ "small-music-base": ModelConfig(
61
+ "stabilityai/stable-audio-3-small-music-base",
62
+ "model_config.json",
63
+ "model.safetensors",
64
+ ),
65
+ "small-sfx": ModelConfig(
66
+ "stabilityai/stable-audio-3-small-sfx",
67
+ "model_config.json",
68
+ "model.safetensors",
69
+ ),
70
+ "small-sfx-base": ModelConfig(
71
+ "stabilityai/stable-audio-3-small-sfx-base",
72
+ "model_config.json",
73
+ "model.safetensors",
74
+ ),
75
+ "medium": ModelConfig(
76
+ "stabilityai/stable-audio-3-medium",
77
+ "model_config.json",
78
+ "model.safetensors",
79
+ ),
80
+ "medium-base": ModelConfig(
81
+ "stabilityai/stable-audio-3-medium-base",
82
+ "model_config.json",
83
+ "model.safetensors",
84
+ ),
85
+ }
86
+
87
+ # Stable Audio 3 full-model configs to probe (in order) before downloading the AE-only repo.
88
+ _small_stable_audio_3: tuple[ModelConfig, ...] = (
89
+ models["small-music"],
90
+ models["small-sfx"],
91
+ )
92
+ _medium_stable_audio_3: tuple[ModelConfig, ...] = (models["medium"],)
93
+
94
+ ae_models: dict[str, AutoencoderModelConfig] = {
95
+ "same-s": AutoencoderModelConfig(
96
+ ae_repo_id="stabilityai/SAME-S",
97
+ ae_config_path="model_config.json",
98
+ ae_ckpt_path="model.safetensors",
99
+ stable_audio_3=_small_stable_audio_3,
100
+ ),
101
+ "same-l": AutoencoderModelConfig(
102
+ ae_repo_id="stabilityai/SAME-L",
103
+ ae_config_path="model_config.json",
104
+ ae_ckpt_path="model.safetensors",
105
+ stable_audio_3=_medium_stable_audio_3,
106
+ ),
107
+ }
108
+
109
+ base_models: dict[str, ModelConfig] = {
110
+ k: v for k, v in models.items() if k.endswith("-base")
111
+ }
112
+
113
+ all_models: dict[str, ModelConfig | AutoencoderModelConfig] = {
114
+ **models,
115
+ **ae_models,
116
+ }
stable_audio_3/models/__init__.py ADDED
File without changes
stable_audio_3/models/autoencoders.py ADDED
@@ -0,0 +1,639 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+
3
+ from torch import nn
4
+ from torch.nn.utils import weight_norm
5
+ from torchaudio import transforms as T
6
+ from einops import rearrange
7
+
8
+ from ..inference.audio_utils import prepare_audio
9
+ from .transformer import TransformerBlock
10
+
11
+ def WNConv1d(*args, **kwargs):
12
+ return weight_norm(nn.Conv1d(*args, **kwargs))
13
+
14
+ def checkpoint(function, *args, **kwargs):
15
+ kwargs.setdefault("use_reentrant", False)
16
+ return torch.utils.checkpoint.checkpoint(function, *args, **kwargs)
17
+
18
+
19
+ class Transpose(nn.Module):
20
+ def __init__(self):
21
+ super().__init__()
22
+ def forward(self, x, **kwargs):
23
+ return rearrange(x, '... a b -> ... b a')
24
+
25
+ def _zero_pad_modulo_sequence(x, size, dim=-2):
26
+ input_len = x.shape[dim]
27
+ pad_len = (size - input_len % size) % size
28
+ if pad_len > 0:
29
+ pad_shape = list(x.shape)
30
+ pad_shape[dim] = pad_len
31
+ x = torch.cat([x, torch.zeros(pad_shape, device=x.device, dtype=x.dtype)], dim=dim)
32
+ return x
33
+
34
+ class TransformerResamplingBlock(nn.Module):
35
+ def __init__(self, in_channels, out_channels, stride, sliding_window = None, chunk_size = 128, chunk_midpoint_shift = False, type = 'encoder', transformer_depth = 3, checkpointing = False,
36
+ conformer = False, layer_scale = False, dim_heads = 128, differential = True, variable_stride = False, feat_scale = False,
37
+ sinusoidal_blocks = 0, mask_noise = 0, ff_mult = 3, mapping_bias = True, cross_attn = False, dyt = True, conv_mapping = False, freeze_backbone = False, **kwargs):
38
+ super().__init__()
39
+ if type not in ['encoder', 'decoder']:
40
+ raise ValueError(f"Unknown type {type}. Must be 'encoder' or 'decoder'")
41
+
42
+ self.checkpointing = checkpointing
43
+
44
+ transformer_dim = out_channels if type == 'encoder' else in_channels
45
+ transformers = []
46
+ self.in_channels = in_channels
47
+ self.out_channels = out_channels
48
+ self.variable_stride = variable_stride
49
+ self.stride = stride
50
+ self.mapping = WNConv1d(in_channels, out_channels, 3 if conv_mapping else 1, padding = 'same', bias = mapping_bias) if in_channels != out_channels else nn.Identity()
51
+ self.chunk_size = chunk_size
52
+ self.chunk_midpoint_shift = chunk_midpoint_shift
53
+ self.type = type
54
+ self.mask_noise = mask_noise
55
+ self.sliding_window_latents = sliding_window
56
+
57
+ self.sliding_window_seq = self._get_sliding_window_size(sliding_window, stride)
58
+ self.input_seg_size, self.output_seg_size, self.sub_chunk_size = self._get_seg_sizes(stride)
59
+ self.transformer_depth = transformer_depth
60
+ for i in range(transformer_depth):
61
+ sinusoidal = True if ((transformer_depth - i) < sinusoidal_blocks) else False
62
+ transformers.append(TransformerBlock(transformer_dim,
63
+ dim_heads = dim_heads,
64
+ causal = False,
65
+ zero_init_branch_outputs = True if not layer_scale else False,
66
+ norm_type = 'dyt' if dyt else 'rms_norm',
67
+ conformer = conformer,
68
+ layer_scale = layer_scale,
69
+ add_rope = True,
70
+ attn_kwargs={'qk_norm': "dyt" if dyt else "rms", "qk_norm_eps": 1e-3, "differential": differential, "feat_scale": feat_scale},
71
+ ff_kwargs={'mult': ff_mult, 'no_bias': False, "sinusoidal": sinusoidal},
72
+ norm_kwargs = {'eps': 1e-3},
73
+ cross_attend = cross_attn))
74
+
75
+ self.new_tokens = nn.Parameter(1e-5 * torch.randn(1, self.output_seg_size if not self.variable_stride else 1, out_channels if type == 'encoder' else in_channels))
76
+
77
+ self.transformers = nn.ModuleList(transformers)
78
+
79
+ if freeze_backbone:
80
+ for param in self.transformers.parameters():
81
+ param.requires_grad = False
82
+ self.new_tokens.requires_grad = False
83
+
84
+ def _get_sliding_window_size(self, window, stride, prepend_cond_length = 0):
85
+ if window is None:
86
+ return None
87
+ else:
88
+ return [(win * (stride + 1 + prepend_cond_length)) for win in window]
89
+
90
+ def _get_seg_sizes(self, stride, prepend_cond_length = 0):
91
+ sub_chunk_size = stride + 1 + prepend_cond_length
92
+ if self.sliding_window_latents is None:
93
+ assert (self.chunk_size % stride) == 0, f"Stride must fit evenly into chunk size:{self.chunk_size}"
94
+ input_seg_size = stride if self.type == 'encoder' else 1
95
+ output_seg_size = 1 if self.type == 'encoder' else stride
96
+ return input_seg_size, output_seg_size, sub_chunk_size
97
+
98
+ #@torch.compile
99
+ def forward(self, x, stride = None, return_features = False, override_new_tokens = None, prepend_cond = None, cross_attn_cond = None):
100
+ batch_size = x.shape[0]
101
+ input_length = x.shape[-1]
102
+ if return_features:
103
+ features = []
104
+
105
+ if stride == None:
106
+ input_seg_size = self.input_seg_size
107
+ output_seg_size = self.output_seg_size
108
+ sub_chunk_size = self.sub_chunk_size
109
+ sliding_window = self.sliding_window_seq
110
+ else:
111
+ if not self.variable_stride:
112
+ print("cannot override stride if variable_stride is not set")
113
+ prepend_cond_length = prepend_cond.shape[-2] if prepend_cond is not None else 0
114
+ input_seg_size, output_seg_size, sub_chunk_size = self._get_seg_sizes(stride, prepend_cond_length)
115
+ sliding_window = self._get_sliding_window_size(self.sliding_window_latents, stride, prepend_cond_length)
116
+
117
+ if self.type == 'encoder':
118
+ # Pad before mapping so silence zeros get projected through the mapping,
119
+ # rather than inserting raw zeros in the mapped space
120
+ if self.transformer_depth > 0:
121
+ if sliding_window is None:
122
+ pad_modulo = self.chunk_size
123
+ else:
124
+ pad_modulo = input_seg_size
125
+ x = _zero_pad_modulo_sequence(x, pad_modulo, dim=-1)
126
+ x = self.mapping(x)
127
+
128
+ if self.transformer_depth > 0:
129
+ x = rearrange(x, '... a b -> ... b a')
130
+ if return_features:
131
+ features.append(x)
132
+ if self.type != 'encoder':
133
+ if sliding_window is None:
134
+ active_stride = stride if stride is not None else self.stride
135
+ pad_modulo = self.chunk_size // active_stride
136
+ x = _zero_pad_modulo_sequence(x, pad_modulo)
137
+ else:
138
+ x = _zero_pad_modulo_sequence(x, input_seg_size)
139
+ x = rearrange(x, 'b (n c) d -> (b n) c d', c = input_seg_size)
140
+ new_token_seq_dim = -1 if not self.variable_stride else output_seg_size
141
+ new_tokens = self.new_tokens.expand([x.shape[0],new_token_seq_dim,-1])
142
+ if override_new_tokens is not None:
143
+ #print(f"Using override new tokens with shape {override_new_tokens.shape}, new tokens shape {new_tokens.shape}, x shape {x.shape}")
144
+ override_new_tokens = rearrange(override_new_tokens, 'b (n c) d -> (b n) c d', c = output_seg_size)
145
+ new_tokens = new_tokens + override_new_tokens
146
+ elif self.mask_noise > 0:
147
+ new_tokens = new_tokens + torch.randn_like(new_tokens) * self.mask_noise
148
+ x = torch.cat([x,new_tokens], dim = -2)
149
+ if prepend_cond is not None:
150
+ n = x.shape[0] // batch_size
151
+ cond_folded = prepend_cond.unsqueeze(1).expand(batch_size, n, prepend_cond.shape[-2], x.shape[-1]).reshape(n * batch_size, prepend_cond.shape[-2], x.shape[-1])
152
+ x = torch.cat([cond_folded, x], dim=-2)
153
+ x = rearrange(x, '(b n) c d -> b (n c) d', b=batch_size)#.contiguous()
154
+
155
+ # Fold into contiguous chunks if no sliding window
156
+ if sliding_window is None:
157
+ prepend_cond_length = prepend_cond.shape[-2] if prepend_cond is not None else 0
158
+ effective_chunk_size = self.chunk_size + self.chunk_size * (1 + prepend_cond_length) // (stride if stride is not None else self.stride)
159
+
160
+ if sliding_window is None and self.chunk_midpoint_shift:
161
+ split = self.transformer_depth // 2
162
+ shift = effective_chunk_size // 2
163
+
164
+ # First half: standard chunks
165
+ nc = x.shape[1] // effective_chunk_size
166
+ x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
167
+ cross_attn_first = None
168
+ if cross_attn_cond is not None:
169
+ cross_attn_first = cross_attn_cond.repeat_interleave(nc, dim=0)
170
+ for layer in self.transformers[:split]:
171
+ if self.checkpointing:
172
+ x = checkpoint(layer, x, context = cross_attn_first, self_attention_flash_sliding_window = None)
173
+ else:
174
+ x = layer(x, context = cross_attn_first)
175
+ if return_features:
176
+ features.append(rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size))
177
+ x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
178
+
179
+ # Second half: shifted chunks with sub-chunk repeat padding
180
+ # shift is always a multiple of sub_chunk_size, so slicing
181
+ # by shift gives whole sub-chunks in their original order
182
+ x = torch.cat([x[:, :shift, :], x, x[:, -shift:, :]], dim=1)
183
+ nc_shifted = x.shape[1] // effective_chunk_size
184
+ x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
185
+ cross_attn_second = None
186
+ if cross_attn_cond is not None:
187
+ cross_attn_second = cross_attn_cond.repeat_interleave(nc_shifted, dim=0)
188
+ for layer in self.transformers[split:]:
189
+ if self.checkpointing:
190
+ x = checkpoint(layer, x, context = cross_attn_second, self_attention_flash_sliding_window = None)
191
+ else:
192
+ x = layer(x, context = cross_attn_second)
193
+ if return_features:
194
+ feat = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
195
+ features.append(feat[:, shift:-shift, :])
196
+ x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
197
+ x = x[:, shift:-shift, :]
198
+ else:
199
+ if sliding_window is None:
200
+ x = rearrange(x, 'b (nc cc) d -> (b nc) cc d', cc=effective_chunk_size)
201
+
202
+ for layer in self.transformers:
203
+ if self.checkpointing:
204
+ x = checkpoint(layer, x, context = cross_attn_cond, self_attention_flash_sliding_window = sliding_window)
205
+ else:
206
+ x = layer(x, context = cross_attn_cond, self_attention_flash_sliding_window = sliding_window)
207
+ if return_features:
208
+ features.append(x)
209
+
210
+ # Unfold chunks back to original batch
211
+ if sliding_window is None:
212
+ x = rearrange(x, '(b nc) cc d -> b (nc cc) d', b=batch_size)
213
+
214
+ x = rearrange(x, 'b (n c) d -> (b n) c d', c=sub_chunk_size)
215
+ x = x[:,-output_seg_size:,:]
216
+ x = rearrange(x, '(b n) c d -> b d (n c)', b=batch_size)
217
+ if self.type == 'decoder':
218
+ x = self.mapping(x)
219
+ if return_features:
220
+ return x, features
221
+ else:
222
+ return x
223
+
224
+
225
+ class SAMEEncoder(nn.Module):
226
+ def __init__(self,
227
+ in_channels=2,
228
+ channels=128,
229
+ latent_dim=32,
230
+ c_mults = [1, 2, 4, 8],
231
+ strides = [2, 4, 8, 8],
232
+ transformer_depths = [3,3,3,3],
233
+ sliding_window = None,
234
+ checkpointing = False,
235
+ conformer = False,
236
+ layer_scale = False,
237
+ causal = False,
238
+ differential = True,
239
+ variable_stride = False,
240
+ mask_noise = 0.0,
241
+ conv_mapping = False,
242
+ freeze_backbone = False,
243
+ **kwargs
244
+ ):
245
+ super().__init__()
246
+ self.in_channels = in_channels
247
+ self.strides = strides
248
+
249
+ channel_dims = [c * channels for c in c_mults]
250
+ channel_dims = [in_channels] + channel_dims
251
+
252
+ self.depth = len(c_mults)
253
+
254
+ layers = []
255
+
256
+ for i in range(self.depth):
257
+ layers += [TransformerResamplingBlock(in_channels=channel_dims[i], out_channels=channel_dims[i+1], stride=strides[i], transformer_depth = transformer_depths[i],
258
+ sliding_window = sliding_window, checkpointing = checkpointing, conformer = conformer, layer_scale = layer_scale, causal = causal,
259
+ differential = differential, variable_stride = variable_stride, mask_noise = mask_noise, conv_mapping = conv_mapping,
260
+ freeze_backbone = freeze_backbone, **kwargs)]
261
+
262
+ layers += [Transpose(), nn.Linear(channel_dims[-1], latent_dim), Transpose()]
263
+ self.layers = nn.ModuleList(layers)
264
+
265
+ if freeze_backbone:
266
+ for param in self.layers[-2].parameters():
267
+ param.requires_grad = False
268
+
269
+ def forward(self, x, override_stride = None, return_features = False, **kwargs):
270
+ if override_stride != None:
271
+ assert isinstance(override_stride, list), "override_stride must be a list"
272
+ assert len(override_stride) == self.depth, "override_stride must be a list containing strides for every layer"
273
+ for i, layer in enumerate(self.layers):
274
+ if isinstance(layer, TransformerResamplingBlock):
275
+ if override_stride != None:
276
+ stride = override_stride[i]
277
+ else:
278
+ stride = None
279
+ if return_features:
280
+ x, features = layer(x, stride = stride, return_features = True)
281
+ else:
282
+ x = layer(x, stride = stride)
283
+ else:
284
+ x = layer(x)
285
+ if return_features:
286
+ return x, features
287
+ else:
288
+ return x
289
+
290
+ class SAMEDecoder(nn.Module):
291
+ def __init__(self,
292
+ out_channels=2,
293
+ channels=128,
294
+ latent_dim=32,
295
+ c_mults = [1, 2, 4, 8],
296
+ strides = [2, 4, 8, 8],
297
+ transformer_depths = [3,3,3,3],
298
+ sliding_window = None,
299
+ checkpointing = False,
300
+ conformer = False,
301
+ layer_scale = False,
302
+ causal = False,
303
+ differential = True,
304
+ variable_stride = False,
305
+ sinusoidal_blocks = [0,0,0,0],
306
+ mask_noise = 0.0,
307
+ conv_mapping = False,
308
+ freeze_backbone = False,
309
+ **kwargs
310
+ ):
311
+ super().__init__()
312
+
313
+ channel_dims = [c * channels for c in c_mults]
314
+ channel_dims = [out_channels] + channel_dims
315
+
316
+ self.depth = len(c_mults)
317
+
318
+ layers = [Transpose(), nn.Linear(latent_dim, channel_dims[-1]), Transpose()]
319
+
320
+ for i in range(self.depth, 0, -1):
321
+ layers += [TransformerResamplingBlock(in_channels=channel_dims[i], out_channels=channel_dims[i-1], stride=strides[i-1], type = 'decoder', transformer_depth = transformer_depths[i-1],
322
+ sliding_window = sliding_window, checkpointing = checkpointing, conformer = conformer, layer_scale = layer_scale, causal = causal, differential = differential,
323
+ variable_stride = variable_stride, sinusoidal_blocks = sinusoidal_blocks[i-1], mask_noise = mask_noise, conv_mapping = conv_mapping,
324
+ freeze_backbone = freeze_backbone, **kwargs)]
325
+
326
+ self.layers = nn.ModuleList(layers)
327
+
328
+ if freeze_backbone:
329
+ for param in self.layers[1].parameters():
330
+ param.requires_grad = False
331
+
332
+ def forward(self, x, override_stride = None, **kwargs):
333
+ if override_stride != None:
334
+ assert isinstance(override_stride, list), "override_stride must be a list"
335
+ assert len(override_stride) == self.depth, "override_stride must be a list containing strides for every layer"
336
+
337
+ transformer_layer_index = 0
338
+ for i, layer in enumerate(self.layers):
339
+ if isinstance(layer, TransformerResamplingBlock):
340
+ if override_stride != None:
341
+ stride = override_stride[transformer_layer_index]
342
+ else:
343
+ stride = None
344
+ x = layer(x, stride = stride)
345
+ transformer_layer_index += 1
346
+ else:
347
+ x = layer(x)
348
+ return x
349
+
350
+
351
+ class AudioAutoencoder(nn.Module):
352
+ def __init__(
353
+ self,
354
+ encoder,
355
+ decoder,
356
+ latent_dim,
357
+ downsampling_ratio,
358
+ sample_rate,
359
+ io_channels=2,
360
+ bottleneck: nn.Module = None,
361
+ pretransform: nn.Module = None,
362
+ in_channels = None,
363
+ out_channels = None,
364
+ soft_clip = False,
365
+ freeze_pretransform = False
366
+ ):
367
+ super().__init__()
368
+ self.downsampling_ratio = downsampling_ratio
369
+ self.sample_rate = sample_rate
370
+
371
+ self.latent_dim = latent_dim
372
+ self.io_channels = io_channels
373
+ self.in_channels = io_channels
374
+ self.out_channels = io_channels
375
+
376
+ self.min_length = self.downsampling_ratio
377
+
378
+ if in_channels is not None:
379
+ self.in_channels = in_channels
380
+
381
+ if out_channels is not None:
382
+ self.out_channels = out_channels
383
+
384
+ self.bottleneck = bottleneck
385
+
386
+ self.encoder = encoder
387
+
388
+ self.decoder = decoder
389
+
390
+ self.pretransform = pretransform
391
+
392
+ self.freeze_pretransform = freeze_pretransform
393
+ if self.pretransform is not None:
394
+ if self.freeze_pretransform:
395
+ for p in self.pretransform.parameters():
396
+ p.requires_grad = False
397
+ else:
398
+ for p in self.pretransform.parameters():
399
+ p.requires_grad = True
400
+
401
+ self.soft_clip = soft_clip
402
+
403
+ self.is_discrete = False
404
+
405
+ def encode(self, audio, return_info=False, skip_pretransform=False, iterate_batch=False, return_pretransform = False, **kwargs):
406
+
407
+ info = {}
408
+ if self.pretransform is not None and not skip_pretransform:
409
+ if self.pretransform.enable_grad:
410
+ if iterate_batch:
411
+ audios = []
412
+ for i in range(audio.shape[0]):
413
+ audios.append(self.pretransform.encode(audio[i:i+1]))
414
+ audio = torch.cat(audios, dim=0)
415
+ else:
416
+ audio = self.pretransform.encode(audio)
417
+ else:
418
+ with torch.no_grad():
419
+ if iterate_batch:
420
+ audios = []
421
+ for i in range(audio.shape[0]):
422
+ audios.append(self.pretransform.encode(audio[i:i+1]))
423
+ audio = torch.cat(audios, dim=0)
424
+ else:
425
+ audio = self.pretransform.encode(audio)
426
+ if self.encoder is not None:
427
+ if iterate_batch:
428
+ latents = []
429
+ for i in range(audio.shape[0]):
430
+ latents.append(self.encoder(audio[i:i+1], **kwargs))
431
+ latents = torch.cat(latents, dim=0)
432
+ else:
433
+ latents = self.encoder(audio, **kwargs)
434
+ else:
435
+ latents = audio
436
+ if self.bottleneck is not None:
437
+ # TODO: Add iterate batch logic, needs to merge the info dicts
438
+ latents, bottleneck_info = self.bottleneck.encode(latents, return_info=True, **kwargs)
439
+
440
+ info.update(bottleneck_info)
441
+
442
+ if return_info and return_pretransform:
443
+ return latents, info, audio
444
+ elif return_info:
445
+ return latents, info
446
+ elif return_pretransform:
447
+ return latents, audio
448
+ else:
449
+ return latents
450
+
451
+ def decode(self, latents, iterate_batch=False, return_loss = False, **kwargs):
452
+ if self.bottleneck is not None:
453
+ if iterate_batch:
454
+ decoded = []
455
+ for i in range(latents.shape[0]):
456
+ decoded.append(self.bottleneck.decode(latents[i:i+1]))
457
+ latents = torch.cat(decoded, dim=0)
458
+ else:
459
+ latents = self.bottleneck.decode(latents)
460
+ if iterate_batch:
461
+ decoded = []
462
+ for i in range(latents.shape[0]):
463
+ decoded.append(self.decoder(latents[i:i+1], **kwargs))
464
+ decoded = torch.cat(decoded, dim=0)
465
+ else:
466
+ if return_loss:
467
+ decoded, loss = self.decoder(latents, **kwargs)
468
+ else:
469
+ decoded = self.decoder(latents, **kwargs)
470
+ if self.pretransform is not None:
471
+ if self.pretransform.enable_grad:
472
+ if iterate_batch:
473
+ decodeds = []
474
+ for i in range(decoded.shape[0]):
475
+ decodeds.append(self.pretransform.decode(decoded[i:i+1]))
476
+ decoded = torch.cat(decodeds, dim=0)
477
+ else:
478
+ decoded = self.pretransform.decode(decoded)
479
+ else:
480
+ with torch.no_grad():
481
+ if iterate_batch:
482
+ decodeds = []
483
+ for i in range(latents.shape[0]):
484
+ decodeds.append(self.pretransform.decode(decoded[i:i+1]))
485
+ decoded = torch.cat(decodeds, dim=0)
486
+ else:
487
+ decoded = self.pretransform.decode(decoded)
488
+
489
+ if self.soft_clip:
490
+ decoded = torch.tanh(decoded)
491
+ if return_loss:
492
+ return decoded, loss
493
+ else:
494
+ return decoded
495
+
496
+
497
+ def preprocess_audio_for_encoder(self, audio, in_sr):
498
+ '''
499
+ Preprocess single audio tensor (Channels x Length) to be compatible with the encoder.
500
+ If the model is mono, stereo audio will be converted to mono.
501
+ Audio will be silence-padded to be a multiple of the model's downsampling ratio.
502
+ Audio will be resampled to the model's sample rate.
503
+ The output will have batch size 1 and be shape (1 x Channels x Length)
504
+ '''
505
+ return self.preprocess_audio_list_for_encoder([audio], [in_sr])
506
+
507
+ def preprocess_audio_list_for_encoder(self, audio_list, in_sr_list):
508
+ '''
509
+ Preprocess a [list] of audio (Channels x Length) into a batch tensor to be compatable with the encoder.
510
+ The audio in that list can be of different lengths and channels.
511
+ in_sr can be an integer or list. If it's an integer it will be assumed it is the input sample_rate for every audio.
512
+ All audio will be resampled to the model's sample rate.
513
+ Audio will be silence-padded to the longest length, and further padded to be a multiple of the model's downsampling ratio.
514
+ If the model is mono, all audio will be converted to mono.
515
+ The output will be a tensor of shape (Batch x Channels x Length)
516
+ '''
517
+ batch_size = len(audio_list)
518
+ if isinstance(in_sr_list, int):
519
+ in_sr_list = [in_sr_list]*batch_size
520
+ assert len(in_sr_list) == batch_size, "list of sample rates must be the same length of audio_list"
521
+ new_audio = []
522
+ max_length = 0
523
+ # resample & find the max length
524
+ for i in range(batch_size):
525
+ audio = audio_list[i]
526
+ in_sr = in_sr_list[i]
527
+ if len(audio.shape) == 3 and audio.shape[0] == 1:
528
+ # batchsize 1 was given by accident. Just squeeze it.
529
+ audio = audio.squeeze(0)
530
+ elif len(audio.shape) == 1:
531
+ # Mono signal, channel dimension is missing, unsqueeze it in
532
+ audio = audio.unsqueeze(0)
533
+ assert len(audio.shape)==2, "Audio should be shape (Channels x Length) with no batch dimension"
534
+ # Resample audio
535
+ if in_sr != self.sample_rate:
536
+ resample_tf = T.Resample(in_sr, self.sample_rate).to(audio.device)
537
+ audio = resample_tf(audio)
538
+ new_audio.append(audio)
539
+ if audio.shape[-1] > max_length:
540
+ max_length = audio.shape[-1]
541
+ # Pad every audio to the same length, multiple of model's downsampling ratio
542
+ padded_audio_length = max_length + (self.min_length - (max_length % self.min_length)) % self.min_length
543
+ for i in range(batch_size):
544
+ # Pad it & if necessary, mixdown/duplicate stereo/mono channels to support model
545
+ new_audio[i] = prepare_audio(new_audio[i], in_sr=in_sr, target_sr=in_sr, target_length=padded_audio_length,
546
+ target_channels=self.in_channels, device=new_audio[i].device).squeeze(0)
547
+ # convert to tensor
548
+ return torch.stack(new_audio)
549
+
550
+ def encode_audio(self, audio, chunked=False, overlap=32, chunk_size=128, **kwargs):
551
+ '''
552
+ Encode audios into latents. Audios should already be preprocesed by preprocess_audio_for_encoder.
553
+ If chunked is True, split the audio into chunks of a given maximum size chunk_size, with given overlap.
554
+ Overlap and chunk_size params are both measured in number of latents (not audio samples)
555
+ # and therefore you likely could use the same values with decode_audio.
556
+ A overlap of zero will cause discontinuity artefacts. Overlap should be => receptive field size.
557
+ Every autoencoder will have a different receptive field size, and thus ideal overlap.
558
+ You can determine it empirically by diffing unchunked vs chunked output and looking at maximum diff.
559
+ The final chunk may have a longer overlap in order to keep chunk_size consistent for all chunks.
560
+ Smaller chunk_size uses less memory, but more compute.
561
+ The chunk_size vs memory tradeoff isn't linear, and possibly depends on the GPU and CUDA version
562
+ For example, on a A6000 chunk_size 128 is overall faster than 256 and 512 even though it has more chunks
563
+ '''
564
+ samples_per_latent = int(self.downsampling_ratio)
565
+ if not chunked or audio.shape[-1] < chunk_size * samples_per_latent:
566
+ return self.encode(audio, **kwargs)
567
+
568
+ # chunk_size/overlap are in latent units; scale to samples for slicing.
569
+ chunk_size_samples = chunk_size * samples_per_latent
570
+ hop_samples = (chunk_size - overlap) * samples_per_latent
571
+ total_samples = audio.shape[-1]
572
+
573
+ # Anchor the final chunk to the signal end (may overlap more than `overlap`).
574
+ chunk_starts = list(range(0, total_samples - chunk_size_samples + 1, hop_samples))
575
+ if chunk_starts[-1] != total_samples - chunk_size_samples:
576
+ chunk_starts.append(total_samples - chunk_size_samples)
577
+
578
+ encoded_chunks = [self.encode(audio[..., s:s + chunk_size_samples]) for s in chunk_starts]
579
+
580
+ total_latents = total_samples // samples_per_latent
581
+ half_overlap_latents = overlap // 2
582
+ output = audio.new_zeros(*encoded_chunks[0].shape[:-1], total_latents)
583
+ num_chunks = len(chunk_starts)
584
+
585
+ # Trim half the overlap off inner edges (edges facing a neighbour chunk).
586
+ for i, (start_sample, chunk) in enumerate(zip(chunk_starts, encoded_chunks)):
587
+ is_first = i == 0
588
+ is_last = i == num_chunks - 1
589
+ out_start = (total_latents - chunk_size) if is_last else (start_sample // samples_per_latent)
590
+ left = 0 if is_first else half_overlap_latents
591
+ right = chunk_size if is_last else chunk_size - half_overlap_latents
592
+ output[..., out_start + left : out_start + right] = chunk[..., left:right]
593
+
594
+ return output
595
+
596
+ def decode_audio(self, latents, chunked=False, overlap=32, chunk_size=128, **kwargs):
597
+ '''
598
+ Decode latents to audio.
599
+ If chunked is True, split the latents into chunks of a given maximum size chunk_size, with given overlap, both of which are measured in number of latents.
600
+ A overlap of zero will cause discontinuity artefacts. Overlap should be => receptive field size.
601
+ Every autoencoder will have a different receptive field size, and thus ideal overlap.
602
+ You can determine it empirically by diffing unchunked vs chunked audio and looking at maximum diff.
603
+ The final chunk may have a longer overlap in order to keep chunk_size consistent for all chunks.
604
+ Smaller chunk_size uses less memory, but more compute.
605
+ The chunk_size vs memory tradeoff isn't linear, and possibly depends on the GPU and CUDA version
606
+ For example, on a A6000 chunk_size 128 is overall faster than 256 and 512 even though it has more chunks
607
+ '''
608
+ if not chunked or latents.shape[-1] < chunk_size:
609
+ return self.decode(latents, **kwargs)
610
+
611
+ # chunk_size/overlap are in latent units — same as `latents`, no scaling.
612
+ samples_per_latent = int(self.downsampling_ratio)
613
+ hop_latents = chunk_size - overlap
614
+ total_latents = latents.shape[-1]
615
+
616
+ # Anchor the final chunk to the signal end (may overlap more than `overlap`).
617
+ chunk_starts = list(range(0, total_latents - chunk_size + 1, hop_latents))
618
+ if chunk_starts[-1] != total_latents - chunk_size:
619
+ chunk_starts.append(total_latents - chunk_size)
620
+
621
+ decoded_chunks = [self.decode(latents[..., s:s + chunk_size]) for s in chunk_starts]
622
+
623
+ total_samples = total_latents * samples_per_latent
624
+ chunk_size_samples = chunk_size * samples_per_latent
625
+ half_overlap_samples = (overlap // 2) * samples_per_latent
626
+ output = latents.new_zeros(*decoded_chunks[0].shape[:-1], total_samples)
627
+ num_chunks = len(chunk_starts)
628
+
629
+ # Trim half the overlap off inner edges (edges facing a neighbour chunk).
630
+ for i, (start_latent, chunk) in enumerate(zip(chunk_starts, decoded_chunks)):
631
+ is_first = i == 0
632
+ is_last = i == num_chunks - 1
633
+ out_start = (total_samples - chunk_size_samples) if is_last else (start_latent * samples_per_latent)
634
+ left = 0 if is_first else half_overlap_samples
635
+ right = chunk_size_samples if is_last else chunk_size_samples - half_overlap_samples
636
+ output[..., out_start + left : out_start + right] = chunk[..., left:right]
637
+
638
+ return output
639
+
stable_audio_3/models/blocks.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import torch
3
+ from torch import nn
4
+ from torch.nn.utils import weight_norm
5
+
6
+
7
+ def get_activation(activation, channels=None) -> nn.Module:
8
+ if activation == "elu":
9
+ return nn.ELU()
10
+ elif activation == "none":
11
+ return nn.Identity()
12
+ else:
13
+ raise ValueError(f"Unknown activation {activation}")
14
+
15
+ def WNConv1d(*args, **kwargs):
16
+ return weight_norm(nn.Conv1d(*args, **kwargs))
17
+
18
+ class ResidualUnit(nn.Module):
19
+ def __init__(self, in_channels, out_channels, dilation, depthwise=False, bias=True):
20
+ super().__init__()
21
+
22
+ self.dilation = dilation
23
+
24
+ padding = (dilation * (7-1)) // 2
25
+
26
+ self.layers = nn.Sequential(
27
+ get_activation("elu"),
28
+ WNConv1d(in_channels=in_channels, out_channels=out_channels,
29
+ kernel_size=7, dilation=dilation, padding=padding, groups=1 if not depthwise else out_channels, bias=bias),
30
+ get_activation("elu"),
31
+ WNConv1d(in_channels=out_channels, out_channels=out_channels,
32
+ kernel_size=1, bias=bias)
33
+ )
34
+
35
+ def forward(self, x):
36
+ res = x
37
+ x = self.layers(x)
38
+ return x + res
39
+
40
+ class FourierFeatures(nn.Module):
41
+ def __init__(self, in_features, out_features, std=16.):
42
+ super().__init__()
43
+ assert out_features % 2 == 0
44
+ self.register_buffer('weight', torch.randn([out_features // 2, in_features]) * std)
45
+
46
+ def forward(self, input):
47
+ f = 2 * math.pi * input @ self.weight.T
48
+ return torch.cat([f.cos(), f.sin()], dim=-1)
49
+
50
+ class ExpoFourierFeatures(nn.Module):
51
+ def __init__(self, dim, min_freq=0.5, max_freq=10000.0):
52
+ super().__init__()
53
+ self.dim = dim
54
+ self.min_freq = min_freq
55
+ self.max_freq = max_freq
56
+
57
+ @torch.amp.autocast("cuda",enabled=False)
58
+ def forward(self, t):
59
+ """
60
+ t: [B] tensor.
61
+ """
62
+ in_dtype = t.dtype
63
+ t = t.float()
64
+
65
+ if t.dim() == 1:
66
+ t = t.unsqueeze(-1)
67
+
68
+ half_dim = self.dim // 2
69
+
70
+ # Calculate frequencies (safely in FP32)
71
+ ramp = torch.linspace(0, 1, half_dim, device=t.device, dtype=torch.float32)
72
+ log_min = math.log(self.min_freq)
73
+ log_max = math.log(self.max_freq)
74
+
75
+ freqs = torch.exp(ramp * (log_max - log_min) + log_min)
76
+
77
+ # Calculate arguments (safely in FP32)
78
+ args = t * freqs * 2 * math.pi
79
+
80
+ embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
81
+
82
+ return embedding.to(in_dtype)
83
+
84
+
stable_audio_3/models/bottleneck.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+
4
+ class SoftNormBottleneck(nn.Module):
5
+ def __init__(self, dim = 32, noise_augment_dim=0, noise_regularize = False, auto_scale = False, freeze = False):
6
+ super().__init__()
7
+
8
+ self.noise_augment_dim = noise_augment_dim
9
+ self.scaling_factor = nn.Parameter(torch.ones(1,dim,1))
10
+ self.bias = nn.Parameter(torch.zeros(1,dim,1))
11
+ self.noise_scaling_factor = nn.Parameter(torch.ones(1,noise_augment_dim,1))
12
+ self.noise_regularize = noise_regularize
13
+ self.freeze = freeze
14
+ if self.freeze:
15
+ self.scaling_factor.requires_grad = False
16
+ self.bias.requires_grad = False
17
+ self.noise_scaling_factor.requires_grad = False
18
+ if auto_scale:
19
+ running_std = torch.ones(1)
20
+ self.register_parameter("running_std", nn.Parameter(running_std, requires_grad=False))
21
+
22
+ def encode(self, x, return_info=False, **kwargs):
23
+ info = {}
24
+
25
+ x = x * self.scaling_factor + self.bias
26
+
27
+ if self.training and hasattr(self,"running_std") and not self.freeze:
28
+ # Update running std
29
+ self.running_std.data = (self.running_std.data * 0.999 + x.std().detach() * 0.001).clamp(min = 1e-4)
30
+
31
+ if hasattr(self, "running_std"):
32
+ x = x / self.running_std
33
+
34
+ if self.training and return_info:
35
+ var = (x.std(dim=-1) ** 2).clip(min = 1e-4)
36
+ logvar = torch.log(var)
37
+ mean = x.mean(dim=-1)
38
+ loss = (mean * mean + var - logvar - 1).mean()
39
+ var = (x.std(dim=-2) ** 2).clip(min = 1e-4)
40
+ logvar = torch.log(var)
41
+ mean = x.mean(dim=-2)
42
+ loss = loss + 0.4 * (mean * mean + var - logvar - 1).mean()
43
+ info["softnorm_loss"] = loss
44
+
45
+ if return_info:
46
+ return x, info
47
+
48
+ return x
49
+
50
+ def decode(self, x, **kwargs):
51
+ if hasattr(self, "running_std"):
52
+ x = x * self.running_std
53
+
54
+ if self.noise_regularize:
55
+ if hasattr(self, "running_std"):
56
+ scaling = self.running_std
57
+ else:
58
+ scaling = x.std(dim = -1).unsqueeze(-1)
59
+ if self.training:
60
+ scale = 5e-2
61
+ else:
62
+ scale = 1e-3
63
+ noise = torch.randn_like(x) * scaling * scale
64
+ x = x + noise
65
+
66
+ if self.noise_augment_dim > 0:
67
+ noise = self.noise_scaling_factor * torch.randn(x.shape[0], self.noise_augment_dim,
68
+ x.shape[-1]).type_as(x)
69
+ x = torch.cat([x, noise], dim=1)
70
+
71
+ return x
stable_audio_3/models/conditioners.py ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #Heavily influenced by https://github.com/facebookresearch/audiocraft/blob/main/audiocraft/modules/conditioners.py
2
+
3
+ import torch
4
+ import logging, warnings
5
+ import typing as tp
6
+ from enum import Enum
7
+ from math import pi
8
+ from typing import List, Union
9
+ from torch import Tensor, nn
10
+ from einops import rearrange
11
+ from .blocks import ExpoFourierFeatures
12
+ from .utils import enable_torch_compile
13
+ import os
14
+
15
+ class PaddingMode(str, Enum):
16
+ """Enum for handling padding in text conditioner embeddings."""
17
+ NONE = "none" # No padding handling (raw embeddings with pad token)
18
+ ZERO = "zero" # Zero out padding positions (default)
19
+ LEARNED = "learned" # Use learned padding embedding
20
+
21
+ class Conditioner(nn.Module):
22
+ def __init__(
23
+ self,
24
+ dim: int,
25
+ output_dim: int,
26
+ project_out: bool = False,
27
+ padding_mode: str = "zero"
28
+ ):
29
+
30
+ super().__init__()
31
+
32
+ self.dim = dim
33
+ self.output_dim = output_dim
34
+ self.padding_mode = padding_mode
35
+ self.proj_out = nn.Linear(dim, output_dim) if (dim != output_dim or project_out) else nn.Identity()
36
+
37
+ # Learned padding embedding (only created if needed)
38
+ if padding_mode == "learned" or padding_mode == PaddingMode.LEARNED:
39
+ self.padding_embedding = nn.Parameter(torch.randn(output_dim) * 0.02)
40
+
41
+ def apply_padding(self, embeddings: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
42
+ """
43
+ Apply padding handling based on padding_mode.
44
+
45
+ Args:
46
+ embeddings: [batch, seq_len, dim] - the embeddings to process
47
+ attention_mask: [batch, seq_len] bool/int, True/1 = valid token
48
+
49
+ Returns:
50
+ embeddings with padding handled according to mode
51
+ """
52
+ mode = self.padding_mode
53
+ if isinstance(mode, str):
54
+ mode = PaddingMode(mode)
55
+
56
+ if mode == PaddingMode.NONE:
57
+ return embeddings
58
+ elif mode == PaddingMode.ZERO:
59
+ return embeddings * attention_mask.unsqueeze(-1).float()
60
+ elif mode == PaddingMode.LEARNED:
61
+ mask_expanded = attention_mask.unsqueeze(-1).bool()
62
+ return torch.where(
63
+ mask_expanded,
64
+ embeddings,
65
+ self.padding_embedding.unsqueeze(0).unsqueeze(0).expand_as(embeddings)
66
+ )
67
+ else:
68
+ raise ValueError(f"Unknown padding mode: {mode}")
69
+
70
+ def forward(self, x: tp.Any) -> tp.Any:
71
+ raise NotImplementedError()
72
+
73
+ class LearnedPositionalEmbedding(nn.Module):
74
+ def __init__(self, dim: int, std: float = 16.0):
75
+ super().__init__()
76
+ assert (dim % 2) == 0
77
+ half_dim = dim // 2
78
+ self.weights = nn.Parameter(torch.randn(half_dim) * std)
79
+
80
+ def forward(self, x: Tensor) -> Tensor:
81
+ x = rearrange(x, "b -> b 1")
82
+ freqs = x * rearrange(self.weights, "d -> 1 d") * 2 * pi
83
+ fouriered = torch.cat((freqs.sin(), freqs.cos()), dim=-1)
84
+ fouriered = torch.cat((x, fouriered), dim=-1)
85
+ return fouriered
86
+
87
+
88
+ def TimePositionalEmbedding(dim: int, out_features: int) -> nn.Module:
89
+ return nn.Sequential(
90
+ LearnedPositionalEmbedding(dim),
91
+ nn.Linear(in_features=dim + 1, out_features=out_features),
92
+ )
93
+
94
+
95
+ class NumberEmbedder(nn.Module):
96
+ def __init__(
97
+ self,
98
+ features: int,
99
+ dim: int = 256,
100
+ fourier_features_type: tp.Literal["learned", "expo"] = "learned"
101
+ ):
102
+ super().__init__()
103
+ self.features = features
104
+ if fourier_features_type == "expo":
105
+ self.embedding = nn.Sequential(ExpoFourierFeatures(dim=dim), nn.Linear(in_features=dim, out_features=features))
106
+ else:
107
+ self.embedding = TimePositionalEmbedding(dim=dim, out_features=features)
108
+
109
+ def forward(self, x: Union[List[float], Tensor]) -> Tensor:
110
+ if not torch.is_tensor(x):
111
+ device = next(self.embedding.parameters()).device
112
+ x = torch.tensor(x, device=device)
113
+ assert isinstance(x, Tensor)
114
+ shape = x.shape
115
+ x = rearrange(x, "... -> (...)")
116
+ embedding = self.embedding(x)
117
+ x = embedding.view(*shape, self.features)
118
+ return x # type: ignore
119
+
120
+
121
+ class NumberConditioner(Conditioner):
122
+ '''
123
+ Conditioner that takes a list of floats, normalizes them for a given range, and returns a list of embeddings
124
+ '''
125
+ def __init__(self,
126
+ output_dim: int,
127
+ min_val: float=0,
128
+ max_val: float=1,
129
+ fourier_features_type : tp.Literal["learned", "expo"] = "learned"
130
+ ):
131
+ super().__init__(output_dim, output_dim)
132
+
133
+ self.min_val = min_val
134
+ self.max_val = max_val
135
+
136
+ self.embedder = NumberEmbedder(features=output_dim, fourier_features_type=fourier_features_type)
137
+
138
+ def forward(self, floats: tp.List[float], device=None) -> tp.Any:
139
+ self.embedder.to(device)
140
+ # Cast the inputs to floats
141
+ floats = [float(x) for x in floats]
142
+
143
+ floats = torch.tensor(floats).to(device)
144
+
145
+ floats = floats.clamp(self.min_val, self.max_val)
146
+
147
+ normalized_floats = (floats - self.min_val) / (self.max_val - self.min_val)
148
+
149
+ # Cast floats to same type as embedder
150
+ embedder_dtype = next(self.embedder.parameters()).dtype
151
+ normalized_floats = normalized_floats.to(embedder_dtype)
152
+
153
+ float_embeds = self.embedder(normalized_floats).unsqueeze(1)
154
+
155
+ return [float_embeds, torch.ones(float_embeds.shape[0], 1).to(device)]
156
+
157
+ class T5GemmaConditioner(Conditioner):
158
+
159
+ T5GEMMA_MODELS = ["google/t5gemma-b-b-ul2"]
160
+
161
+ T5GEMMA_MODEL_DIMS = {
162
+ "google/t5gemma-b-b-ul2": 768,
163
+ }
164
+
165
+ def __init__(
166
+ self,
167
+ output_dim: int,
168
+ model_name: str = "google/t5gemma-b-b-ul2",
169
+ max_length: str = 128,
170
+ enable_grad: bool = False,
171
+ project_out: bool = False,
172
+ padding_mode: str = "zero",
173
+ model_path: str = None,
174
+ repo_id: str = None,
175
+ subfolder: str = None,
176
+ ):
177
+ assert model_name in self.T5GEMMA_MODELS, f"Unknown T5 model name: {model_name}"
178
+ super().__init__(self.T5GEMMA_MODEL_DIMS[model_name], output_dim, project_out=project_out, padding_mode=padding_mode)
179
+
180
+ load_from = model_path or repo_id or model_name
181
+
182
+ self.max_length = max_length
183
+ self.enable_grad = enable_grad
184
+
185
+ # Set environment variables to disable progress bars BEFORE importing transformers
186
+ # This is the most reliable way to suppress HuggingFace progress bars
187
+ prev_hf_hub = os.environ.get("HF_HUB_DISABLE_PROGRESS_BARS")
188
+ prev_transformers = os.environ.get("TRANSFORMERS_VERBOSITY")
189
+ os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = "1"
190
+ os.environ["TRANSFORMERS_VERBOSITY"] = "error"
191
+
192
+ # Suppress logging from transformers
193
+ previous_level = logging.root.manager.disable
194
+ logging.disable(logging.ERROR)
195
+
196
+ with warnings.catch_warnings():
197
+ warnings.simplefilter("ignore")
198
+ try:
199
+ from transformers import T5GemmaEncoderModel, AutoTokenizer, AutoConfig
200
+ logging.info(f"Loading T5Gemma tokenizer and model from: {load_from}")
201
+ hf_kwargs = {"subfolder": subfolder} if subfolder else {}
202
+ self.tokenizer = AutoTokenizer.from_pretrained(load_from, **hf_kwargs)
203
+ config = AutoConfig.from_pretrained(load_from, **hf_kwargs)
204
+ config.is_encoder_decoder = False
205
+ model = T5GemmaEncoderModel.from_pretrained(load_from, config=config, **hf_kwargs).train(enable_grad).requires_grad_(enable_grad)
206
+
207
+ finally:
208
+ logging.disable(previous_level)
209
+ # Restore environment variables
210
+ if prev_hf_hub is None:
211
+ os.environ.pop("HF_HUB_DISABLE_PROGRESS_BARS", None)
212
+ else:
213
+ os.environ["HF_HUB_DISABLE_PROGRESS_BARS"] = prev_hf_hub
214
+ if prev_transformers is None:
215
+ os.environ.pop("TRANSFORMERS_VERBOSITY", None)
216
+ else:
217
+ os.environ["TRANSFORMERS_VERBOSITY"] = prev_transformers
218
+
219
+ # Compile the model to reduce CPU-GPU kernel launch overhead,
220
+ # which is sensitive to CPU contention from DataLoader workers
221
+ if enable_torch_compile:
222
+ model = torch.compile(model)
223
+
224
+ if self.enable_grad:
225
+ self.model = model
226
+ else:
227
+ self.__dict__["model"] = model
228
+
229
+ self._device_initialized = False
230
+
231
+ def forward(self, inputs: tp.Union[tp.List[str], tp.List[tp.Dict[str, torch.Tensor]]], device: tp.Union[torch.device, str]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
232
+
233
+ # Only move to device once (avoid overhead on every forward call)
234
+ if not self._device_initialized:
235
+ self.model.to(device)
236
+ self.proj_out.to(device)
237
+ self.model.eval()
238
+ self._device_initialized = True
239
+
240
+ # Handle pre-tokenized inputs (dicts with input_ids/attention_mask from DataLoader workers)
241
+ # or raw strings (from demo generation / inference)
242
+ if isinstance(inputs[0], dict):
243
+ input_ids = torch.stack([x["input_ids"] for x in inputs]).to(device, non_blocking=True)
244
+ attention_mask = torch.stack([x["attention_mask"] for x in inputs]).to(device, non_blocking=True).to(torch.bool)
245
+ else:
246
+ encoded = self.tokenizer(
247
+ inputs,
248
+ truncation=True,
249
+ max_length=self.max_length,
250
+ padding="max_length",
251
+ return_tensors="pt",
252
+ )
253
+ input_ids = encoded["input_ids"].to(device, non_blocking=True)
254
+ attention_mask = encoded["attention_mask"].to(device, non_blocking=True).to(torch.bool)
255
+
256
+ with torch.no_grad():
257
+ embeddings = self.model(
258
+ input_ids=input_ids, attention_mask=attention_mask
259
+ )["last_hidden_state"]
260
+
261
+ # Cast embeddings to same type as proj_out, unless proj_out is Identity
262
+ if not isinstance(self.proj_out, nn.Identity):
263
+ proj_out_dtype = next(self.proj_out.parameters()).dtype
264
+ embeddings = embeddings.to(proj_out_dtype)
265
+
266
+ embeddings = self.proj_out(embeddings)
267
+ embeddings = self.apply_padding(embeddings, attention_mask)
268
+
269
+ return embeddings, attention_mask
270
+
271
+
272
+ class MultiConditioner(nn.Module):
273
+ """
274
+ A module that applies multiple conditioners to an input dictionary based on the keys
275
+
276
+ Args:
277
+ conditioners: a dictionary of conditioners with keys corresponding to the keys of the conditioning input dictionary (e.g. "prompt")
278
+ default_keys: a dictionary of default keys to use if the key is not in the input dictionary (e.g. {"prompt_t5": "prompt"})
279
+ """
280
+ def __init__(self, conditioners: tp.Dict[str, Conditioner], default_keys: tp.Dict[str, str] = {}, pre_encoded_keys: tp.List[str] = []):
281
+ super().__init__()
282
+
283
+ self.conditioners = nn.ModuleDict(conditioners)
284
+ self.default_keys = default_keys
285
+ self.pre_encoded_keys = pre_encoded_keys
286
+
287
+ def forward(self, batch_metadata: tp.List[tp.Dict[str, tp.Any]], device: tp.Union[torch.device, str]) -> tp.Dict[str, tp.Any]:
288
+ output = {}
289
+
290
+ for key, conditioner in self.conditioners.items():
291
+ condition_key = key
292
+
293
+ conditioner_inputs = []
294
+
295
+ for x in batch_metadata:
296
+
297
+ if condition_key not in x:
298
+ if condition_key in self.default_keys:
299
+ condition_key = self.default_keys[condition_key]
300
+ else:
301
+ raise ValueError(f"Conditioner key {condition_key} not found in batch metadata")
302
+
303
+ #Unwrap the condition info if it's a single-element list or tuple, this is to support collation functions that wrap everything in a list
304
+ if isinstance(x[condition_key], list) or isinstance(x[condition_key], tuple) and len(x[condition_key]) == 1:
305
+ conditioner_input = x[condition_key][0]
306
+
307
+ else:
308
+ conditioner_input = x[condition_key]
309
+
310
+ conditioner_inputs.append(conditioner_input)
311
+
312
+ if key in self.pre_encoded_keys:
313
+ output[key] = [torch.stack(conditioner_inputs, dim=0).to(device), None]
314
+ else:
315
+ output[key] = conditioner(conditioner_inputs, device)
316
+
317
+ return output
318
+
stable_audio_3/models/diffusion.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import nn
3
+ import typing as tp
4
+
5
+ from .conditioners import MultiConditioner
6
+ from .dit import DiffusionTransformer
7
+ from stable_audio_3.inference.distribution_shift import FluxDistributionShift, DistributionShift, LogSNRShift, IdentityDistributionShift
8
+
9
+ from time import time
10
+
11
+ class Profiler:
12
+
13
+ def __init__(self):
14
+ self.ticks = [[time(), None]]
15
+
16
+ def tick(self, msg):
17
+ self.ticks.append([time(), msg])
18
+
19
+ def __repr__(self):
20
+ rep = 80 * "=" + "\n"
21
+ for i in range(1, len(self.ticks)):
22
+ msg = self.ticks[i][1]
23
+ ellapsed = self.ticks[i][0] - self.ticks[i - 1][0]
24
+ rep += msg + f": {ellapsed*1000:.2f}ms\n"
25
+ rep += 80 * "=" + "\n\n\n"
26
+ return rep
27
+
28
+ class ConditionedDiffusionModelWrapper(nn.Module):
29
+ """
30
+ A diffusion model that takes in conditioning
31
+ """
32
+ def __init__(
33
+ self,
34
+ model: nn.Module,
35
+ conditioner: MultiConditioner,
36
+ io_channels,
37
+ sample_rate,
38
+ min_input_length: int,
39
+ diffusion_objective: tp.Literal["v", "rectified_flow", "rf_denoiser"] = "v",
40
+ distribution_shift_options = None,
41
+ sampling_distribution_shift_options = None,
42
+ mask_padding_attention: bool = False,
43
+ use_effective_length_for_schedule: bool = False,
44
+ pretransform: tp.Optional[nn.Module] = None,
45
+ cross_attn_cond_ids: tp.List[str] = [],
46
+ global_cond_ids: tp.List[str] = [],
47
+ input_concat_ids: tp.List[str] = [],
48
+ local_add_cond_ids: tp.List[str] = [],
49
+ modular_local_cond_ids: tp.List[str] = [],
50
+ prepend_cond_ids: tp.List[str] = [],
51
+ ):
52
+ super().__init__()
53
+
54
+ self.model = model
55
+ self.conditioner = conditioner
56
+ self.io_channels = io_channels
57
+ self.sample_rate = sample_rate
58
+ self.diffusion_objective = diffusion_objective
59
+ self.pretransform = pretransform
60
+ self.cross_attn_cond_ids = cross_attn_cond_ids
61
+ self.global_cond_ids = global_cond_ids
62
+ self.input_concat_ids = input_concat_ids
63
+ self.local_add_cond_ids = local_add_cond_ids
64
+ self.modular_local_cond_ids = modular_local_cond_ids
65
+ self.prepend_cond_ids = prepend_cond_ids
66
+ self.min_input_length = min_input_length
67
+ self.mask_padding_attention = mask_padding_attention
68
+ self.use_effective_length_for_schedule = use_effective_length_for_schedule
69
+
70
+ self.dist_shift = None
71
+ if distribution_shift_options is not None:
72
+ self.dist_shift = self._create_dist_shift(distribution_shift_options)
73
+
74
+ # Sampling dist_shift: separate config for inference-time schedule
75
+ if sampling_distribution_shift_options is not None:
76
+ self.sampling_dist_shift = self._create_dist_shift(sampling_distribution_shift_options)
77
+ else:
78
+ # Default: seq_len-invariant LogSNR shift matching legacy log_snr_sampling=True
79
+ self.sampling_dist_shift = LogSNRShift(rate=0, anchor_logsnr=-6.2, logsnr_end=2.0)
80
+
81
+ @staticmethod
82
+ def _create_dist_shift(options: dict):
83
+ """Create a distribution shift object from config options."""
84
+ dist_shift_type = options.get("type", "full")
85
+ dist_shift_kwargs = {k: v for k, v in options.items() if k != "type"}
86
+ if dist_shift_type == "none":
87
+ return IdentityDistributionShift()
88
+ elif dist_shift_type == "flux":
89
+ return FluxDistributionShift(**dist_shift_kwargs)
90
+ elif dist_shift_type == "full":
91
+ return DistributionShift(**dist_shift_kwargs)
92
+ elif dist_shift_type == "logsnr":
93
+ return LogSNRShift(**dist_shift_kwargs)
94
+ else:
95
+ raise ValueError(f"Unknown distribution shift type: {dist_shift_type}. Expected 'none', 'flux', 'full', or 'logsnr'.")
96
+
97
+ def get_conditioning_inputs(self, conditioning_tensors: tp.Dict[str, tp.Any], negative=False):
98
+ cross_attention_input = None
99
+ cross_attention_masks = None
100
+ global_cond = None
101
+ input_concat_cond = None
102
+ prepend_cond = None
103
+ prepend_cond_mask = None
104
+ local_add_cond = None
105
+ modular_local_cond = None
106
+
107
+ if len(self.cross_attn_cond_ids) > 0:
108
+ # Concatenate all cross-attention inputs over the sequence dimension
109
+ # Assumes that the cross-attention inputs are of shape (batch, seq, channels)
110
+ cross_attention_input = []
111
+ cross_attention_masks = []
112
+
113
+ for key in self.cross_attn_cond_ids:
114
+ cross_attn_in, cross_attn_mask = conditioning_tensors[key]
115
+
116
+ # Add sequence dimension if it's not there
117
+ if len(cross_attn_in.shape) == 2:
118
+ cross_attn_in = cross_attn_in.unsqueeze(1)
119
+ cross_attn_mask = cross_attn_mask.unsqueeze(1)
120
+
121
+ cross_attention_input.append(cross_attn_in)
122
+ cross_attention_masks.append(cross_attn_mask)
123
+
124
+ cross_attention_input = torch.cat(cross_attention_input, dim=1)
125
+ cross_attention_masks = torch.cat(cross_attention_masks, dim=1)
126
+
127
+ if len(self.global_cond_ids) > 0:
128
+ # Concatenate all global conditioning inputs over the channel dimension
129
+ # Assumes that the global conditioning inputs are of shape (batch, channels)
130
+ global_conds = []
131
+ for key in self.global_cond_ids:
132
+ global_cond_input = conditioning_tensors[key][0]
133
+
134
+ global_conds.append(global_cond_input)
135
+
136
+ # Concatenate over the channel dimension
137
+ global_cond = torch.cat(global_conds, dim=-1)
138
+
139
+ if len(global_cond.shape) == 3:
140
+ global_cond = global_cond.squeeze(1)
141
+
142
+ if len(self.input_concat_ids) > 0:
143
+ # Concatenate all input concat conditioning inputs over the channel dimension
144
+ # Assumes that the input concat conditioning inputs are of shape (batch, channels, seq)
145
+ input_concat_cond = torch.cat([conditioning_tensors[key][0] for key in self.input_concat_ids], dim=1)
146
+
147
+ if len(self.local_add_cond_ids) > 0:
148
+ # Concatenate all local conditioning inputs over the channel dimension
149
+ # Assumes that the local conditioning inputs are of shape (batch, channels, seq)
150
+ local_add_cond = torch.cat([conditioning_tensors[key][0] for key in self.local_add_cond_ids], dim=1)
151
+
152
+ if len(self.modular_local_cond_ids) > 0:
153
+ # Keep modular local conditioning as a dict of tensors (not concatenated)
154
+ # Each tensor is of shape (batch, channels, seq)
155
+ modular_local_cond = {}
156
+ for key in self.modular_local_cond_ids:
157
+ if key in conditioning_tensors:
158
+ modular_local_cond[key] = conditioning_tensors[key][0]
159
+ # Only set if we have any conditioning
160
+ if len(modular_local_cond) == 0:
161
+ modular_local_cond = None
162
+
163
+ if len(self.prepend_cond_ids) > 0:
164
+ # Concatenate all prepend conditioning inputs over the sequence dimension
165
+ # Assumes that the prepend conditioning inputs are of shape (batch, seq, channels)
166
+ prepend_conds = []
167
+ prepend_cond_masks = []
168
+
169
+ for key in self.prepend_cond_ids:
170
+ prepend_cond_input, prepend_cond_mask = conditioning_tensors[key]
171
+ prepend_conds.append(prepend_cond_input)
172
+ prepend_cond_masks.append(prepend_cond_mask)
173
+
174
+ prepend_cond = torch.cat(prepend_conds, dim=1)
175
+ prepend_cond_mask = torch.cat(prepend_cond_masks, dim=1)
176
+
177
+ if negative:
178
+ return {
179
+ "negative_cross_attn_cond": cross_attention_input,
180
+ "negative_cross_attn_mask": cross_attention_masks,
181
+ "negative_global_cond": global_cond,
182
+ "negative_input_concat_cond": input_concat_cond
183
+ }
184
+ else:
185
+ return {
186
+ "cross_attn_cond": cross_attention_input,
187
+ "cross_attn_mask": cross_attention_masks,
188
+ "global_cond": global_cond,
189
+ "input_concat_cond": input_concat_cond,
190
+ "local_add_cond": local_add_cond,
191
+ "modular_local_cond": modular_local_cond,
192
+ "prepend_cond": prepend_cond,
193
+ "prepend_cond_mask": prepend_cond_mask
194
+ }
195
+
196
+ def forward(self, x: torch.Tensor, t: torch.Tensor, cond: tp.Dict[str, tp.Any], **kwargs):
197
+ return self.model(x, t, **self.get_conditioning_inputs(cond), **kwargs)
198
+
199
+
200
+ class DiTWrapper(nn.Module):
201
+ def __init__(
202
+ self,
203
+ diffusion_objective: str,
204
+ *args,
205
+ **kwargs
206
+ ):
207
+ super().__init__()
208
+
209
+ self.diffusion_objective = diffusion_objective
210
+
211
+ self.model = DiffusionTransformer(diffusion_objective=diffusion_objective, *args, **kwargs)
212
+
213
+ def forward(self,
214
+ x,
215
+ t,
216
+ cross_attn_cond=None,
217
+ cross_attn_mask=None,
218
+ negative_cross_attn_cond=None,
219
+ negative_cross_attn_mask=None,
220
+ input_concat_cond=None,
221
+ local_add_cond=None,
222
+ negative_input_concat_cond=None,
223
+ global_cond=None,
224
+ negative_global_cond=None,
225
+ prepend_cond=None,
226
+ prepend_cond_mask=None,
227
+ cfg_scale=1.0,
228
+ cfg_dropout_prob: float = 0.0,
229
+ batch_cfg: bool = True,
230
+ rescale_cfg: bool = False,
231
+ scale_phi: float = 0.0,
232
+ **kwargs):
233
+
234
+ assert batch_cfg, "batch_cfg must be True for DiTWrapper"
235
+ #assert negative_input_concat_cond is None, "negative_input_concat_cond is not supported for DiTWrapper"
236
+
237
+ return self.model(
238
+ x,
239
+ t,
240
+ cross_attn_cond=cross_attn_cond,
241
+ cross_attn_cond_mask=cross_attn_mask,
242
+ negative_cross_attn_cond=negative_cross_attn_cond,
243
+ negative_cross_attn_mask=negative_cross_attn_mask,
244
+ input_concat_cond=input_concat_cond,
245
+ prepend_cond=prepend_cond,
246
+ prepend_cond_mask=prepend_cond_mask,
247
+ cfg_scale=cfg_scale,
248
+ cfg_dropout_prob=cfg_dropout_prob,
249
+ scale_phi=scale_phi,
250
+ global_embed=global_cond,
251
+ local_add_cond=local_add_cond,
252
+ **kwargs)
253
+
stable_audio_3/models/dit.py ADDED
@@ -0,0 +1,643 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import typing as tp
2
+ import math
3
+ import torch
4
+
5
+ from einops import rearrange
6
+ from torch import nn
7
+ from torch.nn import functional as F
8
+
9
+ from .blocks import FourierFeatures, ExpoFourierFeatures
10
+ from .transformer import ContinuousTransformer
11
+ from .lora import LoRAParametrization, set_lora_strength, has_lora, enable_lora, disable_lora, filter_lora_layers
12
+
13
+ class DiffusionTransformer(nn.Module):
14
+ def __init__(self,
15
+ io_channels=32,
16
+ patch_size=1,
17
+ embed_dim=768,
18
+ cond_token_dim=0,
19
+ project_cond_tokens=True,
20
+ global_cond_dim=0,
21
+ project_global_cond=True,
22
+ input_concat_dim=0,
23
+ prepend_cond_dim=0,
24
+ depth=12,
25
+ num_heads=8,
26
+ transformer_type: tp.Literal["continuous_transformer", "mm_transformer"] = "continuous_transformer",
27
+ global_cond_type: tp.Literal["prepend", "adaLN"] = "prepend",
28
+ timestep_cond_type: tp.Literal["global", "input_concat"] = "global",
29
+ timestep_embed_dim=None,
30
+ diffusion_objective: tp.Literal["v", "rectified_flow", "rf_denoiser"] = "v",
31
+ timestep_features_type: tp.Literal["learned", "expo"] = "learned",
32
+ timestep_features_dim = 256,
33
+ timestep_features_logsnr: bool = False,
34
+ modular_local_cond_configs = None,
35
+ **kwargs):
36
+
37
+ super().__init__()
38
+
39
+ self.cond_token_dim = cond_token_dim
40
+
41
+ # Timestep embeddings
42
+ self.timestep_cond_type = timestep_cond_type
43
+ self.timestep_features_logsnr = timestep_features_logsnr
44
+
45
+ timestep_features_dim = timestep_features_dim
46
+
47
+ if timestep_features_type == "expo":
48
+ self.timestep_features = ExpoFourierFeatures(timestep_features_dim, 0.5, 10000.0)
49
+ else:
50
+ self.timestep_features = FourierFeatures(1, timestep_features_dim)
51
+
52
+ if timestep_cond_type == "global":
53
+ timestep_embed_dim = embed_dim
54
+ elif timestep_cond_type == "input_concat":
55
+ assert timestep_embed_dim is not None, "timestep_embed_dim must be specified if timestep_cond_type is input_concat"
56
+ input_concat_dim += timestep_embed_dim
57
+
58
+ self.to_timestep_embed = nn.Sequential(
59
+ nn.Linear(timestep_features_dim, timestep_embed_dim, bias=True),
60
+ nn.SiLU(),
61
+ nn.Linear(timestep_embed_dim, timestep_embed_dim, bias=True),
62
+ )
63
+
64
+ self.diffusion_objective = diffusion_objective
65
+
66
+ if cond_token_dim > 0:
67
+ # Conditioning tokens
68
+
69
+ cond_embed_dim = cond_token_dim if not project_cond_tokens else embed_dim
70
+ self.to_cond_embed = nn.Sequential(
71
+ nn.Linear(cond_token_dim, cond_embed_dim, bias=False),
72
+ nn.SiLU(),
73
+ nn.Linear(cond_embed_dim, cond_embed_dim, bias=False)
74
+ )
75
+ else:
76
+ cond_embed_dim = 0
77
+
78
+ if global_cond_dim > 0:
79
+ # Global conditioning
80
+ global_embed_dim = global_cond_dim if not project_global_cond else embed_dim
81
+ self.to_global_embed = nn.Sequential(
82
+ nn.Linear(global_cond_dim, global_embed_dim, bias=False),
83
+ nn.SiLU(),
84
+ nn.Linear(global_embed_dim, global_embed_dim, bias=False)
85
+ )
86
+
87
+ if prepend_cond_dim > 0:
88
+ # Prepend conditioning
89
+ self.to_prepend_embed = nn.Sequential(
90
+ nn.Linear(prepend_cond_dim, embed_dim, bias=False),
91
+ nn.SiLU(),
92
+ nn.Linear(embed_dim, embed_dim, bias=False)
93
+ )
94
+
95
+ self.input_concat_dim = input_concat_dim
96
+
97
+ dim_in = io_channels + self.input_concat_dim
98
+
99
+ self.patch_size = patch_size
100
+
101
+ # Transformer
102
+
103
+ self.transformer_type = transformer_type
104
+
105
+ self.global_cond_type = global_cond_type
106
+
107
+ transformer_dim_out = io_channels * patch_size
108
+
109
+ if self.transformer_type == "continuous_transformer":
110
+
111
+ global_dim = None
112
+
113
+ if self.global_cond_type == "adaLN":
114
+ # The global conditioning is projected to the embed_dim already at this point
115
+ global_dim = embed_dim
116
+
117
+ self.transformer = ContinuousTransformer(
118
+ dim=embed_dim,
119
+ depth=depth,
120
+ dim_heads=embed_dim // num_heads,
121
+ dim_in=dim_in * patch_size,
122
+ dim_out=transformer_dim_out,
123
+ cross_attend = cond_token_dim > 0,
124
+ cond_token_dim = cond_embed_dim,
125
+ global_cond_dim=global_dim,
126
+ modular_local_cond_configs=modular_local_cond_configs,
127
+ **kwargs
128
+ )
129
+
130
+ else:
131
+ raise ValueError(f"Unknown transformer type: {self.transformer_type}")
132
+
133
+ self.preprocess_conv = nn.Conv1d(dim_in, dim_in, 1, bias=False)
134
+ nn.init.zeros_(self.preprocess_conv.weight)
135
+ self.postprocess_conv = nn.Conv1d(io_channels, io_channels, 1, bias=False)
136
+ nn.init.zeros_(self.postprocess_conv.weight)
137
+
138
+ # Fixed logsnr normalization range: maps logsnr to [0, 1] preserving direction (t=0→0, t=1→1)
139
+ _LOGSNR_MIN = -12.0
140
+ _LOGSNR_MAX = 5.0
141
+ _LOGSNR_RANGE = _LOGSNR_MAX - _LOGSNR_MIN
142
+
143
+ def _t_to_logsnr_cond(self, t: torch.Tensor) -> torch.Tensor:
144
+ """Convert t to normalized logsnr in [0, 1] for timestep conditioning.
145
+
146
+ Maps t through logsnr = log((1-t)/t), clamps to fixed range,
147
+ then normalizes to [0, 1] preserving direction (t=0→0, t=1→1).
148
+ """
149
+ t_clamped = t.float().clamp(1e-7, 1 - 1e-7)
150
+ logsnr = torch.log((1 - t_clamped) / t_clamped)
151
+ logsnr = logsnr.clamp(self._LOGSNR_MIN, self._LOGSNR_MAX)
152
+ return ((self._LOGSNR_MAX - logsnr) / self._LOGSNR_RANGE).to(t.dtype)
153
+
154
+ def _call_transformer(self, x, *, prepend_inputs=None, cross_attn_cond=None,
155
+ mask=None, prepend_mask=None, return_info=False,
156
+ exit_layer_ix=None, local_add_cond=None,
157
+ modular_local_cond=None, padding_mask=None,
158
+ extra_args=None, **kwargs):
159
+ """Helper method to call transformer and handle early exit logic."""
160
+
161
+ output = self.transformer(x, prepend_embeds=prepend_inputs, context=cross_attn_cond,
162
+ return_info=return_info, exit_layer_ix=exit_layer_ix,
163
+ local_add_cond=local_add_cond, modular_local_cond=modular_local_cond,
164
+ padding_mask=padding_mask,
165
+ **(extra_args or {}), **kwargs)
166
+
167
+ if return_info:
168
+ output, info = output
169
+
170
+ # Avoid postprocessing on early exit
171
+ if exit_layer_ix is not None:
172
+ if return_info:
173
+ return output, info
174
+ else:
175
+ return output
176
+
177
+ return (output, info) if return_info and 'info' in locals() else output
178
+
179
+ def _forward(
180
+ self,
181
+ x,
182
+ t,
183
+ mask=None,
184
+ cross_attn_cond=None,
185
+ cross_attn_cond_mask=None,
186
+ input_concat_cond=None,
187
+ local_add_cond=None,
188
+ modular_local_cond=None,
189
+ global_embed=None,
190
+ prepend_cond=None,
191
+ prepend_cond_mask=None,
192
+ padding_mask=None,
193
+ return_info=False,
194
+ exit_layer_ix=None,
195
+ **kwargs):
196
+
197
+ if cross_attn_cond is not None:
198
+ cross_attn_cond = self.to_cond_embed(cross_attn_cond)
199
+
200
+ if global_embed is not None:
201
+ # Project the global conditioning to the embedding dimension
202
+ global_embed = self.to_global_embed(global_embed)
203
+
204
+ prepend_inputs = None
205
+ prepend_mask = None
206
+ prepend_length = 0
207
+ if prepend_cond is not None:
208
+ # Project the prepend conditioning to the embedding dimension
209
+ prepend_cond = self.to_prepend_embed(prepend_cond)
210
+
211
+ prepend_inputs = prepend_cond
212
+ if prepend_cond_mask is not None:
213
+ prepend_mask = prepend_cond_mask
214
+
215
+ prepend_length = prepend_cond.shape[1]
216
+
217
+ if input_concat_cond is not None:
218
+ # Interpolate input_concat_cond to the same length as x
219
+ if input_concat_cond.shape[2] != x.shape[2]:
220
+ input_concat_cond = F.interpolate(input_concat_cond, (x.shape[2], ), mode='nearest')
221
+
222
+ x = torch.cat([x, input_concat_cond], dim=1)
223
+
224
+ if local_add_cond is not None:
225
+ local_add_cond = rearrange(local_add_cond, "b c t -> b t c")
226
+
227
+ # Rearrange modular_local_cond tensors
228
+ if modular_local_cond is not None:
229
+ modular_local_cond = {
230
+ k: rearrange(v, "b c t -> b t c")
231
+ for k, v in modular_local_cond.items()
232
+ }
233
+
234
+ # Get the batch of timestep embeddings
235
+ t_cond = self._t_to_logsnr_cond(t) if self.timestep_features_logsnr else t
236
+ # Convert to model dtype for linear layers (t itself is kept in float32 for precision)
237
+ # x has already been converted to model dtype in the outer forward() method
238
+ t_cond = t_cond.to(x.dtype)
239
+ timestep_embed = self.to_timestep_embed(self.timestep_features(t_cond[:, None])) # (b, embed_dim)
240
+
241
+ # Timestep embedding is considered a global embedding. Add to the global conditioning if it exists
242
+
243
+ if self.timestep_cond_type == "global":
244
+ if global_embed is not None:
245
+ global_embed = global_embed + timestep_embed
246
+ else:
247
+ global_embed = timestep_embed
248
+ elif self.timestep_cond_type == "input_concat":
249
+ x = torch.cat([x, timestep_embed.unsqueeze(2).expand(-1, -1, x.shape[2])], dim=1)
250
+
251
+ # Add the global_embed to the prepend inputs if there is no global conditioning support in the transformer
252
+ if self.global_cond_type == "prepend" and global_embed is not None:
253
+ if prepend_inputs is None:
254
+ # Prepend inputs are just the global embed, and the mask is all ones
255
+ prepend_inputs = global_embed.unsqueeze(1)
256
+ prepend_mask = torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)
257
+ else:
258
+ # Prepend inputs are the prepend conditioning + the global embed
259
+ prepend_inputs = torch.cat([prepend_inputs, global_embed.unsqueeze(1)], dim=1)
260
+ prepend_mask = torch.cat([prepend_mask, torch.ones((x.shape[0], 1), device=x.device, dtype=torch.bool)], dim=1)
261
+
262
+ prepend_length = prepend_inputs.shape[1]
263
+
264
+ x = self.preprocess_conv(x) + x
265
+
266
+ x = rearrange(x, "b c t -> b t c")
267
+
268
+ extra_args = {}
269
+
270
+ if self.global_cond_type == "adaLN":
271
+ extra_args["global_cond"] = global_embed
272
+
273
+ if self.patch_size > 1:
274
+ x = rearrange(x, "b (t p) c -> b t (c p)", p=self.patch_size)
275
+
276
+ result = self._call_transformer(
277
+ x,
278
+ prepend_inputs=prepend_inputs,
279
+ cross_attn_cond=cross_attn_cond,
280
+ mask=mask,
281
+ prepend_mask=prepend_mask,
282
+ return_info=return_info,
283
+ exit_layer_ix=exit_layer_ix,
284
+ local_add_cond=local_add_cond,
285
+ modular_local_cond=modular_local_cond,
286
+ padding_mask=padding_mask,
287
+ extra_args=extra_args,
288
+ **kwargs,
289
+ )
290
+
291
+ # Handle early exit (result contains both output and info)
292
+ if exit_layer_ix is not None:
293
+ return result
294
+
295
+ output = result[0] if return_info else result
296
+ if return_info:
297
+ info = result[1]
298
+
299
+ output = rearrange(output, "b t c -> b c t")[:,:,prepend_length:]
300
+
301
+ if self.patch_size > 1:
302
+ output = rearrange(output, "b (c p) t -> b c (t p)", p=self.patch_size)
303
+
304
+ output = self.postprocess_conv(output) + output
305
+
306
+ if return_info:
307
+ return output, info
308
+
309
+ return output
310
+
311
+ def apg_project(self, v0, v1, padding_mask=None):
312
+ """
313
+ Project v0 into components parallel and orthogonal to v1.
314
+
315
+ Args:
316
+ v0: Tensor to project (B, C, T)
317
+ v1: Reference direction (B, C, T)
318
+ padding_mask: Optional mask (B, T) where True = valid, False = padding.
319
+ If provided, only valid positions contribute to the projection.
320
+ """
321
+ dtype = v0.dtype
322
+ v0, v1 = v0.double(), v1.double()
323
+
324
+ if padding_mask is not None:
325
+ # Expand mask to match tensor shape: (B, T) -> (B, 1, T)
326
+ mask = padding_mask.unsqueeze(1).double()
327
+ # Zero out padding positions for projection computation
328
+ v0_masked = v0 * mask
329
+ v1_masked = v1 * mask
330
+ # Normalize only over valid positions
331
+ v1_norm = v1_masked.norm(dim=[-1, -2], keepdim=True).clamp(min=1e-8)
332
+ v1_normalized = v1_masked / v1_norm
333
+ # Compute projection using masked values
334
+ v0_parallel = (v0_masked * v1_normalized).sum(dim=[-1, -2], keepdim=True) * v1_normalized
335
+ # Orthogonal component: subtract parallel from original (not masked) v0
336
+ # but apply mask to ensure padding stays zero
337
+ v0_orthogonal = (v0 - (v0 * v1_normalized).sum(dim=[-1, -2], keepdim=True) * v1_normalized) * mask
338
+ else:
339
+ v1 = torch.nn.functional.normalize(v1, dim=[-1, -2])
340
+ v0_parallel = (v0 * v1).sum(dim=[-1, -2], keepdim=True) * v1
341
+ v0_orthogonal = v0 - v0_parallel
342
+
343
+ return v0_parallel.to(dtype), v0_orthogonal.to(dtype)
344
+
345
+ def forward(
346
+ self,
347
+ x,
348
+ t,
349
+ cross_attn_cond=None,
350
+ cross_attn_cond_mask=None,
351
+ negative_cross_attn_cond=None,
352
+ negative_cross_attn_mask=None,
353
+ input_concat_cond=None,
354
+ local_add_cond=None,
355
+ modular_local_cond=None,
356
+ global_embed=None,
357
+ negative_global_embed=None,
358
+ prepend_cond=None,
359
+ prepend_cond_mask=None,
360
+ padding_mask=None,
361
+ cfg_scale=1.0,
362
+ cfg_dropout_prob=0.0,
363
+ cfg_interval = (0, 1),
364
+ lora_interval = (0, 1),
365
+ lora_layer_filter = "",
366
+ lora_configs = None,
367
+ causal=False,
368
+ scale_phi=0.0,
369
+ cfg_norm_threshold=0.0,
370
+ apg_scale=1.0,
371
+ mask=None,
372
+ return_info=False,
373
+ exit_layer_ix=None,
374
+ **kwargs):
375
+
376
+ assert not causal, "Causal mode is not supported for DiffusionTransformer"
377
+
378
+ model_dtype = next(self.parameters()).dtype
379
+
380
+ x = x.to(model_dtype)
381
+
382
+ # Keep t in float32: the logsnr transform log((1-t)/t) amplifies bf16
383
+ # quantization error ~380x near t=1, causing catastrophic conditioning errors.
384
+ # t is a 1D batch-size tensor so float32 has zero memory impact.
385
+ t = t.float()
386
+
387
+ if cross_attn_cond is not None:
388
+ cross_attn_cond = cross_attn_cond.to(model_dtype)
389
+
390
+ if negative_cross_attn_cond is not None:
391
+ negative_cross_attn_cond = negative_cross_attn_cond.to(model_dtype)
392
+
393
+ if input_concat_cond is not None:
394
+ input_concat_cond = input_concat_cond.to(model_dtype)
395
+
396
+ if local_add_cond is not None:
397
+ local_add_cond = local_add_cond.to(model_dtype)
398
+
399
+ if modular_local_cond is not None:
400
+ modular_local_cond = {k: v.to(model_dtype) for k, v in modular_local_cond.items()}
401
+
402
+ if global_embed is not None:
403
+ global_embed = global_embed.to(model_dtype)
404
+
405
+ if negative_global_embed is not None:
406
+ negative_global_embed = negative_global_embed.to(model_dtype)
407
+
408
+ if prepend_cond is not None:
409
+ prepend_cond = prepend_cond.to(model_dtype)
410
+
411
+ if cross_attn_cond_mask is not None:
412
+ cross_attn_cond_mask = cross_attn_cond_mask.bool()
413
+
414
+ cross_attn_cond_mask = None # Temporarily disabling conditioning masks due to kernel issue for flash attention
415
+
416
+ if prepend_cond_mask is not None:
417
+ prepend_cond_mask = prepend_cond_mask.bool()
418
+
419
+ # Early exit bypasses CFG processing
420
+ if exit_layer_ix is not None:
421
+ assert self.transformer_type == "continuous_transformer", "exit_layer_ix is only supported for continuous_transformer"
422
+ return self._forward(
423
+ x,
424
+ t,
425
+ cross_attn_cond=cross_attn_cond,
426
+ cross_attn_cond_mask=cross_attn_cond_mask,
427
+ input_concat_cond=input_concat_cond,
428
+ local_add_cond=local_add_cond,
429
+ modular_local_cond=modular_local_cond,
430
+ global_embed=global_embed,
431
+ prepend_cond=prepend_cond,
432
+ prepend_cond_mask=prepend_cond_mask,
433
+ padding_mask=padding_mask,
434
+ mask=mask,
435
+ return_info=return_info,
436
+ exit_layer_ix=exit_layer_ix,
437
+ **kwargs
438
+ )
439
+
440
+ # CFG dropout
441
+ if cfg_dropout_prob > 0.0 and cfg_scale == 1.0:
442
+ if cross_attn_cond is not None:
443
+ null_embed = torch.zeros_like(cross_attn_cond, device=cross_attn_cond.device)
444
+ dropout_mask = torch.bernoulli(torch.full((cross_attn_cond.shape[0], 1, 1), cfg_dropout_prob, device=cross_attn_cond.device)).to(torch.bool)
445
+ cross_attn_cond = torch.where(dropout_mask, null_embed, cross_attn_cond)
446
+
447
+ if prepend_cond is not None:
448
+ null_embed = torch.zeros_like(prepend_cond, device=prepend_cond.device)
449
+ dropout_mask = torch.bernoulli(torch.full((prepend_cond.shape[0], 1, 1), cfg_dropout_prob, device=prepend_cond.device)).to(torch.bool)
450
+ prepend_cond = torch.where(dropout_mask, null_embed, prepend_cond)
451
+
452
+ if self.diffusion_objective == "v":
453
+ sigma = torch.sin(t * math.pi / 2)
454
+ alpha = torch.cos(t * math.pi / 2)
455
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
456
+ sigma = t
457
+
458
+ # LoRA interval
459
+ if has_lora(self):
460
+ if lora_configs is not None:
461
+ # Multi-LoRA: per-LoRA interval and layer filter
462
+ for lora_config in lora_configs:
463
+ idx = lora_config["lora_index"]
464
+ interval = lora_config.get("interval", (0, 1))
465
+ layer_filter = lora_config.get("layer_filter", "")
466
+ if interval[0] <= sigma[0] <= interval[1]:
467
+ enable_lora(self, lora_index=idx)
468
+ filter_lora_layers(self, layer_filter, lora_index=idx)
469
+ else:
470
+ disable_lora(self, lora_index=idx)
471
+ else:
472
+ # Legacy single-LoRA path
473
+ if lora_interval[0] <= sigma[0] <= lora_interval[1]:
474
+ enable_lora(self)
475
+ filter_lora_layers(self, lora_layer_filter)
476
+ else:
477
+ disable_lora(self)
478
+
479
+ if cfg_scale != 1.0 and (cross_attn_cond is not None or prepend_cond is not None) and (cfg_interval[0] <= sigma[0] <= cfg_interval[1]):
480
+
481
+ # Classifier-free guidance
482
+ # Concatenate conditioned and unconditioned inputs on the batch dimension
483
+ batch_inputs = torch.cat([x, x], dim=0)
484
+ batch_timestep = torch.cat([t, t], dim=0)
485
+
486
+ if global_embed is not None:
487
+ batch_global_cond = torch.cat([global_embed, global_embed], dim=0)
488
+ else:
489
+ batch_global_cond = None
490
+
491
+ if input_concat_cond is not None:
492
+ batch_input_concat_cond = torch.cat([input_concat_cond, input_concat_cond], dim=0)
493
+ else:
494
+ batch_input_concat_cond = None
495
+
496
+ if local_add_cond is not None:
497
+ batch_local_add_cond = torch.cat([local_add_cond, local_add_cond], dim=0)
498
+ else:
499
+ batch_local_add_cond = None
500
+
501
+ if modular_local_cond is not None:
502
+ batch_modular_local_cond = {k: torch.cat([v, v], dim=0) for k, v in modular_local_cond.items()}
503
+ else:
504
+ batch_modular_local_cond = None
505
+
506
+ batch_cond = None
507
+ batch_cond_masks = None
508
+
509
+ # Handle CFG for cross-attention conditioning
510
+ if cross_attn_cond is not None:
511
+
512
+ null_embed = torch.zeros_like(cross_attn_cond, device=cross_attn_cond.device)
513
+
514
+ # For negative cross-attention conditioning, replace the null embed with the negative cross-attention conditioning
515
+ if negative_cross_attn_cond is not None:
516
+
517
+ # If there's a negative cross-attention mask, set the masked tokens to the null embed
518
+ if negative_cross_attn_mask is not None:
519
+ negative_cross_attn_mask = negative_cross_attn_mask.to(torch.bool).unsqueeze(2)
520
+
521
+ negative_cross_attn_cond = torch.where(negative_cross_attn_mask, negative_cross_attn_cond, null_embed)
522
+
523
+ batch_cond = torch.cat([cross_attn_cond, negative_cross_attn_cond], dim=0)
524
+
525
+ else:
526
+ batch_cond = torch.cat([cross_attn_cond, null_embed], dim=0)
527
+
528
+ if cross_attn_cond_mask is not None:
529
+ batch_cond_masks = torch.cat([cross_attn_cond_mask, cross_attn_cond_mask], dim=0)
530
+
531
+ batch_prepend_cond = None
532
+ batch_prepend_cond_mask = None
533
+
534
+ if prepend_cond is not None:
535
+
536
+ null_embed = torch.zeros_like(prepend_cond, device=prepend_cond.device)
537
+
538
+ batch_prepend_cond = torch.cat([prepend_cond, null_embed], dim=0)
539
+
540
+ if prepend_cond_mask is not None:
541
+ batch_prepend_cond_mask = torch.cat([prepend_cond_mask, prepend_cond_mask], dim=0)
542
+
543
+
544
+ if mask is not None:
545
+ batch_masks = torch.cat([mask, mask], dim=0)
546
+ else:
547
+ batch_masks = None
548
+
549
+ if padding_mask is not None:
550
+ batch_padding_mask = torch.cat([padding_mask, padding_mask], dim=0)
551
+ else:
552
+ batch_padding_mask = None
553
+
554
+ batch_output = self._forward(
555
+ batch_inputs,
556
+ batch_timestep,
557
+ cross_attn_cond=batch_cond,
558
+ cross_attn_cond_mask=batch_cond_masks,
559
+ mask = batch_masks,
560
+ input_concat_cond = batch_input_concat_cond,
561
+ local_add_cond = batch_local_add_cond,
562
+ modular_local_cond=batch_modular_local_cond,
563
+ global_embed = batch_global_cond,
564
+ prepend_cond = batch_prepend_cond,
565
+ prepend_cond_mask = batch_prepend_cond_mask,
566
+ padding_mask = batch_padding_mask,
567
+ return_info = return_info,
568
+ **kwargs)
569
+
570
+ if return_info:
571
+ batch_output, info = batch_output
572
+
573
+ cond_output, uncond_output = torch.chunk(batch_output, 2, dim=0)
574
+
575
+ if self.diffusion_objective == "v":
576
+ cond_denoised = x * alpha[:, None, None] - cond_output * sigma[:, None, None]
577
+ uncond_denoised = x * alpha[:, None, None] - uncond_output * sigma[:, None, None]
578
+
579
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
580
+ cond_denoised = x - cond_output * sigma[:, None, None]
581
+ uncond_denoised = x - uncond_output * sigma[:, None, None]
582
+
583
+ diff = cond_denoised - uncond_denoised
584
+
585
+ if cfg_norm_threshold > 0:
586
+ if padding_mask is not None:
587
+ # Only compute norm over valid positions
588
+ mask = padding_mask.unsqueeze(1).float() # (B, 1, T)
589
+ diff_masked = diff * mask
590
+ diff_norm = diff_masked.norm(p=2, dim=[-1, -2], keepdim=True)
591
+ else:
592
+ diff_norm = diff.norm(p=2, dim=[-1, -2], keepdim=True)
593
+ scale_factor = torch.minimum(torch.ones_like(diff), cfg_norm_threshold / diff_norm)
594
+ diff *= scale_factor
595
+
596
+ if apg_scale == 0.0:
597
+ # Vanilla CFG: use full diff
598
+ cfg_diff = diff
599
+ elif apg_scale == 1.0:
600
+ # Full APG: use only orthogonal component
601
+ _, diff_orthogonal = self.apg_project(diff, cond_denoised, padding_mask=padding_mask)
602
+ cfg_diff = diff_orthogonal
603
+ else:
604
+ # Blended APG: interpolate between full diff and orthogonal
605
+ diff_parallel, diff_orthogonal = self.apg_project(diff, cond_denoised, padding_mask=padding_mask)
606
+ cfg_diff = apg_scale * diff_orthogonal + (1 - apg_scale) * diff
607
+
608
+ cfg_denoised = cond_denoised + (cfg_scale - 1) * cfg_diff
609
+
610
+ if self.diffusion_objective == "v":
611
+ output = (x * alpha[:, None, None] - cfg_denoised) / sigma[:, None, None]
612
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
613
+ output = (x - cfg_denoised) / sigma[:, None, None]
614
+
615
+ # CFG Rescale
616
+ if scale_phi != 0.0:
617
+ cond_out_std = cond_output.std(dim=1, keepdim=True)
618
+ out_cfg_std = output.std(dim=1, keepdim=True)
619
+ output = scale_phi * (output * (cond_out_std/out_cfg_std)) + (1-scale_phi) * output
620
+
621
+ if return_info:
622
+ info["uncond_output"] = uncond_output
623
+ return output, info
624
+
625
+ return output
626
+
627
+ else:
628
+ return self._forward(
629
+ x,
630
+ t,
631
+ cross_attn_cond=cross_attn_cond,
632
+ cross_attn_cond_mask=cross_attn_cond_mask,
633
+ input_concat_cond=input_concat_cond,
634
+ local_add_cond=local_add_cond,
635
+ modular_local_cond=modular_local_cond,
636
+ global_embed=global_embed,
637
+ prepend_cond=prepend_cond,
638
+ prepend_cond_mask=prepend_cond_mask,
639
+ padding_mask=padding_mask,
640
+ mask=mask,
641
+ return_info=return_info,
642
+ **kwargs
643
+ )
stable_audio_3/models/inpainting.py ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ import torch
3
+ from enum import Enum
4
+ from typing import List, Optional, Tuple
5
+
6
+ class MaskType(Enum):
7
+ RANDOM_SEGMENTS = 0 # Legacy: uncontrolled ratio, overlapping segments
8
+ FULL_MASK = 1
9
+ CAUSAL_MASK = 2
10
+ RANDOM_SPANS = 3 # New: explicit ratio control, non-overlapping spans
11
+
12
+
13
+ def _generate_random_spans_mask(
14
+ real_sequence_length: int,
15
+ sequence_length: int,
16
+ device: torch.device,
17
+ max_spans: int = 4,
18
+ mask_ratio_range: Tuple[float, float] = (0.2, 1.0),
19
+ span_count_weights: Optional[List[float]] = None,
20
+ ) -> torch.Tensor:
21
+ """
22
+ Generate a mask with 1-N non-overlapping contiguous spans covering a target ratio.
23
+
24
+ Samples a target mask ratio from U[mask_ratio_range], then allocates that budget
25
+ across a variable number of spans with Dirichlet-like random allocation.
26
+
27
+ Args:
28
+ real_sequence_length: Number of valid (non-padding) tokens.
29
+ sequence_length: Total sequence length including padding.
30
+ device: Torch device.
31
+ max_spans: Maximum number of masked spans.
32
+ mask_ratio_range: (lo, hi) range for uniform sampling of target mask ratio.
33
+ span_count_weights: Weights for sampling number of spans [1..max_spans].
34
+ Defaults to geometric decay [4, 2, 1, 1] (biased toward fewer spans).
35
+ """
36
+ item_mask = torch.ones((1, 1, sequence_length), device=device, dtype=torch.float32)
37
+
38
+ if real_sequence_length == 0:
39
+ return item_mask
40
+
41
+ # 1. Sample target mask ratio
42
+ lo, hi = mask_ratio_range
43
+ target_ratio = random.uniform(lo, hi)
44
+ target_masked_tokens = max(1, int(target_ratio * real_sequence_length))
45
+
46
+ # 2. Sample number of spans (biased toward fewer spans)
47
+ if span_count_weights is None:
48
+ span_count_weights = [4, 2, 1, 1]
49
+
50
+ # Truncate/pad weights to max_spans
51
+ weights = span_count_weights[:max_spans]
52
+ while len(weights) < max_spans:
53
+ weights.append(weights[-1])
54
+
55
+ num_spans = random.choices(range(1, max_spans + 1), weights=weights, k=1)[0]
56
+ num_spans = min(num_spans, real_sequence_length)
57
+
58
+ # 3. Allocate target tokens across spans (Dirichlet-like)
59
+ raw_weights = [random.random() for _ in range(num_spans)]
60
+ total_weight = sum(raw_weights)
61
+ span_lengths = [max(1, int(w / total_weight * target_masked_tokens)) for w in raw_weights]
62
+
63
+ # Adjust to hit exact target
64
+ diff = target_masked_tokens - sum(span_lengths)
65
+ if diff > 0:
66
+ span_lengths[0] += diff
67
+ elif diff < 0:
68
+ for i in range(len(span_lengths)):
69
+ reduce = min(-diff, span_lengths[i] - 1)
70
+ span_lengths[i] -= reduce
71
+ diff += reduce
72
+ if diff >= 0:
73
+ break
74
+
75
+ # 4. Place spans without overlap (largest first for better packing)
76
+ span_lengths.sort(reverse=True)
77
+ placed_spans = []
78
+
79
+ for length in span_lengths:
80
+ length = min(length, real_sequence_length)
81
+ if length <= 0:
82
+ continue
83
+
84
+ max_start = real_sequence_length - length
85
+ if max_start < 0:
86
+ continue
87
+
88
+ # Try random placement avoiding overlaps
89
+ placed = False
90
+ for _ in range(50):
91
+ start = random.randint(0, max_start)
92
+ end = start + length
93
+ if not any(s < end and start < e for s, e in placed_spans):
94
+ placed_spans.append((start, end))
95
+ item_mask[:, :, start:end] = 0
96
+ placed = True
97
+ break
98
+
99
+ # Fallback: scan for first valid position
100
+ if not placed:
101
+ for start in range(max_start + 1):
102
+ end = start + length
103
+ if not any(s < end and start < e for s, e in placed_spans):
104
+ placed_spans.append((start, end))
105
+ item_mask[:, :, start:end] = 0
106
+ break
107
+
108
+ return item_mask
109
+
110
+
111
+ def random_inpaint_mask(
112
+ sequence: torch.Tensor,
113
+ padding_masks: torch.Tensor,
114
+ max_mask_segments: int = 10,
115
+ mask_type_probabilities: Optional[List[float]] = None,
116
+ mask_padding: bool = False,
117
+ force_mask_type: Optional[MaskType] = None,
118
+ # RANDOM_SPANS parameters
119
+ max_spans: int = 4,
120
+ mask_ratio_range: Tuple[float, float] = (0.2, 1.0),
121
+ span_count_weights: Optional[List[float]] = None,
122
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
123
+ """
124
+ Generates random inpainting masks for a batch of latent audio sequences.
125
+ The output inpainting mask has 0 where data should be inpainted, and 1 where data is provided.
126
+
127
+ Supports two segment masking modes:
128
+ - RANDOM_SEGMENTS (legacy): Uncontrolled mask ratio, overlapping segments allowed.
129
+ - RANDOM_SPANS (new): Explicit target mask ratio sampled from mask_ratio_range,
130
+ non-overlapping contiguous spans with controllable count distribution.
131
+
132
+ Args:
133
+ sequence: The input sequence tensor of shape (b, c, sequence_length).
134
+ padding_masks: A tensor of shape (b, sequence_length)
135
+ where 1 indicates real data latents and 0 indicates latents encoding silence padding.
136
+ max_mask_segments: (RANDOM_SEGMENTS only) Maximum number of segments.
137
+ mask_type_probabilities: Probabilities for each mask type. Length must match the
138
+ number of MaskType values used:
139
+ - 3 elements: [P(RANDOM_SEGMENTS), P(FULL_MASK), P(CAUSAL_MASK)]
140
+ - 4 elements: [P(RANDOM_SEGMENTS), P(FULL_MASK), P(CAUSAL_MASK), P(RANDOM_SPANS)]
141
+ If None, defaults to [0.1, 0.8, 0.1] (legacy 3-type behavior).
142
+ mask_padding: If True, zero out padding region in the inpaint mask for
143
+ RANDOM_SEGMENTS/RANDOM_SPANS/CAUSAL_MASK. Use when training with
144
+ attention masking for variable-length sequences.
145
+ max_spans: (RANDOM_SPANS only) Maximum number of masked spans.
146
+ mask_ratio_range: (RANDOM_SPANS only) (lo, hi) for uniform sampling of target mask ratio.
147
+ span_count_weights: (RANDOM_SPANS only) Weights for sampling number of spans [1..max_spans].
148
+
149
+ Returns:
150
+ A tuple containing:
151
+ - masked_sequence: The sequence with masks applied (original sequence where mask is 1,
152
+ and usually 0 or a placeholder where mask is 0).
153
+ - inpaint_mask: The generated inpainting mask tensor (0 for inpaint, 1 for keep).
154
+ """
155
+ b, _, sequence_length = sequence.size()
156
+
157
+ # Skip probability validation when forcing a specific mask type
158
+ if force_mask_type is not None:
159
+ mask_types_to_sample = None
160
+ else:
161
+ if mask_type_probabilities is None:
162
+ mask_type_probabilities = [0.1, 0.8, 0.1]
163
+
164
+ num_probs = len(mask_type_probabilities)
165
+
166
+ # Determine which mask types are active based on probability list length
167
+ if num_probs == 3:
168
+ # Legacy: [RANDOM_SEGMENTS, FULL_MASK, CAUSAL_MASK]
169
+ mask_types_to_sample = [MaskType.RANDOM_SEGMENTS.value, MaskType.FULL_MASK.value, MaskType.CAUSAL_MASK.value]
170
+ elif num_probs == 4:
171
+ # Extended: [RANDOM_SEGMENTS, FULL_MASK, CAUSAL_MASK, RANDOM_SPANS]
172
+ mask_types_to_sample = [MaskType.RANDOM_SEGMENTS.value, MaskType.FULL_MASK.value, MaskType.CAUSAL_MASK.value, MaskType.RANDOM_SPANS.value]
173
+ else:
174
+ raise ValueError(
175
+ f"mask_type_probabilities must have 3 or 4 elements. Got {num_probs}."
176
+ )
177
+
178
+ if not torch.isclose(torch.tensor(sum(mask_type_probabilities)), torch.tensor(1.0)):
179
+ raise ValueError(
180
+ f"mask_type_probabilities must sum to 1.0. "
181
+ f"Current sum: {sum(mask_type_probabilities)}"
182
+ )
183
+
184
+ output_masks_list = []
185
+
186
+ for i in range(b):
187
+ padding_mask_single_item = padding_masks[i]
188
+ real_sequence_length = (padding_mask_single_item == 1).sum().item()
189
+
190
+ item_mask = torch.ones((1, 1, sequence_length), device=sequence.device, dtype=torch.float32)
191
+
192
+ if force_mask_type is not None:
193
+ current_mask_type = force_mask_type
194
+ else:
195
+ chosen_mask_value = random.choices(mask_types_to_sample, weights=mask_type_probabilities, k=1)[0]
196
+ current_mask_type = MaskType(chosen_mask_value)
197
+
198
+ if current_mask_type == MaskType.FULL_MASK:
199
+ item_mask = torch.zeros((1, 1, sequence_length), device=sequence.device, dtype=torch.float32)
200
+ elif real_sequence_length == 0:
201
+ pass # item_mask remains all ones
202
+ else:
203
+ if current_mask_type == MaskType.RANDOM_SEGMENTS:
204
+ # Legacy behavior: uncontrolled ratio, overlapping segments
205
+ num_segments = random.randint(1, max_mask_segments)
206
+ max_len_per_segment_calc = max(1, real_sequence_length // num_segments)
207
+
208
+ for _ in range(num_segments):
209
+ segment_length = random.randint(1, max_len_per_segment_calc)
210
+
211
+ if real_sequence_length - segment_length < 0:
212
+ continue
213
+ mask_start = random.randint(0, real_sequence_length - segment_length)
214
+ item_mask[:, :, mask_start : mask_start + segment_length] = 0
215
+
216
+ elif current_mask_type == MaskType.RANDOM_SPANS:
217
+ item_mask = _generate_random_spans_mask(
218
+ real_sequence_length=real_sequence_length,
219
+ sequence_length=sequence_length,
220
+ device=sequence.device,
221
+ max_spans=max_spans,
222
+ mask_ratio_range=mask_ratio_range,
223
+ span_count_weights=span_count_weights,
224
+ )
225
+
226
+ elif current_mask_type == MaskType.CAUSAL_MASK:
227
+ unmasked_prefix_len = random.randint(0, real_sequence_length)
228
+
229
+ if unmasked_prefix_len < real_sequence_length:
230
+ item_mask[:, :, unmasked_prefix_len:real_sequence_length] = 0
231
+
232
+ # When using attention masking for variable-length training, zero out padding
233
+ # so the model doesn't see it as provided context. Without attention masking,
234
+ # padding is handled via mask_loss_weight and should remain as provided input.
235
+ if mask_padding:
236
+ item_mask[:, :, real_sequence_length:] = 0
237
+
238
+ output_masks_list.append(item_mask)
239
+
240
+ final_inpaint_mask = torch.cat(output_masks_list, dim=0).to(sequence.device)
241
+ masked_sequence = sequence * final_inpaint_mask
242
+ return masked_sequence, final_inpaint_mask
stable_audio_3/models/lora/__init__.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .model import LoRAParametrization, add_lora, default_lora_config, merge_lora, remove_lora, remove_lora_by_index, set_lora_strength
2
+ from .utils import (
3
+ apply_to_lora,
4
+ convert_lora_ckpt_to_safetensors,
5
+ disable_lora,
6
+ enable_lora,
7
+ filter_lora_layers,
8
+ get_lora_count,
9
+ has_lora,
10
+ get_bias_params,
11
+ get_lora_params,
12
+ get_lora_layers,
13
+ get_lora_state_dict,
14
+ load_lora_checkpoint,
15
+ load_multiple_lora,
16
+ name_is_lora,
17
+ remap_lora_state_dict,
18
+ save_lora_safetensors,
19
+ select_lora,
20
+ tie_weights,
21
+ untie_weights,
22
+ infer_global_rank,
23
+ cast_base_to_precision,
24
+ detect_dora_variant,
25
+ prepare_dora_state_dict,
26
+ resolve_adapter_type
27
+ )
28
+ from .loader import load_and_apply_loras
stable_audio_3/models/lora/loader.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Helpers for loading pre-trained LoRA checkpoints onto a live model."""
2
+ import os
3
+ from functools import partial
4
+
5
+ import torch
6
+
7
+ from .model import LoRAParametrization, add_lora
8
+ from .utils import (
9
+ get_lora_layers,
10
+ infer_global_rank,
11
+ load_lora_checkpoint,
12
+ prepare_dora_state_dict,
13
+ remap_lora_state_dict,
14
+ resolve_adapter_type,
15
+ )
16
+ from ...verbose import vprint
17
+
18
+
19
+ def load_and_apply_loras(model, lora_ckpt_paths, model_type, svd_bases_path=None):
20
+ """Load LoRA checkpoints from disk and attach them to `model`.
21
+
22
+ - Uses a two-pass approach: first resolves each LoRA's adapter type, then
23
+ loads SVD bases (only if any LoRA is -XS), then applies each LoRA.
24
+ - Handles legacy "dora" adapter_type via `resolve_adapter_type`.
25
+ - Only passes svd_bases to -XS LoRAs to avoid spurious "no -XS layers"
26
+ messages for non-XS LoRAs.
27
+ - Sets `model.use_lora = True` and `model.lora_names = [...]` as a
28
+ convenience for downstream UI code.
29
+
30
+ Args:
31
+ model: The loaded model. For `diffusion_cond` / `diffusion_cond_inpaint`
32
+ types, LoRAs are applied to `model.model` and `model.conditioner`;
33
+ otherwise they are applied to `model` directly.
34
+ lora_ckpt_paths: List of paths to `.ckpt` or `.safetensors` files.
35
+ model_type: String from the model config, e.g. "diffusion_cond".
36
+ svd_bases_path: Optional path to a precomputed SVD bases .pt file
37
+ (used only by -XS adapter types).
38
+
39
+ Returns:
40
+ List of display names (file stems) in the same order as the input paths.
41
+ """
42
+ if not lora_ckpt_paths:
43
+ model.use_lora = False
44
+ model.lora_names = []
45
+ return []
46
+
47
+ is_cond = model_type in ("diffusion_cond", "diffusion_cond_inpaint")
48
+
49
+ # Pass 1: load each checkpoint and resolve adapter type.
50
+ lora_entries = [] # (path, state_dict, config_dict, adapter_type)
51
+ for i, lora_path in enumerate(lora_ckpt_paths):
52
+ print(f"Loading LoRA {i} from {lora_path}")
53
+ state_dict, config_dict = load_lora_checkpoint(lora_path)
54
+ adapter_type_raw = config_dict.get("adapter_type", "lora")
55
+ adapter_type = resolve_adapter_type(adapter_type_raw, state_dict)
56
+ if adapter_type != adapter_type_raw:
57
+ vprint(f"Resolved legacy '{adapter_type_raw}' -> {adapter_type}")
58
+ lora_entries.append((lora_path, state_dict, config_dict, adapter_type))
59
+
60
+ # Load SVD bases only if at least one LoRA is -XS.
61
+ model_svd_bases = None
62
+ cond_svd_bases = None
63
+ any_xs = any(t.endswith("-xs") for _, _, _, t in lora_entries)
64
+ if any_xs:
65
+ if svd_bases_path is not None:
66
+ vprint(f"Loading SVD bases from {svd_bases_path}")
67
+ svd_bases_all = torch.load(svd_bases_path, map_location="cpu", weights_only=True)
68
+ model_svd_bases = {k[len("model."):]: v for k, v in svd_bases_all.items() if k.startswith("model.")}
69
+ cond_svd_bases = {k[len("conditioner."):]: v for k, v in svd_bases_all.items() if k.startswith("conditioner.")}
70
+ vprint(f" model bases: {len(model_svd_bases)}, conditioner bases: {len(cond_svd_bases)}")
71
+ else:
72
+ print("WARNING: -XS adapter type present without svd_bases_path -- SVD will be computed on current device")
73
+
74
+ # Pass 2: apply each LoRA.
75
+ lora_names = []
76
+ for i, (lora_path, state_dict, config_dict, adapter_type) in enumerate(lora_entries):
77
+ rank = config_dict.get("rank", infer_global_rank(state_dict))
78
+ alpha = config_dict.get("alpha", rank)
79
+ include = config_dict.get("include", None)
80
+ exclude = config_dict.get("exclude", None)
81
+ is_xs = adapter_type.endswith("-xs")
82
+
83
+ lora_config = {
84
+ torch.nn.Linear: {
85
+ "weight": partial(LoRAParametrization.from_linear, rank=rank, lora_alpha=alpha, adapter_type=adapter_type, lora_index=i),
86
+ },
87
+ torch.nn.Conv1d: {
88
+ "weight": partial(LoRAParametrization.from_conv1d, rank=rank, lora_alpha=alpha, adapter_type=adapter_type, lora_index=i),
89
+ },
90
+ }
91
+ if is_cond:
92
+ add_lora(model.model, lora_config, include=include, exclude=exclude,
93
+ svd_bases=model_svd_bases if is_xs else None)
94
+ add_lora(model.conditioner, lora_config, include=include, exclude=exclude,
95
+ svd_bases=cond_svd_bases if is_xs else None)
96
+ else:
97
+ add_lora(model, lora_config, include=include, exclude=exclude,
98
+ svd_bases=model_svd_bases if is_xs else None)
99
+
100
+ prepare_dora_state_dict(state_dict)
101
+ remapped_sd = remap_lora_state_dict(state_dict, i)
102
+ if is_cond:
103
+ model.model.load_state_dict(remapped_sd, strict=False)
104
+ model.conditioner.load_state_dict(remapped_sd, strict=False)
105
+ else:
106
+ model.load_state_dict(remapped_sd, strict=False)
107
+ lora_names.append(os.path.splitext(os.path.basename(lora_path))[0])
108
+
109
+ vprint("lora layers:", len(get_lora_layers(model)))
110
+ model.use_lora = True
111
+ model.lora_names = lora_names
112
+ return lora_names
stable_audio_3/models/lora/model.py ADDED
@@ -0,0 +1,471 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ References:
3
+ 1) the official LoRA implementation released by Microsoft:
4
+ https://github.com/microsoft/LoRA/blob/main/loralib/layers.py
5
+ """
6
+
7
+ import math
8
+ import re
9
+ from functools import partial
10
+ from itertools import product
11
+
12
+ import torch
13
+ import torch.nn.utils.parametrize as parametrize
14
+ from torch import nn
15
+
16
+ from ...verbose import vprint
17
+
18
+
19
+ def _canonicalize_svd_signs(U, Vh):
20
+ """Enforce deterministic sign convention: largest-magnitude element of each U column is positive."""
21
+ max_abs_idx = U.abs().argmax(dim=0)
22
+ signs = U[max_abs_idx, torch.arange(U.shape[1])].sign()
23
+ signs[signs == 0] = 1
24
+ return U * signs.unsqueeze(0), Vh * signs.unsqueeze(1)
25
+
26
+
27
+ class LoRAParametrization(nn.Module):
28
+ def __init__(self, fan_in, fan_out, fan_in_fan_out=False, rank=4, lora_dropout_p=0.0, lora_alpha=1,
29
+ adapter_type="lora", W0=None, lora_index=0, svd_bases=None
30
+ ):
31
+ super().__init__()
32
+ dtype = torch.get_default_dtype()
33
+ device = W0.device if W0 is not None else None
34
+ # if weight is stored as (fan_out, fan_in), the memory layout of A & B follows (W + BA)x
35
+ # otherwise, it's x(W + AB). This allows us to tie the weights between linear layers and embeddings
36
+ self.swap = (lambda x: (x[1], x[0])) if fan_in_fan_out else (lambda x: x)
37
+ self.lora_alpha, self.rank = lora_alpha, rank
38
+ self.scaling = lora_alpha / rank
39
+ self.lora_dropout = nn.Dropout(p=lora_dropout_p) if lora_dropout_p > 0 else lambda x: x
40
+ self.dropout_fn = self._dropout if lora_dropout_p > 0 else lambda x: x
41
+ self.register_buffer("lora_dropout_mask", torch.ones(self.swap((1, fan_in)), dtype=dtype, device=device))
42
+ self.register_buffer("lora_strength", torch.tensor(1.0, dtype=dtype, device=device), persistent=False)
43
+ self.adapter_type = adapter_type
44
+ self.lora_index = lora_index
45
+
46
+ if self.adapter_type == "lora":
47
+ self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
48
+ self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
49
+ nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
50
+ self.forward_fn = self.lora_forward
51
+ self._adapter_forward_fn = self.forward_fn
52
+
53
+ elif self.adapter_type in ("dora-rows", "dora"):
54
+ # DoRA-rows: magnitude per row (per-output-neuron, dim=1) - paper-correct variant
55
+ # "dora" maps here as the default for backward compat
56
+ self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
57
+ self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
58
+ nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
59
+ self._norm_dim = 1
60
+ if W0 is not None:
61
+ W0_2d = W0.view(W0.shape[0], -1)
62
+ self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
63
+ else:
64
+ self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
65
+ self.forward_fn = self.dora_forward
66
+ self._adapter_forward_fn = self.forward_fn
67
+
68
+ elif self.adapter_type == "dora-cols":
69
+ # DoRA-cols: magnitude per column (per-input-feature, dim=0) - legacy variant
70
+ self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
71
+ self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
72
+ nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
73
+ self._norm_dim = 0
74
+ if W0 is not None:
75
+ W0_2d = W0.view(W0.shape[0], -1)
76
+ self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
77
+ else:
78
+ self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
79
+ self.forward_fn = self.dora_forward
80
+ self._adapter_forward_fn = self.forward_fn
81
+
82
+ elif self.adapter_type == "bora":
83
+ # BoRA: bi-dimensional DoRA - magnitude on both row and column axes
84
+ self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
85
+ self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
86
+ nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
87
+ if W0 is not None:
88
+ W0_2d = W0.view(W0.shape[0], -1)
89
+ self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
90
+ self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
91
+ else:
92
+ self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
93
+ self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
94
+ self.forward_fn = self.bora_forward
95
+ self._adapter_forward_fn = self.forward_fn
96
+
97
+ elif self.adapter_type == "lora-xs":
98
+ # frozen bases U:(fan_out,r), V:(fan_in,r); tiny trainable core M:(r,r)
99
+ self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
100
+ self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
101
+ self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
102
+ if svd_bases is not None:
103
+ self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
104
+ self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
105
+ elif W0 is not None:
106
+ W0_2d = W0.view(W0.shape[0], -1).cpu().float()
107
+ U_full, S, Vh_full = torch.linalg.svd(W0_2d, full_matrices=False)
108
+ U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
109
+ self.U.copy_(U_full[:, :self.rank].to(dtype))
110
+ self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
111
+ self.forward_fn = self.lora_xs_forward
112
+ self._adapter_forward_fn = self.forward_fn
113
+
114
+ elif self.adapter_type == "dora-rows-xs":
115
+ # DoRA-rows + LoRA-XS: frozen SVD bases + trainable M_xs core + row magnitude
116
+ self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
117
+ self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
118
+ self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
119
+ self._norm_dim = 1
120
+ if svd_bases is not None:
121
+ self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
122
+ self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
123
+ if W0 is not None:
124
+ W0_2d = W0.view(W0.shape[0], -1)
125
+ if svd_bases is None:
126
+ U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
127
+ U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
128
+ self.U.copy_(U_full[:, :self.rank].to(dtype))
129
+ self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
130
+ self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
131
+ else:
132
+ self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
133
+ self.forward_fn = self.dora_xs_forward
134
+ self._adapter_forward_fn = self.forward_fn
135
+
136
+ elif self.adapter_type == "dora-cols-xs":
137
+ # DoRA-cols + LoRA-XS: frozen SVD bases + trainable M_xs core + column magnitude
138
+ self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
139
+ self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
140
+ self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
141
+ self._norm_dim = 0
142
+ if svd_bases is not None:
143
+ self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
144
+ self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
145
+ if W0 is not None:
146
+ W0_2d = W0.view(W0.shape[0], -1)
147
+ if svd_bases is None:
148
+ U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
149
+ U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
150
+ self.U.copy_(U_full[:, :self.rank].to(dtype))
151
+ self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
152
+ self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
153
+ else:
154
+ self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
155
+ self.forward_fn = self.dora_xs_forward
156
+ self._adapter_forward_fn = self.forward_fn
157
+
158
+ elif self.adapter_type == "bora-xs":
159
+ # BoRA + LoRA-XS: frozen SVD bases + trainable M_xs core + row & column magnitudes
160
+ self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
161
+ self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
162
+ self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
163
+ if svd_bases is not None:
164
+ self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
165
+ self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
166
+ if W0 is not None:
167
+ W0_2d = W0.view(W0.shape[0], -1)
168
+ if svd_bases is None:
169
+ U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
170
+ U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
171
+ self.U.copy_(U_full[:, :self.rank].to(dtype))
172
+ self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
173
+ self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
174
+ self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
175
+ else:
176
+ self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
177
+ self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
178
+ self.forward_fn = self.bora_xs_forward
179
+ self._adapter_forward_fn = self.forward_fn
180
+
181
+
182
+ def lora_forward(self, W):
183
+ delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A)))).view(W.shape)
184
+ delta = self.scaling * self.lora_strength * delta
185
+ return (W + delta.to(W.dtype))
186
+
187
+ def dora_forward(self, W):
188
+ if self.lora_strength == 0:
189
+ return W
190
+ # work in 2D so norm/magnitude ops don't broadcast badly for Conv1d/Conv2d
191
+ # Upcast W to LoRA param dtype for stable norm computation, cast result back.
192
+ orig_shape = W.shape
193
+ W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
194
+ # low-rank update on the *direction*
195
+ delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
196
+ V = W_2d + self.scaling * self.lora_strength * delta
197
+ # normalize along _norm_dim, then scale by per-element magnitude
198
+ V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
199
+ return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
200
+
201
+ def bora_forward(self, W):
202
+ if self.lora_strength == 0:
203
+ return W
204
+ orig_shape = W.shape
205
+ W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
206
+ delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
207
+ V = W_2d + self.scaling * self.lora_strength * delta
208
+ # Row-normalize, then scale by row magnitudes
209
+ V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
210
+ intermediate = self.magnitude_r.unsqueeze(1) * V_r
211
+ # Column-normalize, then scale by column magnitudes
212
+ H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
213
+ return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
214
+
215
+ def lora_xs_forward(self, W):
216
+ delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
217
+ delta = delta.view(W.shape)
218
+ return (W + (self.scaling * self.lora_strength * delta).to(W.dtype))
219
+
220
+ def dora_xs_forward(self, W):
221
+ if self.lora_strength == 0:
222
+ return W
223
+ orig_shape = W.shape
224
+ W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
225
+ delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
226
+ V = W_2d + self.scaling * self.lora_strength * delta
227
+ V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
228
+ return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
229
+
230
+ def bora_xs_forward(self, W):
231
+ if self.lora_strength == 0:
232
+ return W
233
+ orig_shape = W.shape
234
+ W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
235
+ delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
236
+ V = W_2d + self.scaling * self.lora_strength * delta
237
+ V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
238
+ intermediate = self.magnitude_r.unsqueeze(1) * V_r
239
+ H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
240
+ return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
241
+
242
+ def forward(self, X):
243
+ return self.forward_fn(X)
244
+
245
+ def _dropout(self, A):
246
+ # to mimic the original implementation: A @ dropout(x), we do (A * dropout(ones)) @ x
247
+ return A * self.lora_dropout(self.lora_dropout_mask)
248
+
249
+ def disable_lora(self):
250
+ self.forward_fn = lambda x: x
251
+
252
+ def enable_lora(self):
253
+ self.forward_fn = self._adapter_forward_fn
254
+
255
+ @classmethod
256
+ def _get_original_weight(cls, layer, attr_name="weight"):
257
+ """Get the true original weight, even when the layer is already parametrized."""
258
+ if hasattr(layer, 'parametrizations') and hasattr(layer.parametrizations, attr_name):
259
+ return getattr(layer.parametrizations, attr_name).original.detach()
260
+ return getattr(layer, attr_name).detach()
261
+
262
+ @classmethod
263
+ def from_linear(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
264
+ fan_out, fan_in = layer.weight.shape
265
+ W0 = cls._get_original_weight(layer)
266
+ return cls(
267
+ fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
268
+ W0=W0, svd_bases=svd_bases, **kwargs
269
+ )
270
+
271
+ @classmethod
272
+ def from_conv2d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
273
+ fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
274
+ W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
275
+ return cls(
276
+ fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
277
+ W0=W0, svd_bases=svd_bases, **kwargs
278
+ )
279
+
280
+ @classmethod
281
+ def from_conv1d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
282
+ fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
283
+ W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
284
+ return cls(
285
+ fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
286
+ W0=W0, svd_bases=svd_bases, **kwargs
287
+ )
288
+
289
+ @classmethod
290
+ def from_embedding(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
291
+ fan_in, fan_out = layer.weight.shape
292
+ W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
293
+ return cls(
294
+ fan_in, fan_out, fan_in_fan_out=True, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
295
+ W0=W0, svd_bases=svd_bases, **kwargs
296
+ )
297
+
298
+
299
+ default_lora_config = { # specify which layers to add lora to, by default only add to linear layers
300
+ nn.Linear: {
301
+ "weight": partial(LoRAParametrization.from_linear, rank=4),
302
+ },
303
+ }
304
+
305
+
306
+ # --- layer name filtering helpers ---
307
+
308
+ def _expand(s):
309
+ """Expand bracket notation in layer filter strings, e.g. 'layers[0-5]' -> ['layers0', ..., 'layers5']."""
310
+ t = re.split(r'\[(\d+)-(\d+)\]', s)
311
+ if len(t) == 1:
312
+ return [s]
313
+ lits, starts, ends = t[0::3], t[1::3], t[2::3]
314
+ pools = []
315
+ for a, b in zip(starts, ends):
316
+ ai, bi = int(a), int(b)
317
+ step = 1 if bi >= ai else -1
318
+ pools.append([str(n) for n in range(ai, bi + step, step)])
319
+ out = []
320
+ for combo in product(*pools):
321
+ pieces = []
322
+ for i, lit in enumerate(lits):
323
+ pieces.append(lit)
324
+ if i < len(combo):
325
+ pieces.append(combo[i])
326
+ out.append(''.join(pieces))
327
+ return out
328
+
329
+
330
+ def _matches_any(name, patterns):
331
+ """Check if name contains any pattern substring (with bracket expansion)."""
332
+ if not patterns:
333
+ return False
334
+ for pattern in patterns:
335
+ for expanded in _expand(pattern.strip()):
336
+ if expanded in name:
337
+ return True
338
+ return False
339
+
340
+
341
+ # --- core LoRA application ---
342
+
343
+ def _match_layer_type(layer, lora_config):
344
+ """Find the matching lora_config key for a layer, using isinstance to handle ParametrizedLinear etc."""
345
+ for layer_type in lora_config:
346
+ if isinstance(layer, layer_type):
347
+ return layer_type
348
+ return None
349
+
350
+
351
+ def apply_lora(layer, register=True, merge=False, lora_config=default_lora_config):
352
+ """add lora parametrization to a layer, designed to be used with model.apply"""
353
+ if register:
354
+ matched_type = _match_layer_type(layer, lora_config)
355
+ if matched_type is not None:
356
+ for attr_name, parametrization in lora_config[matched_type].items():
357
+ parametrize.register_parametrization(layer, attr_name, parametrization(layer), unsafe=True)
358
+ else: # this will remove all parametrizations, use with caution
359
+ if hasattr(layer, "parametrizations"):
360
+ for attr_name in layer.parametrizations.keys():
361
+ parametrize.remove_parametrizations(layer, attr_name, leave_parametrized=merge)
362
+
363
+
364
+ def add_lora(model, lora_config=default_lora_config, include=None, exclude=None, svd_bases=None):
365
+ """Add LoRA parametrization to layers in a model.
366
+
367
+ Args:
368
+ model: The model to add LoRA to.
369
+ lora_config: Dict mapping nn.Module types to parametrization configs.
370
+ include: Optional list of substrings. If provided, only modules whose
371
+ name contains at least one pattern get LoRA. Supports bracket
372
+ expansion (e.g. "layers[0-11]").
373
+ exclude: Optional list of substrings. Modules matching any pattern are
374
+ skipped even if they match an include pattern.
375
+ svd_bases: Optional dict of pre-computed SVD bases keyed by
376
+ "layer_name.weight". When provided, -XS adapter types use
377
+ these instead of computing SVD.
378
+ """
379
+ if svd_bases is not None or include is not None or exclude is not None:
380
+ # Named-module path: iterate by name so we can look up SVD bases per layer
381
+ n_cached = 0
382
+ n_recomputed = 0
383
+ for name, module in model.named_modules():
384
+ matched_type = _match_layer_type(module, lora_config)
385
+ if matched_type is None:
386
+ continue
387
+
388
+ if include is not None and not _matches_any(name, include):
389
+ continue
390
+
391
+ if exclude is not None and _matches_any(name, exclude):
392
+ continue
393
+
394
+ for attr_name, parametrization_fn in lora_config[matched_type].items():
395
+ layer_bases = None
396
+ if svd_bases is not None:
397
+ bases_key = f"{name}.{attr_name}"
398
+ layer_bases = svd_bases.get(bases_key)
399
+ param = parametrization_fn(module, svd_bases=layer_bases)
400
+ parametrize.register_parametrization(module, attr_name, param, unsafe=True)
401
+ if hasattr(param, 'U'): # -XS layer that needs SVD bases
402
+ if layer_bases is not None:
403
+ n_cached += 1
404
+ else:
405
+ n_recomputed += 1
406
+ if n_cached > 0:
407
+ vprint(f"SVD bases: {n_cached} layers loaded from cache")
408
+ if n_recomputed > 0:
409
+ vprint(f"SVD bases: {n_recomputed} layers RECOMPUTED (missing from cache)")
410
+ if svd_bases is not None and n_cached == 0 and n_recomputed == 0:
411
+ vprint("SVD bases: provided but no -XS layers found")
412
+ else:
413
+ # Fast path: original behavior, no name inspection needed
414
+ model.apply(partial(apply_lora, lora_config=lora_config))
415
+
416
+ def add_lora_by_name(model, target_module_names, lora_config=default_lora_config, svd_bases=None):
417
+ """Add LoRA to specific layers by name. Convenience wrapper around add_lora()."""
418
+ add_lora(model, lora_config=lora_config, include=target_module_names, svd_bases=svd_bases)
419
+
420
+
421
+ def merge_lora(model):
422
+ """merge lora parametrization to all layers in a model. This will remove all parametrization"""
423
+ model.apply(partial(apply_lora, register=False, merge=True))
424
+
425
+ def remove_lora(model):
426
+ """remove lora parametrization to all layers in a model. This will remove all parametrization"""
427
+ model.apply(partial(apply_lora, register=False, merge=False))
428
+
429
+ def remove_lora_by_index(model, lora_index):
430
+ """Remove all LoRA parametrizations with a specific lora_index.
431
+
432
+ For modules where other LoRA entries remain, only the matching entry is deleted.
433
+ For modules where the removed entry was the only one, the parametrization is
434
+ removed entirely and the weight reverts to its original value.
435
+ """
436
+ for name, mod in model.named_modules():
437
+ if not hasattr(mod, 'parametrizations'):
438
+ continue
439
+ for attr in list(mod.parametrizations.keys()):
440
+ params = mod.parametrizations[attr]
441
+ to_remove = [i for i, p in enumerate(params)
442
+ if isinstance(p, LoRAParametrization) and p.lora_index == lora_index]
443
+ if not to_remove:
444
+ continue
445
+ if len(params) == len(to_remove):
446
+ # All entries being removed — remove parametrization entirely
447
+ parametrize.remove_parametrizations(mod, attr, leave_parametrized=False)
448
+ else:
449
+ for i in reversed(to_remove):
450
+ del params[i]
451
+
452
+ def set_lora_strength(model, strength: float, lora_index=None):
453
+ """Set lora strength. If lora_index is None, sets all LoRAs. If specified, sets only that index."""
454
+ strength = float(strength)
455
+ if lora_index is None:
456
+ for name, buf in model.named_buffers(recurse=True):
457
+ if name.endswith("lora_strength"):
458
+ buf.fill_(strength)
459
+ else:
460
+ for p in _iter_lora_params(model):
461
+ if p.lora_index == lora_index:
462
+ p.lora_strength.fill_(strength)
463
+
464
+ def _iter_lora_params(model):
465
+ for _, mod in model.named_modules():
466
+ plist = getattr(getattr(mod, "parametrizations", None), "weight", None)
467
+ if plist is None:
468
+ continue
469
+ for p in plist:
470
+ if isinstance(p, LoRAParametrization):
471
+ yield p
stable_audio_3/models/lora/utils.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+
3
+ import torch
4
+ from safetensors import safe_open
5
+ from safetensors.torch import save_file as _st_save_file
6
+ from torch import nn
7
+
8
+ from .model import LoRAParametrization, _expand
9
+ from ...verbose import vprint
10
+
11
+ def apply_to_lora(fn):
12
+ """apply a function to LoRAParametrization layers, designed to be used with model.apply"""
13
+
14
+ def apply_fn(layer):
15
+ if isinstance(layer, LoRAParametrization):
16
+ fn(layer)
17
+
18
+ return apply_fn
19
+
20
+ def enable_lora(model, lora_index=None):
21
+ """Enable LoRA layers. If lora_index is None, enables all. If specified, enables only that index."""
22
+ def _enable(layer):
23
+ if isinstance(layer, LoRAParametrization):
24
+ if lora_index is None or layer.lora_index == lora_index:
25
+ layer.enable_lora()
26
+ model.apply(_enable)
27
+
28
+ def disable_lora(model, lora_index=None):
29
+ """Disable LoRA layers. If lora_index is None, disables all. If specified, disables only that index."""
30
+ def _disable(layer):
31
+ if isinstance(layer, LoRAParametrization):
32
+ if lora_index is None or layer.lora_index == lora_index:
33
+ layer.disable_lora()
34
+ model.apply(_disable)
35
+
36
+ def get_lora_layers(model):
37
+ layers = []
38
+ for name, m in model.named_modules():
39
+ plist = getattr(getattr(m, "parametrizations", None), "weight", None)
40
+ if plist is None:
41
+ continue
42
+ for p in plist:
43
+ if isinstance(p, LoRAParametrization):
44
+ layers.append((name, p))
45
+ return layers
46
+
47
+ def has_lora(model):
48
+ """Return True if the model has at least one LoRAParametrization on any weight."""
49
+ return len(get_lora_layers(model))>=1
50
+
51
+ def filter_lora_layers(model, lora_layer_filter, lora_index=None):
52
+ """Enable/disable LoRA layers by name filter. If lora_index is specified, only affects that index."""
53
+ lora_layer_filter = (lora_layer_filter or "").strip()
54
+ # lora_layer_filter: logical OR on comma separated substrings
55
+ # e.g. ".transformer.layers, .to_global_embed."
56
+ def is_filtered(layer_name):
57
+ filters = [x.lower().strip() for x in lora_layer_filter.split(",") if len(x)]
58
+ for f in filters:
59
+ for _f in _expand(f):
60
+ if _f in layer_name:
61
+ return True
62
+ return False
63
+ for name, p in get_lora_layers(model):
64
+ if lora_index is not None and p.lora_index != lora_index:
65
+ continue
66
+ if is_filtered(name):
67
+ p.disable_lora()
68
+ else:
69
+ p.enable_lora()
70
+
71
+
72
+
73
+ # ------------------- helper function for collecting parameters for training/saving -------------------
74
+
75
+
76
+ def name_is_lora(name):
77
+ return (
78
+ len(name.split(".")) >= 4
79
+ and (name.split(".")[-4]) == "parametrizations"
80
+ and name.split(".")[-1] in ["lora_A", "lora_B", "M_xs", "magnitude", "magnitude_r", "magnitude_c"]
81
+ )
82
+
83
+
84
+ def name_is_bias(name):
85
+ return name.split(".")[-1] == "bias"
86
+
87
+
88
+ def get_params_by_name(model, print_shapes=False, name_filter=None):
89
+ for n, p in model.named_parameters():
90
+ if name_filter is None or name_filter(n):
91
+ if print_shapes:
92
+ print(n, p.shape)
93
+ yield p
94
+
95
+
96
+ def get_lora_params(model, print_shapes=False):
97
+ return get_params_by_name(model, print_shapes=print_shapes, name_filter=name_is_lora)
98
+
99
+
100
+ def get_bias_params(model, print_shapes=False):
101
+ return get_params_by_name(model, print_shapes=print_shapes, name_filter=name_is_bias)
102
+
103
+
104
+ def get_lora_state_dict(model):
105
+ return {k: v for k, v in model.state_dict().items() if name_is_lora(k)}
106
+
107
+
108
+ def cast_base_to_precision(model, precision):
109
+ """Cast frozen base weights to lower precision, keeping LoRA params in fp32.
110
+
111
+ Args:
112
+ model: Model with LoRA parametrizations applied.
113
+ precision: One of "bf16", "bfloat16", "fp16", "float16".
114
+ """
115
+ import torch
116
+
117
+ dtype_map = {"bf16": torch.bfloat16, "bfloat16": torch.bfloat16,
118
+ "fp16": torch.float16, "float16": torch.float16}
119
+ target_dtype = dtype_map.get(precision)
120
+ if target_dtype is None:
121
+ print(f"Unknown base_precision={precision!r}, keeping fp32")
122
+ return
123
+ vprint(f"Casting frozen base weights to {target_dtype}")
124
+ model.to(target_dtype)
125
+ # Restore LoRA params to fp32 — optimizer needs full precision
126
+ n_restored = 0
127
+ for p in get_lora_params(model):
128
+ p.data = p.data.to(torch.float32)
129
+ n_restored += 1
130
+ vprint(f" Restored {n_restored} LoRA params to fp32")
131
+
132
+
133
+ # ------------------- helper function for loading LoRa at inference -------------------
134
+
135
+ def infer_global_rank(lora_sd: dict) -> int:
136
+ candidates = []
137
+ for k, v in lora_sd.items():
138
+ if k.endswith(".lora_A"):
139
+ candidates.append(v.shape[0])
140
+ elif k.endswith(".lora_B"):
141
+ candidates.append(v.shape[1])
142
+ elif k.endswith(".M_xs"):
143
+ candidates.append(v.shape[0])
144
+ if not candidates:
145
+ raise ValueError("No LoRA/LoRA-XS tensors found to infer rank")
146
+ r = candidates[0]
147
+ # sanity check they all match (most checkpoints use one global r)
148
+ if any(c != r for c in candidates):
149
+ print(f"Multiple ranks detected in ckpt: {sorted(set(candidates))}. "
150
+ "You’ll need per-layer ranks.")
151
+ return r
152
+
153
+
154
+ def detect_dora_variant(state_dict):
155
+ """Detect DoRA variant from a state dict.
156
+
157
+ Returns "dora-cols" (dim=0, per-input-feature) or "dora-rows" (dim=1,
158
+ per-output-neuron) based on the shape of the first .magnitude tensor
159
+ found. Returns None if no magnitude tensors exist or axis is ambiguous.
160
+ """
161
+ for key, val in state_dict.items():
162
+ if not key.endswith(".magnitude"):
163
+ continue
164
+ if val.dim() == 2:
165
+ if val.shape[0] == 1:
166
+ return "dora-cols" # old format: (1, fan_in)
167
+ elif val.shape[1] == 1:
168
+ return "dora-rows" # (fan_out, 1)
169
+ # 1D magnitude — can't determine axis without model context
170
+ return None
171
+ return None
172
+
173
+
174
+ def prepare_dora_state_dict(state_dict):
175
+ """Squeeze 2D DoRA magnitude tensors to 1D for loading. Modifies in-place."""
176
+ for key in list(state_dict.keys()):
177
+ if key.endswith(".magnitude") and state_dict[key].dim() == 2:
178
+ state_dict[key] = state_dict[key].squeeze()
179
+
180
+
181
+ def resolve_adapter_type(adapter_type, state_dict=None):
182
+ """Resolve legacy "dora" adapter_type to the correct variant.
183
+
184
+ If adapter_type is "dora" and a checkpoint state_dict is provided,
185
+ attempts to detect dora-rows vs dora-cols from magnitude tensor shapes.
186
+ Falls back to "dora-rows" (paper-correct default).
187
+ All other adapter types are returned unchanged.
188
+ """
189
+ if adapter_type != "dora":
190
+ return adapter_type
191
+ if state_dict is not None:
192
+ detected = detect_dora_variant(state_dict)
193
+ if detected:
194
+ return detected
195
+ return "dora-rows"
196
+
197
+
198
+ # ------------------- helper functions for safetensors LoRA checkpoints -------------------
199
+
200
+
201
+ def save_lora_safetensors(state_dict, lora_config, path):
202
+ """Save a LoRA checkpoint in safetensors format with config as metadata.
203
+
204
+ The lora_config dict is JSON-serialized and stored under the metadata key
205
+ ``"lora_config"``, which safetensors stores in the file header as a string.
206
+
207
+ Args:
208
+ state_dict: Dict of LoRA tensors (from get_lora_state_dict).
209
+ lora_config: Dict with keys like rank, alpha, adapter_type, include, exclude.
210
+ path: Output file path (should end in .safetensors).
211
+ """
212
+ metadata = {"lora_config": json.dumps(lora_config)}
213
+ fp16_dict = {k: v.half() if v.is_floating_point() else v for k, v in state_dict.items()}
214
+ _st_save_file(fp16_dict, str(path), metadata=metadata)
215
+
216
+
217
+ def load_lora_checkpoint(path):
218
+ """Load a LoRA checkpoint from either .safetensors or .ckpt format.
219
+
220
+ Returns:
221
+ Tuple of (state_dict, lora_config) where lora_config may be an empty
222
+ dict if the checkpoint has no embedded config.
223
+ """
224
+ path = str(path)
225
+ if path.endswith(".safetensors"):
226
+ with safe_open(path, framework="pt", device="cpu") as f:
227
+ state_dict = {key: f.get_tensor(key) for key in f.keys()}
228
+ metadata = f.metadata()
229
+ lora_config = {}
230
+ if metadata and "lora_config" in metadata:
231
+ lora_config = json.loads(metadata["lora_config"])
232
+ return state_dict, lora_config
233
+ else:
234
+ ckpt = torch.load(path, map_location="cpu", weights_only=False)
235
+ state_dict = ckpt["state_dict"]
236
+ lora_config = ckpt.get("lora_config", {})
237
+ return state_dict, lora_config
238
+
239
+
240
+ def convert_lora_ckpt_to_safetensors(ckpt_path, output_path=None):
241
+ """Convert a .ckpt LoRA checkpoint to .safetensors format.
242
+
243
+ Args:
244
+ ckpt_path: Path to the input .ckpt file.
245
+ output_path: Path for the output .safetensors file.
246
+ If None, replaces .ckpt extension with .safetensors.
247
+
248
+ Returns:
249
+ The output path.
250
+ """
251
+ ckpt_path = str(ckpt_path)
252
+ if output_path is None:
253
+ if ckpt_path.endswith(".ckpt"):
254
+ output_path = ckpt_path[:-5] + ".safetensors"
255
+ else:
256
+ output_path = ckpt_path + ".safetensors"
257
+ state_dict, lora_config = load_lora_checkpoint(ckpt_path)
258
+ save_lora_safetensors(state_dict, lora_config, output_path)
259
+ return output_path
260
+
261
+
262
+ # ------------------- helper functions for multi-LoRA stacking -------------------
263
+
264
+
265
+ def get_lora_count(model):
266
+ """Return the number of stacked LoRAs (distinct lora_index values)."""
267
+ indices = set()
268
+ for _, p in get_lora_layers(model):
269
+ indices.add(p.lora_index)
270
+ return len(indices)
271
+
272
+
273
+ def remap_lora_state_dict(state_dict, target_index):
274
+ """Remap LoRA state dict keys from parametrization index 0 to target_index.
275
+
276
+ Checkpoints always save keys with index 0 (e.g. *.parametrizations.weight.0.lora_A).
277
+ When loading the Nth stacked LoRA, we need to remap to index N.
278
+ """
279
+ if target_index == 0:
280
+ return state_dict
281
+ remapped = {}
282
+ for k, v in state_dict.items():
283
+ new_key = k.replace(
284
+ ".parametrizations.weight.0.",
285
+ ".parametrizations.weight.{}.".format(target_index)
286
+ )
287
+ remapped[new_key] = v
288
+ return remapped
289
+
290
+
291
+ # ------------------- helper function for inferencing with multiple lora -------------------
292
+
293
+
294
+ def _get_adapter_param_names(lora_layer):
295
+ """Return the list of trainable parameter names for this adapter type."""
296
+ if lora_layer.adapter_type == "lora":
297
+ return ["lora_A", "lora_B"]
298
+ elif lora_layer.adapter_type in ("dora", "dora-rows", "dora-cols"):
299
+ return ["lora_A", "lora_B", "magnitude"]
300
+ elif lora_layer.adapter_type == "bora":
301
+ return ["lora_A", "lora_B", "magnitude_r", "magnitude_c"]
302
+ elif lora_layer.adapter_type == "lora-xs":
303
+ return ["M_xs"]
304
+ elif lora_layer.adapter_type in ("dora-rows-xs", "dora-cols-xs"):
305
+ return ["M_xs", "magnitude"]
306
+ elif lora_layer.adapter_type == "bora-xs":
307
+ return ["M_xs", "magnitude_r", "magnitude_c"]
308
+ return []
309
+
310
+
311
+ def _prepare_for_multiple_lora(lora_layer):
312
+ lora_layer._multi_lora_slots = []
313
+
314
+
315
+ def _append_lora(lora_layer):
316
+ slot = {}
317
+ for name in _get_adapter_param_names(lora_layer):
318
+ slot[name] = nn.Parameter(getattr(lora_layer, name).clone())
319
+ lora_layer._multi_lora_slots.append(slot)
320
+
321
+
322
+ def load_multiple_lora(model, lora_state_dicts):
323
+ model.apply(apply_to_lora(_prepare_for_multiple_lora))
324
+ for state_dict in lora_state_dicts:
325
+ _ = model.load_state_dict(state_dict, strict=False)
326
+ model.apply(apply_to_lora(_append_lora))
327
+ return model
328
+
329
+
330
+ def _select_lora(lora_layer, index):
331
+ slot = lora_layer._multi_lora_slots[index]
332
+ for name, param in slot.items():
333
+ setattr(lora_layer, name, param)
334
+
335
+
336
+ def select_lora(model, index):
337
+ model.apply(apply_to_lora(lambda x: _select_lora(x, index)))
338
+ return model
339
+
340
+ # ------------------- helper function for merging multiple LoRAs -------------------
341
+
342
+ def merge_loras_into_base_model(model, lora_configurations):
343
+ """
344
+ Merges multiple LoRAs with specified application weights directly into the base model's weights.
345
+ Works with all adapter types (LoRA, DoRA, LoRA-XS) by using the parametrization's own forward.
346
+ After merging, the LoRA parametrization on each layer is disabled.
347
+
348
+ Args:
349
+ model: The PyTorch model with LoRAParametrization already applied (via add_lora).
350
+ lora_configurations: A list of dicts, each specifying a LoRA to merge:
351
+ [
352
+ {
353
+ 'name': 'style1',
354
+ 'state_dict': loaded_torch_state_dict,
355
+ 'application_weight': 0.7
356
+ },
357
+ ...
358
+ ]
359
+ """
360
+ import torch
361
+
362
+ if not lora_configurations:
363
+ return
364
+
365
+ # Collect all parametrized layers with their LoRA instances
366
+ parametrized_layers = []
367
+ for name, module in model.named_modules():
368
+ if not hasattr(module, "parametrizations"):
369
+ continue
370
+ for attr_name in list(module.parametrizations.keys()):
371
+ plist = module.parametrizations[attr_name]
372
+ for p in plist:
373
+ if isinstance(p, LoRAParametrization):
374
+ parametrized_layers.append((name, module, attr_name, p, plist.original))
375
+ break
376
+
377
+ # Initialize per-layer delta accumulators
378
+ deltas = {id(orig): torch.zeros_like(orig.data) for _, _, _, _, orig in parametrized_layers}
379
+
380
+ for lora_cfg in lora_configurations:
381
+ lora_sd = lora_cfg['state_dict']
382
+ app_weight = lora_cfg['application_weight']
383
+
384
+ # Load this LoRA's adapter weights into the model's parametrizations
385
+ model.load_state_dict(lora_sd, strict=False)
386
+
387
+ # Compute each layer's delta using the parametrization's own forward
388
+ with torch.no_grad():
389
+ for name, module, attr_name, lora_p, original in parametrized_layers:
390
+ merged = lora_p(original)
391
+ deltas[id(original)] += app_weight * (merged - original)
392
+
393
+ # Apply accumulated deltas to original weights and disable LoRA
394
+ with torch.no_grad():
395
+ for name, module, attr_name, lora_p, original in parametrized_layers:
396
+ original.data.add_(deltas[id(original)])
397
+ lora_p.disable_lora()
398
+
399
+ # ------------------- helper function for tying and untieing weights -------------------
400
+
401
+
402
+ def tie_weights(linear: nn.Linear, embedding: nn.Embedding):
403
+ """tie the weights of the linear layer and the embedding layer both with the same lora"""
404
+ lora_p = embedding.parametrizations.weight[0]
405
+ if lora_p.adapter_type != "lora":
406
+ raise NotImplementedError(f"tie_weights only supports standard LoRA, got adapter_type='{lora_p.adapter_type}'")
407
+ # this line below is optional if the original is already tied
408
+ embedding.parametrizations.weight.original = linear.parametrizations.weight.original
409
+ embedding.parametrizations.weight[0].lora_A = linear.parametrizations.weight[0].lora_B
410
+ embedding.parametrizations.weight[0].lora_B = linear.parametrizations.weight[0].lora_A
411
+
412
+
413
+ def untie_weights(linear: nn.Linear, embedding: nn.Embedding):
414
+ """untie the weights of the linear layer and the embedding layer"""
415
+ lora_p = embedding.parametrizations.weight[0]
416
+ if lora_p.adapter_type != "lora":
417
+ raise NotImplementedError(f"untie_weights only supports standard LoRA, got adapter_type='{lora_p.adapter_type}'")
418
+ embedding.parametrizations.weight.original = nn.Parameter(embedding.weight.original.clone())
419
+ embedding.parametrizations.weight[0].lora_A = nn.Parameter(embedding.parametrizations.weight[0].lora_A.clone())
420
+ embedding.parametrizations.weight[0].lora_B = nn.Parameter(embedding.parametrizations.weight[0].lora_B.clone())
stable_audio_3/models/pretransforms.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ from einops import rearrange
4
+ from torchaudio.transforms import Resample
5
+ from .blocks import ResidualUnit, WNConv1d
6
+
7
+
8
+ class AutoencoderPretransform(nn.Module):
9
+ def __init__(self, model, scale=1.0, iterate_batch=False, chunked=False):
10
+ super().__init__()
11
+ self.model = model
12
+ self.model.requires_grad_(False).eval()
13
+ self.scale = scale
14
+ self.downsampling_ratio = model.downsampling_ratio
15
+ self.io_channels = model.io_channels
16
+ self.enable_grad = False
17
+ self.iterate_batch = iterate_batch
18
+ self.chunked = chunked
19
+
20
+ def encode(self, x, **kwargs):
21
+ return self.model.encode_audio(x, chunked=self.chunked, iterate_batch=self.iterate_batch, **kwargs) / self.scale
22
+
23
+ def decode(self, z, chunked=None, **kwargs):
24
+ chunked = self.chunked if chunked is None else chunked
25
+ return self.model.decode_audio(z * self.scale, chunked=chunked, iterate_batch=self.iterate_batch, **kwargs)
26
+
27
+ def fold_channels_into_batch(x):
28
+ x = rearrange(x, 'b c ... -> (b c) ...')
29
+ return x
30
+
31
+ def unfold_channels_from_batch(x, channels):
32
+ if channels == 1:
33
+ return x.unsqueeze(1)
34
+ x = rearrange(x, '(b c) ... -> b c ...', c = channels)
35
+ return x
36
+
37
+
38
+ class PatchedPretransform(nn.Module):
39
+ def __init__(self, channels, patch_size, oversampling = 1, postfilter_channels = 0, **kwargs):
40
+ super().__init__()
41
+ self.channels = channels
42
+ self.patch_size = patch_size
43
+ self.oversampling = oversampling
44
+
45
+ self.downsampling_ratio = patch_size
46
+ self.io_channels = channels
47
+ self.encoded_channels = channels * patch_size
48
+ self.enable_grad = False
49
+
50
+ if self.oversampling > 1:
51
+ self.input_upsampler = Resample(1, self.oversampling)
52
+ self.output_downsampler = Resample(self.oversampling, 1)
53
+
54
+ if postfilter_channels > 0:
55
+ self.postfilter = nn.Sequential(
56
+ WNConv1d(in_channels=channels, out_channels=postfilter_channels, kernel_size=7, padding=3, bias=True),
57
+ ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
58
+ dilation=1, use_snake=True, bias = True),
59
+ ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
60
+ dilation=3, use_snake=True, bias = True),
61
+ ResidualUnit(in_channels=postfilter_channels, out_channels=postfilter_channels,
62
+ dilation=9, use_snake=True, bias = True),
63
+ WNConv1d(in_channels=postfilter_channels, out_channels=channels, kernel_size=7, padding=3, bias=False))
64
+
65
+ def _pad(self, x):
66
+ seq_len = x.shape[-1]
67
+ pad_len = (self.patch_size - (seq_len % self.patch_size)) % self.patch_size
68
+ if pad_len > 0:
69
+ x = torch.cat([x, torch.zeros_like(x[:, :, :pad_len])], dim=-1)
70
+ return x
71
+
72
+ def encode(self, x):
73
+ if self.oversampling > 1:
74
+ x = self.input_upsampler(x)
75
+ x = self._pad(x)
76
+ x = rearrange(x, "b c (l h) -> b (c h) l", h=self.patch_size)
77
+ return x
78
+ def decode(self, x):
79
+ x = rearrange(x, "b (c h) l -> b c (l h)", h=self.patch_size)
80
+ if hasattr(self, 'postfilter'):
81
+ x = self.postfilter(x)
82
+ if self.oversampling > 1:
83
+ x = self.output_downsampler(x)
84
+ return x
stable_audio_3/models/transformer.py ADDED
@@ -0,0 +1,1272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import reduce, partial
2
+ from packaging import version
3
+ import logging
4
+ import math
5
+
6
+ from einops import rearrange, repeat
7
+ from einops.layers.torch import Rearrange
8
+ import torch
9
+ import torch.nn.functional as F
10
+ from torch import nn, einsum
11
+ from torch.amp import autocast
12
+ from torch.nn.utils.parametrizations import weight_norm
13
+ from typing import Callable, Literal, Optional
14
+ try:
15
+ from torch.nn.attention.flex_attention import flex_attention, create_block_mask
16
+ flex_attention_available = True
17
+ except ImportError:
18
+ flex_attention = None
19
+ create_block_mask = None
20
+ flex_attention_available = False
21
+
22
+ try:
23
+ from flash_attn import flash_attn_func, flash_attn_kvpacked_func
24
+ except ImportError as e:
25
+ print(e)
26
+ print('flash_attn not installed, disabling Flash Attention')
27
+ flash_attn_kvpacked_func = None
28
+ flash_attn_func = None
29
+
30
+ try:
31
+ from flash_attn import flash_attn_varlen_func
32
+ from flash_attn.bert_padding import pad_input, unpad_input, index_first_axis
33
+ except ImportError as e:
34
+ print(e)
35
+ print('flash_attn varlen/bert_padding not available, disabling varlen attention')
36
+ flash_attn_varlen_func = None
37
+ pad_input = None
38
+ unpad_input = None
39
+ index_first_axis = None
40
+
41
+
42
+ def precompute_varlen_metadata(padding_mask: torch.Tensor):
43
+ """
44
+ Precompute varlen attention metadata once to avoid recomputation in every attention layer.
45
+
46
+ Args:
47
+ padding_mask: Boolean tensor of shape (batch, seq_len) where True = valid
48
+
49
+ Returns:
50
+ Dict with cu_seqlens, max_seqlen, indices, batch_size, seq_len for use in attention
51
+ """
52
+ if padding_mask is None or unpad_input is None:
53
+ return None
54
+
55
+ batch_size, seq_len = padding_mask.shape
56
+
57
+ # Compute cumulative sequence lengths (same for all of q, k, v)
58
+ seqlens = padding_mask.sum(dim=-1, dtype=torch.int32)
59
+ cu_seqlens = F.pad(torch.cumsum(seqlens, dim=0, dtype=torch.int32), (1, 0))
60
+ max_seqlen = seqlens.max().item()
61
+
62
+ # Compute indices for gathering valid tokens
63
+ # indices maps from packed position -> original (batch, seq) position
64
+ indices = torch.nonzero(padding_mask.flatten(), as_tuple=False).flatten()
65
+
66
+ return {
67
+ "cu_seqlens": cu_seqlens,
68
+ "max_seqlen": max_seqlen,
69
+ "indices": indices,
70
+ "batch_size": batch_size,
71
+ "seq_len": seq_len,
72
+ }
73
+
74
+ from .utils import compile
75
+
76
+
77
+ def _left_pad_to_match(emb, target_len):
78
+ """Left-pad or right-trim emb along seq dim to match target_len.
79
+
80
+ Used for local conditioning embeddings that need to align with x
81
+ without affecting prepended tokens (memory tokens, global cond, etc.).
82
+ """
83
+ emb_len = emb.shape[-2]
84
+ if emb_len < target_len:
85
+ return F.pad(emb, (0, 0, target_len - emb_len, 0), value=0.)
86
+ elif emb_len > target_len:
87
+ return emb[:, -target_len:, :]
88
+ return emb
89
+
90
+ if flex_attention_available:
91
+ try:
92
+ torch._dynamo.config.cache_size_limit = 5000
93
+ flex_attention_compiled = torch.compile(flex_attention, dynamic=False, mode="max-autotune-no-cudagraphs")
94
+ except Exception as e:
95
+ logging.debug(f"Could not compile flex_attention, using uncompiled version: {e}")
96
+ flex_attention_compiled = flex_attention
97
+ else:
98
+ flex_attention_compiled = None
99
+
100
+
101
+ # Cache band block_masks for sliding-window attention fallback (flex_attention path).
102
+ # Keyed by (seq_q, seq_k, w_left, w_right, device). create_block_mask is expensive
103
+ # but the result is reused across all transformer layers and forward passes.
104
+ _SLIDING_WINDOW_BLOCK_MASK_CACHE = {}
105
+
106
+ def _get_sliding_window_block_mask(seq_q, seq_k, w_left, w_right, device):
107
+ key = (seq_q, seq_k, int(w_left), int(w_right), str(device))
108
+ bm = _SLIDING_WINDOW_BLOCK_MASK_CACHE.get(key)
109
+ if bm is None:
110
+ wl, wr = int(w_left), int(w_right)
111
+ def _band_mod(b, h, q_idx, kv_idx):
112
+ delta = kv_idx - q_idx
113
+ return (delta >= -wl) & (delta <= wr)
114
+ bm = create_block_mask(_band_mod, B=None, H=None, Q_LEN=seq_q, KV_LEN=seq_k, device=device)
115
+ _SLIDING_WINDOW_BLOCK_MASK_CACHE[key] = bm
116
+ return bm
117
+
118
+ def _sliding_window_additive_mask(seq_q, seq_k, w_left, w_right, device, dtype):
119
+ """Build a (seq_q, seq_k) additive mask for masked SDPA fallback.
120
+ 0 inside the band [i - w_left, i + w_right], -inf outside.
121
+ """
122
+ ii = torch.arange(seq_q, device=device)
123
+ jj = torch.arange(seq_k, device=device)
124
+ delta = jj[None, :] - ii[:, None]
125
+ in_band = (delta >= -int(w_left)) & (delta <= int(w_right))
126
+ mask = torch.zeros((seq_q, seq_k), dtype=dtype, device=device)
127
+ return mask.masked_fill(~in_band, float('-inf'))
128
+
129
+
130
+ # Chunked-halo SDPA fallback. Math-equivalent to masked SDPA with a band
131
+ # mask, but processes queries in non-overlapping chunks with a (w_left,
132
+ # w_right) halo of keys/values on each side — every query stays inside its
133
+ # chunk's softmax. Avoids materializing the O(N^2) mask.
134
+ #
135
+ # At realistic SAME-L decoder shapes (N=69632, W=17, packed sequence is
136
+ # latent_length * (stride+1)): ~34x faster than full masked SDPA, and
137
+ # ~140x less peak mask memory (~1 MB per chunk vs 9.7 GB for one N x N mask).
138
+ # Chunk size is a tunable; 1024 is a good default at typical pretransform
139
+ # decoder shapes. Larger chunks waste more compute on out-of-band tiles;
140
+ # smaller chunks suffer from launch overhead.
141
+ _SLIDING_WINDOW_CHUNK_SIZE = 1024
142
+
143
+ def _sliding_window_chunked_halo_sdpa(q, k, v, w_left, w_right, chunk_size=_SLIDING_WINDOW_CHUNK_SIZE):
144
+ B, H, N, D = q.shape
145
+ outs = []
146
+ for q_start in range(0, N, chunk_size):
147
+ q_end = min(q_start + chunk_size, N)
148
+ k_start = max(0, q_start - int(w_left))
149
+ k_end = min(N, q_end + int(w_right))
150
+ q_c = q[..., q_start:q_end, :]
151
+ k_c = k[..., k_start:k_end, :]
152
+ v_c = v[..., k_start:k_end, :]
153
+ q_idx = torch.arange(q_start, q_end, device=q.device)
154
+ k_idx = torch.arange(k_start, k_end, device=q.device)
155
+ delta = k_idx[None, :] - q_idx[:, None]
156
+ in_band = (delta >= -int(w_left)) & (delta <= int(w_right))
157
+ mask = torch.zeros(delta.shape, dtype=q.dtype, device=q.device).masked_fill(~in_band, float('-inf'))
158
+ outs.append(F.scaled_dot_product_attention(q_c, k_c, v_c, attn_mask=mask, is_causal=False))
159
+ return torch.cat(outs, dim=-2)
160
+
161
+
162
+ def checkpoint(function, *args, **kwargs):
163
+ kwargs.setdefault("use_reentrant", False)
164
+ # Preserve autocast context during recomputation to avoid dtype mismatches
165
+ if "context_fn" not in kwargs:
166
+ from torch.amp import autocast
167
+ import functools
168
+ # Get current autocast state
169
+ if torch.is_autocast_enabled():
170
+ dtype = torch.get_autocast_dtype('cuda')
171
+ def get_contexts():
172
+ return (
173
+ autocast('cuda', dtype=dtype),
174
+ autocast('cuda', dtype=dtype),
175
+ )
176
+ kwargs["context_fn"] = get_contexts
177
+ return torch.utils.checkpoint.checkpoint(function, *args, **kwargs)
178
+
179
+
180
+ # Copied and modified from https://github.com/lucidrains/x-transformers/blob/main/x_transformers/attend.py under MIT License
181
+ # License can be found in LICENSES/LICENSE_XTRANSFORMERS.txt
182
+
183
+ def create_causal_mask(i, j, device):
184
+ return torch.ones((i, j), device = device, dtype = torch.bool).triu(j - i + 1)
185
+
186
+ def or_reduce(masks):
187
+ head, *body = masks
188
+ for rest in body:
189
+ head = head | rest
190
+ return head
191
+
192
+ # positional embeddings
193
+
194
+ class AbsolutePositionalEmbedding(nn.Module):
195
+ def __init__(self, dim, max_seq_len):
196
+ super().__init__()
197
+ self.scale = dim ** -0.5
198
+ self.max_seq_len = max_seq_len
199
+ self.emb = nn.Embedding(max_seq_len, dim)
200
+
201
+ def forward(self, x, pos = None, seq_start_pos = None):
202
+ seq_len, device = x.shape[1], x.device
203
+ assert seq_len <= self.max_seq_len, f'you are passing in a sequence length of {seq_len} but your absolute positional embedding has a max sequence length of {self.max_seq_len}'
204
+
205
+ if pos is None:
206
+ pos = torch.arange(seq_len, device = device)
207
+
208
+ if seq_start_pos is not None:
209
+ pos = (pos - seq_start_pos[..., None]).clamp(min = 0)
210
+
211
+ pos_emb = self.emb(pos)
212
+ pos_emb = pos_emb * self.scale
213
+ return pos_emb
214
+
215
+ class ScaledSinusoidalEmbedding(nn.Module):
216
+ def __init__(self, dim, theta = 10000):
217
+ super().__init__()
218
+ assert (dim % 2) == 0, 'dimension must be divisible by 2'
219
+ self.scale = nn.Parameter(torch.ones(1) * dim ** -0.5)
220
+
221
+ half_dim = dim // 2
222
+ freq_seq = torch.arange(half_dim).float() / half_dim
223
+ inv_freq = theta ** -freq_seq
224
+ self.register_buffer('inv_freq', inv_freq, persistent = False)
225
+
226
+ def forward(self, x, pos = None, seq_start_pos = None):
227
+ seq_len, device = x.shape[1], x.device
228
+
229
+ if pos is None:
230
+ pos = torch.arange(seq_len, device = device)
231
+
232
+ if seq_start_pos is not None:
233
+ pos = pos - seq_start_pos[..., None]
234
+
235
+ emb = einsum('i, j -> i j', pos, self.inv_freq)
236
+ emb = torch.cat((emb.sin(), emb.cos()), dim = -1)
237
+ return emb * self.scale
238
+
239
+ class RotaryEmbedding(nn.Module):
240
+ def __init__(
241
+ self,
242
+ dim,
243
+ use_xpos = False,
244
+ scale_base = 512,
245
+ interpolation_factor = 1.,
246
+ base = 10000,
247
+ base_rescale_factor = 1.
248
+ ):
249
+ super().__init__()
250
+ # proposed by reddit user bloc97, to rescale rotary embeddings to longer sequence length without fine-tuning
251
+ # has some connection to NTK literature
252
+ # https://www.reddit.com/r/LocalLLaMA/comments/14lz7j5/ntkaware_scaled_rope_allows_llama_models_to_have/
253
+ base *= base_rescale_factor ** (dim / (dim - 2))
254
+
255
+ inv_freq = 1. / (base ** (torch.arange(0, dim, 2).float() / dim))
256
+ self.register_buffer('inv_freq', inv_freq)
257
+
258
+ assert interpolation_factor >= 1.
259
+ self.interpolation_factor = interpolation_factor
260
+
261
+ if not use_xpos:
262
+ self.register_buffer('scale', None)
263
+ return
264
+
265
+ scale = (torch.arange(0, dim, 2) + 0.4 * dim) / (1.4 * dim)
266
+
267
+ self.scale_base = scale_base
268
+ self.register_buffer('scale', scale)
269
+
270
+ def forward_from_seq_len(self, seq_len):
271
+ device = self.inv_freq.device
272
+
273
+ t = torch.arange(seq_len, device = device)
274
+ return self.forward(t)
275
+
276
+ @autocast("cuda", enabled = False)
277
+ def forward(self, t):
278
+ device = self.inv_freq.device
279
+
280
+ t = t.to(torch.float32)
281
+
282
+ t = t / self.interpolation_factor
283
+
284
+ freqs = torch.einsum('i , j -> i j', t, self.inv_freq)
285
+ freqs = torch.cat((freqs, freqs), dim = -1)
286
+
287
+ if self.scale is None:
288
+ return freqs, 1.
289
+
290
+ power = (torch.arange(seq_len, device = device) - (seq_len // 2)) / self.scale_base
291
+ scale = self.scale ** rearrange(power, 'n -> n 1')
292
+ scale = torch.cat((scale, scale), dim = -1)
293
+
294
+ return freqs, scale
295
+
296
+ def rotate_half(x):
297
+ x = rearrange(x, '... (j d) -> ... j d', j = 2)
298
+ x1, x2 = x.unbind(dim = -2)
299
+ return torch.cat((-x2, x1), dim = -1)
300
+
301
+
302
+ @autocast("cuda", enabled = False)
303
+ def apply_rotary_pos_emb(t, freqs, scale = 1):
304
+ out_dtype = t.dtype
305
+
306
+ # cast to float32 if necessary for numerical stability
307
+ dtype = reduce(torch.promote_types, (t.dtype, freqs.dtype, torch.float32))
308
+ rot_dim, seq_len = freqs.shape[-1], t.shape[-2]
309
+ freqs, t = freqs.to(dtype), t.to(dtype)
310
+ freqs = freqs[-seq_len:, :]
311
+
312
+ if t.ndim == 4 and freqs.ndim == 3:
313
+ freqs = rearrange(freqs, 'b n d -> b 1 n d')
314
+
315
+ # partial rotary embeddings, Wang et al. GPT-J
316
+ t, t_unrotated = t[..., :rot_dim], t[..., rot_dim:]
317
+
318
+ t = (t * freqs.cos() * scale ) + (rotate_half(t) * freqs.sin() * scale)
319
+
320
+ t, t_unrotated = t.to(out_dtype), t_unrotated.to(out_dtype)
321
+
322
+ return torch.cat((t, t_unrotated), dim = -1)
323
+
324
+ # norms
325
+ class DynamicTanh(nn.Module):
326
+ def __init__(self, dim, init_alpha=4.0, **kwargs):
327
+ super().__init__()
328
+ self.alpha = nn.Parameter(torch.ones(1) * init_alpha)
329
+ self.gamma = nn.Parameter(torch.ones(dim))
330
+ self.beta = nn.Parameter(torch.zeros(dim))
331
+
332
+ def forward(self, x):
333
+ x = F.tanh(self.alpha * x)
334
+ return self.gamma * x + self.beta
335
+
336
+ class RunningInstanceNorm(nn.Module):
337
+ def __init__(self, dim, momentum = 0.99, eps = 1e-4, saturate = True, trainable_gain = True):
338
+ super().__init__()
339
+ self.register_buffer("running_mean", torch.zeros(1,1,dim))
340
+ self.register_buffer("running_std", torch.ones(1,1,dim))
341
+ self.saturate = saturate
342
+ self.eps = eps
343
+ self.momentum = momentum
344
+ self.dim = dim
345
+ self.trainable_gain = trainable_gain
346
+ if self.trainable_gain:
347
+ self.gain = nn.Parameter(torch.ones(1))
348
+
349
+ def _update_stats(self, x):
350
+ self.running_mean = self.running_mean * self.momentum + x.detach().mean(dim = [0,1]).view(1, 1, self.dim) * (1 - self.momentum)
351
+ self.running_std = (self.running_std * self.momentum + x.detach().std(dim = [0,1]).view(1, 1, self.dim) * (1 - self.momentum)).clip(min = self.eps)
352
+
353
+ def forward(self, x):
354
+ if self.training:
355
+ self._update_stats(x)
356
+ x = (x - self.running_mean) / self.running_std
357
+ if self.saturate:
358
+ x = torch.asinh(x)
359
+ if self.trainable_gain:
360
+ x = x * self.gain
361
+ return x
362
+
363
+ class LayerNorm(nn.Module):
364
+ def __init__(self, dim, bias=False, fix_scale=False, force_fp32=False, eps=1e-5):
365
+ """
366
+ bias-less layernorm has been shown to be more stable. most newer models have moved towards rmsnorm, also bias-less
367
+ """
368
+ super().__init__()
369
+
370
+ if fix_scale:
371
+ self.register_buffer("gamma", torch.ones(dim))
372
+ else:
373
+ self.gamma = nn.Parameter(torch.ones(dim))
374
+
375
+ if bias:
376
+ self.beta = nn.Parameter(torch.zeros(dim))
377
+ else:
378
+ self.register_buffer("beta", torch.zeros(dim))
379
+
380
+ self.eps = eps
381
+
382
+ self.force_fp32 = force_fp32
383
+
384
+ #@autocast("cuda", enabled = False)
385
+ def forward(self, x):
386
+ if not self.force_fp32:
387
+ return F.layer_norm(x, x.shape[-1:], weight=self.gamma, bias=self.beta, eps=self.eps)
388
+ else:
389
+ output = F.layer_norm(x.float(), x.shape[-1:], weight=self.gamma.float(), bias=self.beta.float(), eps=self.eps)
390
+ return output.to(x.dtype)
391
+
392
+ class RMSNorm(nn.Module):
393
+ def __init__(self, dim, fix_scale=False, force_fp32=False, eps=1e-5):
394
+ super().__init__()
395
+
396
+ if fix_scale:
397
+ self.register_buffer("gamma", torch.ones(dim))
398
+ else:
399
+ self.gamma = nn.Parameter(torch.ones(dim))
400
+
401
+ self.eps = eps
402
+
403
+ self.force_fp32 = force_fp32
404
+
405
+ def forward(self, x):
406
+ if not self.force_fp32:
407
+ return F.rms_norm(x, x.shape[-1:], weight=self.gamma, eps=self.eps)
408
+ else:
409
+ output = F.rms_norm(x.float(), x.shape[-1:], weight=self.gamma.float(), eps=self.eps)
410
+ return output.to(x.dtype)
411
+
412
+ class LayerScale(nn.Module):
413
+ def __init__(self, dim, init_val = 1e-5):
414
+ super().__init__()
415
+ self.scale = nn.Parameter(torch.full([dim], init_val))
416
+ def forward(self, x):
417
+ return x * self.scale
418
+
419
+ # feedforward
420
+
421
+ class GLU(nn.Module):
422
+ def __init__(
423
+ self,
424
+ dim_in,
425
+ dim_out,
426
+ activation: Callable,
427
+ use_conv = False,
428
+ conv_kernel_size = 3,
429
+ ):
430
+ super().__init__()
431
+ self.act = activation
432
+ self.proj = nn.Linear(dim_in, dim_out * 2) if not use_conv else nn.Conv1d(dim_in, dim_out * 2, conv_kernel_size, padding = (conv_kernel_size // 2))
433
+ self.use_conv = use_conv
434
+
435
+ def forward(self, x):
436
+ if self.use_conv:
437
+ x = rearrange(x, 'b n d -> b d n')
438
+ x = self.proj(x)
439
+ x = rearrange(x, 'b d n -> b n d')
440
+ else:
441
+ x = self.proj(x)
442
+
443
+ x, gate = x.chunk(2, dim = -1)
444
+ return x * self.act(gate)
445
+
446
+ class Sin(nn.Module):
447
+ def __init__(self):
448
+ super().__init__()
449
+
450
+ def forward(self, x):
451
+ return torch.sin(3.14159265359 * x)
452
+
453
+ class FeedForward(nn.Module):
454
+ def __init__(
455
+ self,
456
+ dim,
457
+ dim_out = None,
458
+ mult = 4,
459
+ no_bias = False,
460
+ glu = True,
461
+ use_conv = False,
462
+ conv_kernel_size = 3,
463
+ zero_init_output = True,
464
+ sinusoidal = False
465
+ ):
466
+ super().__init__()
467
+ inner_dim = int(dim * mult)
468
+
469
+ # Default to SwiGLU
470
+
471
+ activation = nn.SiLU() if not sinusoidal else Sin()
472
+
473
+ dim_out = dim if dim_out is None else dim_out
474
+
475
+ if glu:
476
+ linear_in = GLU(dim, inner_dim, activation)
477
+ else:
478
+ linear_in = nn.Sequential(
479
+ Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
480
+ nn.Linear(dim, inner_dim, bias = not no_bias) if not use_conv else nn.Conv1d(dim, inner_dim, conv_kernel_size, padding = (conv_kernel_size // 2), bias = not no_bias),
481
+ Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
482
+ activation
483
+ )
484
+
485
+ linear_out = nn.Linear(inner_dim, dim_out, bias = not no_bias) if not use_conv else nn.Conv1d(inner_dim, dim_out, conv_kernel_size, padding = (conv_kernel_size // 2), bias = not no_bias)
486
+
487
+ # init last linear layer to 0
488
+ if zero_init_output:
489
+ nn.init.zeros_(linear_out.weight)
490
+ if not no_bias:
491
+ nn.init.zeros_(linear_out.bias)
492
+
493
+
494
+ self.ff = nn.Sequential(
495
+ linear_in,
496
+ Rearrange('b d n -> b n d') if use_conv else nn.Identity(),
497
+ linear_out,
498
+ Rearrange('b n d -> b d n') if use_conv else nn.Identity(),
499
+ )
500
+
501
+ #@compile
502
+ def forward(self, x, varlen_metadata=None):
503
+ if varlen_metadata is not None and index_first_axis is not None and pad_input is not None:
504
+ # Pack valid tokens for efficient FFN computation (skip padding tokens)
505
+ # Padding positions become zeros after unpack, which is fine since FFN output
506
+ # is added to residual, preserving values at padding positions
507
+ batch_size = varlen_metadata["batch_size"]
508
+ seq_len = varlen_metadata["seq_len"]
509
+ indices = varlen_metadata["indices"]
510
+ dim = x.shape[-1]
511
+
512
+ # Pack to (N_valid, D)
513
+ x_packed = index_first_axis(x.reshape(-1, dim), indices)
514
+
515
+ # FFN on packed representation with pseudo-batch dim
516
+ x_packed = self.ff(x_packed.unsqueeze(0)).squeeze(0)
517
+
518
+ # Unpack back to (B, T, D)
519
+ return pad_input(x_packed, indices, batch_size, seq_len)
520
+ else:
521
+ return self.ff(x)
522
+
523
+ class Attention(nn.Module):
524
+ def __init__(
525
+ self,
526
+ dim,
527
+ dim_heads = 64,
528
+ dim_context = None,
529
+ causal = False,
530
+ zero_init_output=True,
531
+ qk_norm_eps = 1e-6,
532
+ qk_norm: Literal['l2', 'ln', 'rms', 'dyt', 'none'] = 'none',
533
+ differential = False,
534
+ feat_scale = False
535
+ ):
536
+ super().__init__()
537
+ self.dim = dim
538
+ self.dim_heads = dim_heads
539
+
540
+ self.differential = differential
541
+
542
+ dim_kv = dim_context if dim_context is not None else dim
543
+
544
+ self.num_heads = dim // dim_heads
545
+ self.kv_heads = dim_kv // dim_heads
546
+
547
+ if dim_context is not None:
548
+ if differential:
549
+ self.to_q = nn.Linear(dim, dim * 2, bias=False)
550
+ self.to_kv = nn.Linear(dim_kv, dim_kv * 3, bias=False)
551
+ else:
552
+ self.to_q = nn.Linear(dim, dim, bias=False)
553
+ self.to_kv = nn.Linear(dim_kv, dim_kv * 2, bias=False)
554
+ else:
555
+ if differential:
556
+ self.to_qkv = nn.Linear(dim, dim * 5, bias=False)
557
+ else:
558
+ self.to_qkv = nn.Linear(dim, dim * 3, bias=False)
559
+
560
+ self.to_out = nn.Linear(dim, dim, bias=False)
561
+
562
+ if zero_init_output:
563
+ nn.init.zeros_(self.to_out.weight)
564
+
565
+ if qk_norm not in ['l2', 'ln', 'rms', 'dyt','none']:
566
+ raise ValueError(f'qk_norm must be one of ["l2", "ln", "rms" ,"dyt", "none"], got {qk_norm}')
567
+
568
+ self.qk_norm = qk_norm
569
+ self.qk_norm_eps = qk_norm_eps
570
+
571
+ if self.qk_norm == "ln":
572
+ self.q_norm = nn.LayerNorm(dim_heads, elementwise_affine=True, eps=qk_norm_eps)
573
+ self.k_norm = nn.LayerNorm(dim_heads, elementwise_affine=True, eps=qk_norm_eps)
574
+ elif self.qk_norm == "rms":
575
+ self.q_norm = RMSNorm(dim_heads, eps=qk_norm_eps)
576
+ self.k_norm = RMSNorm(dim_heads, eps=qk_norm_eps)
577
+ elif self.qk_norm == 'dyt':
578
+ self.q_norm = DynamicTanh(dim_heads)
579
+ self.k_norm = DynamicTanh(dim_heads)
580
+
581
+ self.feat_scale = feat_scale
582
+
583
+ if self.feat_scale:
584
+ self.lambda_dc = nn.Parameter(torch.zeros(dim))
585
+ self.lambda_hf = nn.Parameter(torch.zeros(dim))
586
+
587
+ self.causal = causal
588
+
589
+ @compile
590
+ def apply_qk_layernorm(self, q, k):
591
+ q_type = q.dtype
592
+ k_type = k.dtype
593
+ q = self.q_norm(q).to(q_type)
594
+ k = self.k_norm(k).to(k_type)
595
+ return q, k
596
+
597
+
598
+ def apply_attn(self, q, k, v, causal = None, flex_attention_block_mask = None, flex_attention_score_mod = None, flash_attn_sliding_window = None, padding_mask = None, varlen_metadata = None):
599
+
600
+ if self.num_heads != self.kv_heads:
601
+ # Repeat interleave kv_heads to match q_heads for grouped query attention
602
+ heads_per_kv_head = self.num_heads // self.kv_heads
603
+ k, v = map(lambda t: t.repeat_interleave(heads_per_kv_head, dim = 1), (k, v))
604
+
605
+ flash_attn_available = flash_attn_func is not None
606
+ flash_attn_varlen_available = flash_attn_varlen_func is not None and index_first_axis is not None
607
+
608
+ if causal and (flex_attention_block_mask is not None or flex_attention_score_mod is not None):
609
+ flex_attention_block_mask = None
610
+ flex_attention_score_mod = None
611
+
612
+ if flex_attention_block_mask is not None or flex_attention_score_mod is not None:
613
+ # Flex attention path - use V-zeroing for padding mask
614
+ if padding_mask is not None:
615
+ mask_expanded = padding_mask.unsqueeze(1).unsqueeze(-1).to(v.dtype)
616
+ v = v * mask_expanded
617
+ out = flex_attention_compiled(q,k,v,
618
+ block_mask = flex_attention_block_mask,
619
+ score_mod = flex_attention_score_mod)
620
+ elif flash_attn_available and varlen_metadata is not None and flash_attn_varlen_available:
621
+ # Flash attention with varlen using precomputed metadata (fast path)
622
+ batch_size = varlen_metadata["batch_size"]
623
+ seq_len = varlen_metadata["seq_len"]
624
+ cu_seqlens = varlen_metadata["cu_seqlens"]
625
+ max_seqlen = varlen_metadata["max_seqlen"]
626
+ indices = varlen_metadata["indices"]
627
+
628
+ fa_dtype_in = q.dtype
629
+ # Rearrange to (B, T, H, D) for flash_attn
630
+ q, k, v = map(lambda t: rearrange(t, 'b h n d -> b n h d'), (q, k, v))
631
+
632
+ if fa_dtype_in != torch.float16 and fa_dtype_in != torch.bfloat16:
633
+ q, k, v = map(lambda t: t.to(torch.float16), (q, k, v))
634
+
635
+ # Pack q, k, v using precomputed indices (much faster than calling unpad_input 3x)
636
+ num_heads, head_dim = q.shape[2], q.shape[3]
637
+ q_unpad = index_first_axis(q.reshape(-1, num_heads, head_dim), indices)
638
+ k_unpad = index_first_axis(k.reshape(-1, num_heads, head_dim), indices)
639
+ v_unpad = index_first_axis(v.reshape(-1, num_heads, head_dim), indices)
640
+
641
+ out_unpad = flash_attn_varlen_func(
642
+ q_unpad, k_unpad, v_unpad,
643
+ cu_seqlens, cu_seqlens,
644
+ max_seqlen, max_seqlen,
645
+ causal=causal if causal is not None else False,
646
+ window_size=flash_attn_sliding_window if flash_attn_sliding_window is not None else (-1, -1),
647
+ )
648
+
649
+ # Pad output back to original shape
650
+ out = pad_input(out_unpad, indices, batch_size, seq_len)
651
+ out = rearrange(out.to(fa_dtype_in), 'b n h d -> b h n d')
652
+ elif flash_attn_available:
653
+ # Standard flash attention (no padding mask, or varlen imports not available)
654
+ # Apply V-zeroing fallback if padding_mask provided but we couldn't use varlen
655
+ if padding_mask is not None:
656
+ mask_expanded = padding_mask.unsqueeze(1).unsqueeze(-1).to(v.dtype)
657
+ v = v * mask_expanded
658
+ fa_dtype_in = q.dtype
659
+ q, k, v = map(lambda t: rearrange(t, 'b h n d -> b n h d'), (q, k, v))
660
+
661
+ if fa_dtype_in != torch.float16 and fa_dtype_in != torch.bfloat16:
662
+ q, k, v = map(lambda t: t.to(torch.float16), (q, k, v))
663
+
664
+ out = flash_attn_func(q, k, v, causal = causal, window_size=flash_attn_sliding_window if (flash_attn_sliding_window is not None) else [-1,-1])
665
+
666
+ out = rearrange(out.to(fa_dtype_in), 'b n h d -> b h n d')
667
+ else:
668
+ # No flash-attn available. Sliding-window fallback cascade:
669
+ # Tier 2: flex_attention with band block_mask (best when torch.compile works)
670
+ # Tier 3: chunked-halo masked SDPA (math-equivalent, ~30x faster than tier 4)
671
+ # Tier 4: full masked SDPA (N x N mask) (last resort; high memory)
672
+ # For the no-sliding-window case, fall through to plain SDPA full attention.
673
+ # All apply V-zeroing for padding masks (cheap and equivalent to masking
674
+ # those positions out of attention output).
675
+ if padding_mask is not None:
676
+ mask_expanded = padding_mask.unsqueeze(1).unsqueeze(-1).to(v.dtype)
677
+ v = v * mask_expanded
678
+ if flash_attn_sliding_window is not None:
679
+ seq_q, seq_k = q.shape[2], k.shape[2]
680
+ wl, wr = flash_attn_sliding_window
681
+ handled = False
682
+ if flex_attention_available and flex_attention_compiled is not None:
683
+ try:
684
+ bm = _get_sliding_window_block_mask(seq_q, seq_k, wl, wr, q.device)
685
+ out = flex_attention_compiled(q, k, v, block_mask=bm)
686
+ handled = True
687
+ except Exception as _flex_err:
688
+ logging.debug(f"flex_attention failed, trying chunked-halo SDPA: {_flex_err}")
689
+ if not handled:
690
+ try:
691
+ out = _sliding_window_chunked_halo_sdpa(q, k, v, wl, wr)
692
+ handled = True
693
+ except Exception as _chunk_err:
694
+ logging.debug(f"chunked-halo SDPA failed, falling back to full masked SDPA: {_chunk_err}")
695
+ if not handled:
696
+ add_mask = _sliding_window_additive_mask(seq_q, seq_k, wl, wr, q.device, q.dtype)
697
+ out = F.scaled_dot_product_attention(q, k, v, attn_mask=add_mask, is_causal=False)
698
+ else:
699
+ out = F.scaled_dot_product_attention(q, k, v, is_causal=causal if causal is not None else False)
700
+ return out
701
+
702
+
703
+ #@compile
704
+ def forward(
705
+ self,
706
+ x,
707
+ context = None,
708
+ rotary_pos_emb = None,
709
+ rotary_pos_emb_k = None,
710
+ causal = None,
711
+ flex_attention_block_mask = None,
712
+ flex_attention_score_mod = None,
713
+ flash_attn_sliding_window = None,
714
+ padding_mask = None,
715
+ varlen_metadata = None,
716
+ ):
717
+ h, kv_h, has_context = self.num_heads, self.kv_heads, context is not None
718
+
719
+ kv_input = context if has_context else x
720
+
721
+ if hasattr(self, 'to_q'):
722
+ # Use separate linear projections for q and k/v
723
+ if self.differential:
724
+ q, q_diff = self.to_q(x).chunk(2, dim=-1)
725
+ q, q_diff = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), (q, q_diff))
726
+ q = torch.stack([q, q_diff], dim = 1)
727
+ k, k_diff, v = self.to_kv(kv_input).chunk(3, dim=-1)
728
+ k, k_diff, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = kv_h), (k, k_diff, v))
729
+ k = torch.stack([k, k_diff], dim = 1)
730
+ else:
731
+ q = self.to_q(x)
732
+ q = rearrange(q, 'b n (h d) -> b h n d', h = h)
733
+ k, v = self.to_kv(kv_input).chunk(2, dim=-1)
734
+ k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = kv_h), (k, v))
735
+ else:
736
+ # Use fused linear projection
737
+ if self.differential:
738
+ q, k, v, q_diff, k_diff = self.to_qkv(x).chunk(5, dim=-1)
739
+ q, k, v, q_diff, k_diff = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), (q, k, v, q_diff, k_diff))
740
+ q = torch.stack([q, q_diff], dim = 1)
741
+ k = torch.stack([k, k_diff], dim = 1)
742
+ else:
743
+ q, k, v = self.to_qkv(x).chunk(3, dim=-1)
744
+ q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = h), (q, k, v))
745
+
746
+ # Normalize q and k for cosine sim attention
747
+ if self.qk_norm == "l2":
748
+ q = F.normalize(q, dim=-1, eps=self.qk_norm_eps)
749
+ k = F.normalize(k, dim=-1, eps=self.qk_norm_eps)
750
+ elif self.qk_norm != "none":
751
+ q, k = self.apply_qk_layernorm(q, k)
752
+ # q = self.q_norm(q)
753
+ # k = self.k_norm(k)
754
+
755
+ if rotary_pos_emb is not None:
756
+ freqs, _ = rotary_pos_emb
757
+ q_dtype = q.dtype
758
+ k_dtype = k.dtype
759
+ q = q.to(torch.float32)
760
+ k = k.to(torch.float32)
761
+ freqs = freqs.to(torch.float32)
762
+
763
+ q_freqs = freqs
764
+
765
+ if rotary_pos_emb_k is not None:
766
+ k_freqs, _ = rotary_pos_emb_k
767
+ k_freqs = k_freqs.to(torch.float32)
768
+ else:
769
+ k_freqs = q_freqs
770
+
771
+ if q.shape[-2] >= k.shape[-2]:
772
+ ratio = q.shape[-2] / k.shape[-2]
773
+ q_freqs, k_freqs = freqs, ratio * freqs
774
+ else:
775
+ ratio = k.shape[-2] / q.shape[-2]
776
+ q_freqs, k_freqs = ratio * freqs, freqs
777
+
778
+ q = apply_rotary_pos_emb(q, q_freqs)
779
+ k = apply_rotary_pos_emb(k, k_freqs)
780
+ q = q.to(v.dtype)
781
+ k = k.to(v.dtype)
782
+
783
+ n, device = q.shape[-2], q.device
784
+
785
+ causal = self.causal if causal is None else causal
786
+
787
+ if n == 1 and causal:
788
+ causal = False
789
+
790
+ if self.differential:
791
+ q, q_diff = q.unbind(dim = 1)
792
+ k, k_diff = k.unbind(dim = 1)
793
+ out = self.apply_attn(q, k, v, causal = causal, flex_attention_block_mask = flex_attention_block_mask, flex_attention_score_mod = flex_attention_score_mod, flash_attn_sliding_window = flash_attn_sliding_window, padding_mask = padding_mask, varlen_metadata = varlen_metadata)
794
+ out_diff = self.apply_attn(q_diff, k_diff, v, causal = causal, flex_attention_block_mask = flex_attention_block_mask, flex_attention_score_mod = flex_attention_score_mod, flash_attn_sliding_window = flash_attn_sliding_window, padding_mask = padding_mask, varlen_metadata = varlen_metadata)
795
+ out = out - out_diff
796
+ else:
797
+ out = self.apply_attn(q, k, v, causal = causal, flex_attention_block_mask = flex_attention_block_mask, flex_attention_score_mod = flex_attention_score_mod, flash_attn_sliding_window = flash_attn_sliding_window, padding_mask = padding_mask, varlen_metadata = varlen_metadata)
798
+ # merge heads
799
+ out = rearrange(out, ' b h n d -> b n (h d)')
800
+
801
+ # Communicate between heads
802
+
803
+ # with autocast(enabled = False):
804
+ # out_dtype = out.dtype
805
+ # out = out.to(torch.float32)
806
+ # out = self.to_out(out).to(out_dtype)
807
+ out = self.to_out(out)
808
+
809
+ if self.feat_scale:
810
+ if padding_mask is not None:
811
+ mask = padding_mask.unsqueeze(-1).to(out.dtype) # (b, n, 1)
812
+ out_dc = (out * mask).sum(dim=-2, keepdim=True) / mask.sum(dim=-2, keepdim=True).clamp(min=1)
813
+ out_hf = out - out_dc
814
+ out = out + (self.lambda_dc * out_dc + self.lambda_hf * out_hf) * mask
815
+ else:
816
+ out_dc = out.mean(dim=-2, keepdim=True)
817
+ out_hf = out - out_dc
818
+ out = out + self.lambda_dc * out_dc + self.lambda_hf * out_hf
819
+
820
+ return out
821
+
822
+ class ConformerModule(nn.Module):
823
+ def __init__(
824
+ self,
825
+ dim,
826
+ norm_kwargs = {},
827
+ ):
828
+
829
+ super().__init__()
830
+
831
+ self.dim = dim
832
+
833
+ self.in_norm = LayerNorm(dim, **norm_kwargs)
834
+ self.pointwise_conv = nn.Conv1d(dim, dim, kernel_size=1, bias=False)
835
+ self.glu = GLU(dim, dim, nn.SiLU())
836
+ self.depthwise_conv = nn.Conv1d(dim, dim, kernel_size=17, groups=dim, padding=8, bias=False)
837
+ self.mid_norm = LayerNorm(dim, **norm_kwargs) # This is a batch norm in the original but I don't like batch norm
838
+ self.swish = nn.SiLU()
839
+ self.pointwise_conv_2 = nn.Conv1d(dim, dim, kernel_size=1, bias=False)
840
+
841
+ #@compile
842
+ def forward(self, x):
843
+ x = self.in_norm(x)
844
+ x = rearrange(x, 'b n d -> b d n')
845
+ x = self.pointwise_conv(x)
846
+ x = rearrange(x, 'b d n -> b n d')
847
+ x = self.glu(x)
848
+ x = rearrange(x, 'b n d -> b d n')
849
+ x = self.depthwise_conv(x)
850
+ x = rearrange(x, 'b d n -> b n d')
851
+ x = self.mid_norm(x)
852
+ x = self.swish(x)
853
+ x = rearrange(x, 'b n d -> b d n')
854
+ x = self.pointwise_conv_2(x)
855
+ x = rearrange(x, 'b d n -> b n d')
856
+
857
+ return x
858
+
859
+ class TransformerBlock(nn.Module):
860
+ def __init__(
861
+ self,
862
+ dim,
863
+ dim_heads = 64,
864
+ cross_attend = False,
865
+ dim_context = None,
866
+ global_cond_dim = None,
867
+ local_add_cond_dim = None,
868
+ modular_local_cond_configs = None,
869
+ causal = False,
870
+ zero_init_branch_outputs = True,
871
+ conformer = False,
872
+ layer_ix = -1,
873
+ add_rope = False,
874
+ layer_scale = False,
875
+ norm_type = 'layer_norm',
876
+ attn_kwargs = {},
877
+ ff_kwargs = {},
878
+ norm_kwargs = {}
879
+ ):
880
+
881
+ super().__init__()
882
+ self.dim = dim
883
+ self.dim_heads = min(dim_heads,dim)
884
+ self.cross_attend = cross_attend
885
+ self.dim_context = dim_context
886
+ self.causal = causal
887
+
888
+ if layer_scale and zero_init_branch_outputs:
889
+ print('zero_init_branch_outputs is redundant with layer_scale, setting zero_init_branch_outputs to False')
890
+ zero_init_branch_outputs = False
891
+
892
+ if norm_type not in ['layer_norm', 'rms_norm', 'dyt']:
893
+ raise ValueError(f'norm_type must be one of ["layer_norm", "rms_norm", "dyt"], got {norm_type}')
894
+
895
+ norm_layer_map = {
896
+ 'layer_norm': LayerNorm,
897
+ 'rms_norm': RMSNorm,
898
+ 'dyt': DynamicTanh
899
+ }
900
+ norm_layer = norm_layer_map[norm_type]
901
+
902
+ self.pre_norm = norm_layer(dim,**norm_kwargs)
903
+ self.add_rope = add_rope
904
+
905
+ self.self_attn = Attention(
906
+ dim,
907
+ dim_heads = self.dim_heads,
908
+ causal = causal,
909
+ zero_init_output=zero_init_branch_outputs,
910
+ **attn_kwargs
911
+ )
912
+
913
+ self.self_attn_scale = LayerScale(dim) if layer_scale else nn.Identity()
914
+
915
+ self.cross_attend = cross_attend
916
+ if cross_attend:
917
+ self.cross_attend_norm = norm_layer(dim, **norm_kwargs)
918
+ self.cross_attn = Attention(
919
+ dim,
920
+ dim_heads = self.dim_heads,
921
+ dim_context=dim_context,
922
+ causal = causal,
923
+ zero_init_output=zero_init_branch_outputs,
924
+ **attn_kwargs
925
+ )
926
+ self.cross_attn_scale = LayerScale(dim) if layer_scale else nn.Identity()
927
+
928
+ self.ff_norm = norm_layer(dim, **norm_kwargs)
929
+ self.ff = FeedForward(dim, zero_init_output=zero_init_branch_outputs, **ff_kwargs)
930
+ self.ff_scale = LayerScale(dim) if layer_scale else nn.Identity()
931
+
932
+ self.layer_ix = layer_ix
933
+
934
+ self.conformer = None
935
+ if conformer:
936
+ self.conformer = ConformerModule(dim, norm_kwargs=norm_kwargs)
937
+ self.conformer_scale = LayerScale(dim) if layer_scale else nn.Identity()
938
+
939
+ self.global_cond_dim = global_cond_dim
940
+
941
+ if global_cond_dim is not None:
942
+ self.to_scale_shift_gate = nn.Parameter(torch.randn(6*dim)/dim**0.5)
943
+
944
+ self.local_add_cond_dim = local_add_cond_dim
945
+
946
+ if local_add_cond_dim is not None:
947
+ self.to_local_embed = nn.Sequential(
948
+ nn.Linear(local_add_cond_dim, dim),
949
+ nn.SiLU(),
950
+ nn.Linear(dim, dim)
951
+ )
952
+
953
+ nn.init.zeros_(self.to_local_embed[-1].weight)
954
+ nn.init.zeros_(self.to_local_embed[-1].bias)
955
+
956
+ else:
957
+ self.to_local_embed = None
958
+
959
+ # Modular local conditioning - independent projections per conditioning ID
960
+ self.modular_local_cond_configs = modular_local_cond_configs or []
961
+ self.modular_local_embeds = nn.ModuleDict()
962
+
963
+ for config in self.modular_local_cond_configs:
964
+ cond_id = config["id"]
965
+ cond_dim = config["dim"]
966
+ proj = nn.Sequential(
967
+ nn.Linear(cond_dim, dim),
968
+ nn.SiLU(),
969
+ nn.Linear(dim, dim)
970
+ )
971
+ # Zero-init output layer so new conditioning doesn't affect model initially
972
+ nn.init.zeros_(proj[-1].weight)
973
+ nn.init.zeros_(proj[-1].bias)
974
+ self.modular_local_embeds[cond_id] = proj
975
+
976
+ self.rope = RotaryEmbedding(self.dim_heads // 2) if add_rope else None
977
+
978
+ def _apply_local_conditioning(self, x, local_add_cond, modular_local_cond):
979
+ """Apply local additive and modular local conditioning to x."""
980
+ if local_add_cond is not None and self.to_local_embed is not None:
981
+ local_emb = self.to_local_embed(local_add_cond)
982
+ x = x + _left_pad_to_match(local_emb, x.shape[-2])
983
+
984
+ if modular_local_cond is not None and len(self.modular_local_embeds) > 0:
985
+ modular_sum = None
986
+ for cond_id, proj in self.modular_local_embeds.items():
987
+ if cond_id in modular_local_cond:
988
+ local_emb = proj(modular_local_cond[cond_id])
989
+ local_emb = _left_pad_to_match(local_emb, x.shape[-2])
990
+ modular_sum = local_emb if modular_sum is None else modular_sum + local_emb
991
+ if modular_sum is not None:
992
+ x = x + modular_sum
993
+
994
+ return x
995
+
996
+ @compile
997
+ def forward(
998
+ self,
999
+ x,
1000
+ context = None,
1001
+ global_cond=None,
1002
+ local_add_cond=None,
1003
+ modular_local_cond=None,
1004
+ rotary_pos_emb = None,
1005
+ cross_attn_rotary_pos_emb = None,
1006
+ self_attention_block_mask = None,
1007
+ self_attention_score_mod = None,
1008
+ cross_attention_block_mask = None,
1009
+ cross_attention_score_mod = None,
1010
+ self_attention_flash_sliding_window = None,
1011
+ cross_attention_flash_sliding_window = None,
1012
+ padding_mask = None,
1013
+ varlen_metadata = None,
1014
+ ):
1015
+ if rotary_pos_emb is None and self.add_rope:
1016
+ rotary_pos_emb = self.rope.forward_from_seq_len(x.shape[-2])
1017
+
1018
+ if self.global_cond_dim is not None and self.global_cond_dim > 0 and global_cond is not None:
1019
+
1020
+ scale_self, shift_self, gate_self, scale_ff, shift_ff, gate_ff = (self.to_scale_shift_gate + global_cond).unsqueeze(1).chunk(6, dim=-1)
1021
+
1022
+ # self-attention with adaLN
1023
+ residual = x
1024
+ x = self.pre_norm(x)
1025
+ x = x * (1 + scale_self) + shift_self
1026
+ x = self.self_attn(x, rotary_pos_emb = rotary_pos_emb, flex_attention_block_mask = self_attention_block_mask, flex_attention_score_mod = self_attention_score_mod, flash_attn_sliding_window = self_attention_flash_sliding_window, padding_mask = padding_mask, varlen_metadata = varlen_metadata)
1027
+ x = x * torch.sigmoid(1 - gate_self)
1028
+ x = self.self_attn_scale(x)
1029
+ x = x + residual
1030
+
1031
+ if context is not None and self.cross_attend:
1032
+ if cross_attn_rotary_pos_emb is not None:
1033
+ x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), rotary_pos_emb = rotary_pos_emb, rotary_pos_emb_k = cross_attn_rotary_pos_emb, context = context, flex_attention_block_mask = cross_attention_block_mask, flex_attention_score_mod = cross_attention_score_mod, flash_attn_sliding_window = cross_attention_flash_sliding_window))
1034
+ else:
1035
+ x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), context = context, flex_attention_block_mask = cross_attention_block_mask, flex_attention_score_mod = cross_attention_score_mod, flash_attn_sliding_window = cross_attention_flash_sliding_window))
1036
+
1037
+ if self.conformer is not None:
1038
+ x = x + self.conformer_scale(self.conformer(x))
1039
+
1040
+ x = self._apply_local_conditioning(x, local_add_cond, modular_local_cond)
1041
+
1042
+ # feedforward with adaLN
1043
+ residual = x
1044
+ x = self.ff_norm(x)
1045
+ x = x * (1 + scale_ff) + shift_ff
1046
+ x = self.ff(x, varlen_metadata=varlen_metadata)
1047
+ x = x * torch.sigmoid(1 - gate_ff)
1048
+ x = self.ff_scale(x)
1049
+ x = x + residual
1050
+
1051
+ else:
1052
+ x = x + self.self_attn_scale(self.self_attn(self.pre_norm(x), rotary_pos_emb = rotary_pos_emb, flex_attention_block_mask = self_attention_block_mask, flex_attention_score_mod = self_attention_score_mod, flash_attn_sliding_window = self_attention_flash_sliding_window, padding_mask = padding_mask, varlen_metadata = varlen_metadata))
1053
+
1054
+ if context is not None and self.cross_attend:
1055
+ if cross_attn_rotary_pos_emb is not None:
1056
+ x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), rotary_pos_emb = rotary_pos_emb, rotary_pos_emb_k = cross_attn_rotary_pos_emb, context = context, flex_attention_block_mask = cross_attention_block_mask, flex_attention_score_mod = cross_attention_score_mod, flash_attn_sliding_window = cross_attention_flash_sliding_window))
1057
+ else:
1058
+ x = x + self.cross_attn_scale(self.cross_attn(self.cross_attend_norm(x), context = context, flex_attention_block_mask = cross_attention_block_mask, flex_attention_score_mod = cross_attention_score_mod, flash_attn_sliding_window = cross_attention_flash_sliding_window))
1059
+
1060
+ if self.conformer is not None:
1061
+ x = x + self.conformer_scale(self.conformer(x))
1062
+
1063
+ x = self._apply_local_conditioning(x, local_add_cond, modular_local_cond)
1064
+
1065
+ x = x + self.ff_scale(self.ff(self.ff_norm(x), varlen_metadata=varlen_metadata))
1066
+
1067
+
1068
+ return x
1069
+
1070
+ class ContinuousTransformer(nn.Module):
1071
+ def __init__(
1072
+ self,
1073
+ dim,
1074
+ depth,
1075
+ *,
1076
+ dim_in = None,
1077
+ dim_out = None,
1078
+ dim_heads = 64,
1079
+ cross_attend=False,
1080
+ cond_token_dim=None,
1081
+ final_cross_attn_ix=-1,
1082
+ global_cond_dim=None,
1083
+ local_add_cond_dim=None,
1084
+ modular_local_cond_configs=None,
1085
+ causal=False,
1086
+ rotary_pos_emb=True,
1087
+ cross_attn_rotary_pos_emb=False,
1088
+ zero_init_branch_outputs=True,
1089
+ conformer=False,
1090
+ use_sinusoidal_emb=False,
1091
+ use_abs_pos_emb=False,
1092
+ abs_pos_emb_max_length=10000,
1093
+ num_memory_tokens=0,
1094
+ sliding_window=None,
1095
+ **kwargs
1096
+ ):
1097
+
1098
+ super().__init__()
1099
+
1100
+ self.dim = dim
1101
+ self.depth = depth
1102
+ self.causal = causal
1103
+ self.layers = nn.ModuleList([])
1104
+
1105
+ self.project_in = nn.Linear(dim_in, dim, bias=False) if dim_in is not None else nn.Identity()
1106
+ self.project_out = nn.Linear(dim, dim_out, bias=False) if dim_out is not None else nn.Identity()
1107
+
1108
+ if rotary_pos_emb:
1109
+ self.rotary_pos_emb = RotaryEmbedding(max(dim_heads // 2, 32))
1110
+ else:
1111
+ self.rotary_pos_emb = None
1112
+
1113
+ if cross_attn_rotary_pos_emb:
1114
+ self.cross_attn_rotary_pos_emb = RotaryEmbedding(max(dim_heads // 2, 32))
1115
+ else:
1116
+ self.cross_attn_rotary_pos_emb = None
1117
+
1118
+ self.num_memory_tokens = num_memory_tokens
1119
+ if num_memory_tokens > 0:
1120
+ self.memory_tokens = nn.Parameter(torch.randn(num_memory_tokens, dim))
1121
+
1122
+ self.use_sinusoidal_emb = use_sinusoidal_emb
1123
+ if use_sinusoidal_emb:
1124
+ self.pos_emb = ScaledSinusoidalEmbedding(dim)
1125
+
1126
+ self.use_abs_pos_emb = use_abs_pos_emb
1127
+ if use_abs_pos_emb:
1128
+ self.pos_emb = AbsolutePositionalEmbedding(dim, abs_pos_emb_max_length + self.num_memory_tokens)
1129
+
1130
+ self.global_cond_embedder = None
1131
+ if global_cond_dim is not None:
1132
+ self.global_cond_embedder = nn.Sequential(
1133
+ nn.Linear(global_cond_dim, dim),
1134
+ nn.SiLU(),
1135
+ nn.Linear(dim, dim * 6)
1136
+ )
1137
+
1138
+ self.final_cross_attn_ix = final_cross_attn_ix
1139
+
1140
+ self.sliding_window = sliding_window
1141
+
1142
+ for i in range(depth):
1143
+ should_cross_attend = cross_attend and (self.final_cross_attn_ix == -1 or i <= (self.final_cross_attn_ix))
1144
+ self.layers.append(
1145
+ TransformerBlock(
1146
+ dim,
1147
+ dim_heads = dim_heads,
1148
+ cross_attend = should_cross_attend,
1149
+ dim_context = cond_token_dim,
1150
+ global_cond_dim = global_cond_dim,
1151
+ local_add_cond_dim = local_add_cond_dim,
1152
+ modular_local_cond_configs = modular_local_cond_configs,
1153
+ causal = causal,
1154
+ zero_init_branch_outputs = zero_init_branch_outputs,
1155
+ conformer=conformer,
1156
+ layer_ix=i,
1157
+ **kwargs
1158
+ )
1159
+ )
1160
+
1161
+ def forward(
1162
+ self,
1163
+ x,
1164
+ context = None,
1165
+ prepend_embeds = None,
1166
+ global_cond = None,
1167
+ local_add_cond = None,
1168
+ modular_local_cond = None,
1169
+ return_info = False,
1170
+ use_checkpointing = True,
1171
+ exit_layer_ix = None,
1172
+ padding_mask: Optional[torch.Tensor] = None,
1173
+ **kwargs
1174
+ ):
1175
+ batch, seq, device = *x.shape[:2], x.device
1176
+
1177
+ model_dtype = next(self.parameters()).dtype
1178
+ x = x.to(model_dtype)
1179
+
1180
+ info = {
1181
+ "hidden_states": [],
1182
+ }
1183
+
1184
+ x = self.project_in(x)
1185
+
1186
+ if prepend_embeds is not None:
1187
+ prepend_length, prepend_dim = prepend_embeds.shape[1:]
1188
+
1189
+ assert prepend_dim == x.shape[-1], 'prepend dimension must match sequence dimension'
1190
+
1191
+ x = torch.cat((prepend_embeds, x), dim = -2)
1192
+
1193
+ if self.num_memory_tokens > 0:
1194
+ memory_tokens = self.memory_tokens.expand(batch, -1, -1)
1195
+ x = torch.cat((memory_tokens, x), dim=1)
1196
+
1197
+ if self.rotary_pos_emb is not None:
1198
+ rotary_pos_emb = self.rotary_pos_emb.forward_from_seq_len(x.shape[1])
1199
+ else:
1200
+ rotary_pos_emb = None
1201
+
1202
+ if self.cross_attn_rotary_pos_emb is not None:
1203
+ cross_attn_rotary_pos_emb = self.cross_attn_rotary_pos_emb.forward_from_seq_len(context.shape[-1])
1204
+ else:
1205
+ cross_attn_rotary_pos_emb = None
1206
+
1207
+ if self.use_sinusoidal_emb or self.use_abs_pos_emb:
1208
+ x = x + self.pos_emb(x)
1209
+
1210
+ if global_cond is not None and self.global_cond_embedder is not None:
1211
+ global_cond = self.global_cond_embedder(global_cond)
1212
+
1213
+ # Extend padding mask for prepended tokens if provided
1214
+ extended_padding_mask = None
1215
+ varlen_metadata = None
1216
+ if padding_mask is not None:
1217
+ # Compute total prepend length (memory tokens + prepend_embeds)
1218
+ prepend_length = self.num_memory_tokens
1219
+ if prepend_embeds is not None:
1220
+ prepend_length += prepend_embeds.shape[1]
1221
+
1222
+ # Prepend tokens are always valid for attention
1223
+ if prepend_length > 0:
1224
+ prepend_valid = torch.ones(batch, prepend_length, device=device, dtype=torch.bool)
1225
+ extended_padding_mask = torch.cat([prepend_valid, padding_mask], dim=-1)
1226
+ else:
1227
+ extended_padding_mask = padding_mask
1228
+
1229
+ # Precompute varlen metadata once for all layers (major performance optimization)
1230
+ # Only compute if varlen attention is actually available
1231
+ if flash_attn_varlen_func is not None and index_first_axis is not None:
1232
+ varlen_metadata = precompute_varlen_metadata(extended_padding_mask)
1233
+
1234
+ # Iterate over the transformer layers
1235
+ for layer_ix, layer in enumerate(self.layers):
1236
+
1237
+ layer_kwargs = {
1238
+ "context": context,
1239
+ "rotary_pos_emb": rotary_pos_emb,
1240
+ "cross_attn_rotary_pos_emb": cross_attn_rotary_pos_emb,
1241
+ "global_cond": global_cond,
1242
+ "local_add_cond": local_add_cond,
1243
+ "modular_local_cond": modular_local_cond,
1244
+ "self_attention_flash_sliding_window": self.sliding_window,
1245
+ "padding_mask": extended_padding_mask,
1246
+ "varlen_metadata": varlen_metadata
1247
+ }
1248
+
1249
+ if use_checkpointing:
1250
+ x = checkpoint(layer, x, **layer_kwargs, **kwargs)
1251
+ else:
1252
+ x = layer(x, **layer_kwargs, **kwargs)
1253
+
1254
+ if return_info:
1255
+ info["hidden_states"].append(x)
1256
+
1257
+ if exit_layer_ix is not None and layer_ix == exit_layer_ix:
1258
+ x = x[:, self.num_memory_tokens:, :]
1259
+
1260
+ if return_info:
1261
+ return x, info
1262
+
1263
+ return x
1264
+
1265
+ x = x[:, self.num_memory_tokens:, :]
1266
+
1267
+ x = self.project_out(x)
1268
+
1269
+ if return_info:
1270
+ return x, info
1271
+
1272
+ return x
stable_audio_3/models/utils.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import os
3
+
4
+ torch._dynamo.config.cache_size_limit = max(64, torch._dynamo.config.cache_size_limit)
5
+ torch._dynamo.config.suppress_errors = True
6
+
7
+ # Get torch.compile flag from environment variable ENABLE_TORCH_COMPILE
8
+
9
+ enable_torch_compile = os.environ.get("ENABLE_TORCH_COMPILE", "0") == "1"
10
+
11
+ def compile(function, *args, **kwargs):
12
+
13
+ if enable_torch_compile:
14
+ try:
15
+ return torch.compile(function, *args, **kwargs)
16
+ except RuntimeError:
17
+ return function
18
+
19
+ return function
stable_audio_3/training/diffusion.py ADDED
@@ -0,0 +1,1143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ import pytorch_lightning as pl
3
+ from pytorch_lightning.loggers import WandbLogger, CometLogger
4
+ import os
5
+ import torch
6
+ import gc
7
+ import typing as tp
8
+ import torchaudio
9
+
10
+ from einops import rearrange
11
+ from safetensors.torch import save_file
12
+ from functools import partial
13
+ from torch.nn import functional as F
14
+
15
+ from ..interface.aeiou import audio_spectrogram_image
16
+ from ..inference.sampling import truncated_logistic_normal_rescaled, sample_timesteps_logsnr, sample_timesteps_logsnr_uniform, sample_diffusion
17
+ from ..models.diffusion import ConditionedDiffusionModelWrapper
18
+ from ..models.inpainting import random_inpaint_mask, MaskType
19
+ from ..models.lora import add_lora, get_lora_params, get_lora_state_dict, LoRAParametrization, get_lora_layers, save_lora_safetensors, resolve_adapter_type, prepare_dora_state_dict, cast_base_to_precision
20
+ from .utils import create_optimizer_from_config, create_scheduler_from_config, log_audio, log_image, log_metric, get_rank, create_augmented_padding_mask, compute_masked_loss, compute_normalized_mse, resize_padding_mask, StaggeredLogger, compute_per_elem_trim, trim_and_concat
21
+ from time import time
22
+
23
+ class Profiler:
24
+
25
+ def __init__(self):
26
+ self.ticks = [[time(), None]]
27
+
28
+ def tick(self, msg):
29
+ self.ticks.append([time(), msg])
30
+
31
+ def __repr__(self):
32
+ rep = 80 * "=" + "\n"
33
+ for i in range(1, len(self.ticks)):
34
+ msg = self.ticks[i][1]
35
+ ellapsed = self.ticks[i][0] - self.ticks[i - 1][0]
36
+ rep += msg + f": {ellapsed*1000:.2f}ms\n"
37
+ rep += 80 * "=" + "\n\n\n"
38
+ return rep
39
+
40
+ class DiffusionCondTrainingWrapper(pl.LightningModule):
41
+ '''
42
+ Wrapper for training a conditional audio diffusion model.
43
+ '''
44
+ def __init__(
45
+ self,
46
+ model: ConditionedDiffusionModelWrapper,
47
+ lr: float = None,
48
+ mask_loss_weight: float = 0.0,
49
+ mask_padding_attention: bool = False,
50
+ silence_extension_scale_seconds: float = 0.0,
51
+ use_ema: bool = True,
52
+ log_loss_info: bool = False,
53
+ optimizer_configs: dict = None,
54
+ pre_encoded: bool = False,
55
+ cfg_dropout_prob = 0.1,
56
+ timestep_sampler: tp.Literal["uniform", "logit_normal", "trunc_logit_normal", "log_snr", "log_snr_uniform"] = "uniform",
57
+ timestep_sampler_options: tp.Optional[tp.Dict[str, tp.Any]] = None,
58
+ validation_timesteps = [0.1, 0.3, 0.5, 0.7, 0.9],
59
+ p_one_shot: float = 0.0,
60
+ inpainting_config: dict = None,
61
+ use_effective_length_for_schedule: bool = False,
62
+ sample_rate: int = 44100,
63
+ sample_size: int = None,
64
+ loss_normalization: tp.Literal["none", "timestep", "sample", "sample_channel"] = "none",
65
+ loss_norm_eps: float = 1e-6,
66
+ lora_config: tp.Optional[tp.Dict[str, tp.Any]] = None,
67
+ lora_state_dict: tp.Optional[tp.Dict[str, tp.Any]] = None,
68
+ svd_bases_path: tp.Optional[str] = None,
69
+ log_every_n_steps: int = 10,
70
+ ot_coupling: bool = False,
71
+ base_precision: tp.Optional[str] = None,
72
+ ):
73
+ super().__init__()
74
+
75
+ self.ot_coupling = ot_coupling
76
+
77
+ self.diffusion = model
78
+
79
+ self.lora_config = lora_config
80
+ if self.lora_config is not None:
81
+ # Don't use EMA with LoRA
82
+ use_ema = False
83
+ # Freeze the pre-trained model weights
84
+ self.diffusion.model.eval().requires_grad_(False)
85
+ self.diffusion.conditioner.eval().requires_grad_(False)
86
+ rank = self.lora_config.get("rank", 8)
87
+ lora_alpha = self.lora_config.get("alpha", rank)
88
+ adapter_type = self.lora_config.get("adapter_type", "lora")
89
+ include = self.lora_config.get("include", None)
90
+ exclude = self.lora_config.get("exclude", None)
91
+ # Resolve legacy "dora" to rows/cols variant
92
+ adapter_type = resolve_adapter_type(adapter_type, lora_state_dict)
93
+ print(f"LoRA config: rank={rank}, alpha={lora_alpha}, adapter_type={adapter_type}")
94
+ if include:
95
+ print(f" include: {include}")
96
+ if exclude:
97
+ print(f" exclude: {exclude}")
98
+ # Load pre-computed SVD bases for -XS adapter types
99
+ svd_bases = None
100
+ if svd_bases_path is not None:
101
+ print(f"Loading SVD bases from {svd_bases_path}")
102
+ svd_bases = torch.load(svd_bases_path, map_location="cpu", weights_only=True)
103
+ elif adapter_type.endswith("-xs"):
104
+ print("WARNING: -XS adapter without svd_bases_path — SVD will be computed per layer")
105
+ lora_config = {
106
+ torch.nn.Linear: {
107
+ "weight": partial(LoRAParametrization.from_linear, rank=rank, lora_alpha=lora_alpha, adapter_type=adapter_type),
108
+ },
109
+ torch.nn.Conv1d: {
110
+ "weight": partial(LoRAParametrization.from_conv1d, rank=rank, lora_alpha=lora_alpha, adapter_type=adapter_type),
111
+ }
112
+ }
113
+ # Add LoRA to the model
114
+ add_lora(self.diffusion.model, lora_config, include=include, exclude=exclude, svd_bases=svd_bases)
115
+ # Add LoRA to the conditioner
116
+ add_lora(self.diffusion.conditioner, lora_config, include=include, exclude=exclude, svd_bases=svd_bases)
117
+ print("lora layers:", len(get_lora_layers(self.diffusion)))
118
+
119
+ if lora_state_dict is not None:
120
+ # Old DoRA checkpoints saved magnitude as 2D (1,fan_in) or (fan_out,1);
121
+ # current code expects 1D. Squeeze so old checkpoints still load.
122
+ prepare_dora_state_dict(lora_state_dict)
123
+ self.diffusion.model.load_state_dict(lora_state_dict, strict=False)
124
+ self.diffusion.conditioner.load_state_dict(lora_state_dict, strict=False)
125
+
126
+ # Cast frozen base weights to lower precision if requested
127
+ if base_precision:
128
+ cast_base_to_precision(self.diffusion.model, base_precision)
129
+ cast_base_to_precision(self.diffusion.conditioner, base_precision)
130
+ if self.diffusion.pretransform is not None:
131
+ self.diffusion.pretransform.to(
132
+ torch.bfloat16 if base_precision in ("bf16", "bfloat16") else torch.float16
133
+ )
134
+
135
+ self.diffusion_ema = None
136
+ self.mask_loss_weight = mask_loss_weight
137
+
138
+ # Attention masking for padded tokens
139
+ # Backward compat: if passed from training config, propagate to model
140
+ if mask_padding_attention and not self.diffusion.mask_padding_attention:
141
+ import warnings
142
+ warnings.warn("mask_padding_attention in training config is deprecated. Move to model.diffusion config.", FutureWarning)
143
+ self.diffusion.mask_padding_attention = mask_padding_attention
144
+ self.mask_padding_attention = self.diffusion.mask_padding_attention
145
+ self.silence_extension_scale_seconds = silence_extension_scale_seconds
146
+
147
+ self.cfg_dropout_prob = cfg_dropout_prob
148
+
149
+ self.rng = torch.quasirandom.SobolEngine(1, scramble=True)
150
+
151
+ self.timestep_sampler = timestep_sampler
152
+
153
+ self.timestep_sampler_options = {} if timestep_sampler_options is None else timestep_sampler_options
154
+
155
+ if self.timestep_sampler == "log_snr":
156
+ self.mean_logsnr = self.timestep_sampler_options.get("mean_logsnr", -1.2)
157
+ self.std_logsnr = self.timestep_sampler_options.get("std_logsnr", 2.0)
158
+ elif self.timestep_sampler == "log_snr_uniform":
159
+ self.min_logsnr = self.timestep_sampler_options.get("min_logsnr", -6.0)
160
+ self.max_logsnr = self.timestep_sampler_options.get("max_logsnr", 5.0)
161
+
162
+ self.p_one_shot = p_one_shot
163
+
164
+ self.diffusion_objective = model.diffusion_objective
165
+
166
+ self.log_loss_info = log_loss_info
167
+
168
+ self._staggered_logger = StaggeredLogger(every_n_steps=log_every_n_steps)
169
+
170
+ assert lr is not None or optimizer_configs is not None, "Must specify either lr or optimizer_configs in training config"
171
+
172
+ if optimizer_configs is None:
173
+ optimizer_configs = {
174
+ "diffusion": {
175
+ "optimizer": {
176
+ "type": "Adam",
177
+ "config": {
178
+ "lr": lr
179
+ }
180
+ }
181
+ }
182
+ }
183
+ else:
184
+ if lr is not None:
185
+ print(f"WARNING: learning_rate and optimizer_configs both specified in config. Ignoring learning_rate and using optimizer_configs.")
186
+
187
+ self.optimizer_configs = optimizer_configs
188
+
189
+ self.pre_encoded = pre_encoded
190
+
191
+ # Loss normalization by target magnitude
192
+ # Options: "none", "timestep", "sample", "sample_channel"
193
+ self.loss_normalization = loss_normalization
194
+ self.loss_norm_eps = loss_norm_eps
195
+
196
+ # Inpainting
197
+ self.inpainting_config = inpainting_config
198
+
199
+ if self.inpainting_config is not None:
200
+ self.inpaint_mask_kwargs = self.inpainting_config.get("mask_kwargs", {})
201
+
202
+ # Per-element schedule shift based on effective (unpadded) sequence length
203
+ # Backward compat: if passed from training config, propagate to model
204
+ if use_effective_length_for_schedule and not self.diffusion.use_effective_length_for_schedule:
205
+ import warnings
206
+ warnings.warn("use_effective_length_for_schedule in training config is deprecated. Move to model.diffusion config.", DeprecationWarning)
207
+ self.diffusion.use_effective_length_for_schedule = use_effective_length_for_schedule
208
+ self.use_effective_length_for_schedule = self.diffusion.use_effective_length_for_schedule
209
+ self.sample_rate = sample_rate
210
+ self.sample_size = sample_size
211
+
212
+ # FSDP
213
+ self.use_fsdp = False
214
+
215
+ # Validation
216
+ self.validation_timesteps = validation_timesteps
217
+
218
+ self.validation_step_outputs = {}
219
+
220
+ for validation_timestep in self.validation_timesteps:
221
+ self.validation_step_outputs[f'val/loss_{validation_timestep:.1f}'] = []
222
+
223
+ def configure_optimizers(self):
224
+ diffusion_opt_config = self.optimizer_configs['diffusion']
225
+
226
+ if self.lora_config is not None:
227
+ opt_params = [*get_lora_params(self.diffusion.model), *get_lora_params(self.diffusion.conditioner)]
228
+ elif diffusion_opt_config['optimizer'].get('type') == 'MuonAdamW':
229
+ # Pass (name, param) tuples so MuonAdamW can match fused layer patterns
230
+ opt_params = [(n, p) for n, p in self.diffusion.named_parameters() if p.requires_grad]
231
+ else:
232
+ # Only include parameters that require gradients (excludes frozen pretransform, conditioner, etc.)
233
+ opt_params = [p for p in self.diffusion.parameters() if p.requires_grad]
234
+
235
+ opt_diff = create_optimizer_from_config(diffusion_opt_config['optimizer'], opt_params)
236
+
237
+ if "scheduler" in diffusion_opt_config:
238
+ sched_diff = create_scheduler_from_config(diffusion_opt_config['scheduler'], opt_diff)
239
+ sched_diff_config = {
240
+ "scheduler": sched_diff,
241
+ "interval": "step"
242
+ }
243
+ return [opt_diff], [sched_diff_config]
244
+
245
+ return [opt_diff]
246
+
247
+ def training_step(self, batch, batch_idx):
248
+ reals, metadata = batch
249
+
250
+ p = Profiler()
251
+
252
+ if reals.ndim == 4 and reals.shape[0] == 1:
253
+ reals = reals[0]
254
+
255
+ diffusion_input = reals
256
+
257
+ p.tick("setup")
258
+
259
+ #with torch.amp.autocast(device_type="cuda"):
260
+ conditioning = self.diffusion.conditioner(metadata, self.device)
261
+
262
+ # Create batch tensor of padding masks from the metadata
263
+ # If padding_mask not provided, assume all positions are valid (no padding)
264
+ if all("padding_mask" in md for md in metadata):
265
+ padding_masks = torch.stack([md["padding_mask"][0] for md in metadata], dim=0).to(self.device) # Shape (batch_size, sequence_length)
266
+ else:
267
+ # All-True mask: everything is signal, no padding
268
+ padding_masks = torch.ones(diffusion_input.shape[0], diffusion_input.shape[-1], dtype=torch.bool, device=self.device)
269
+
270
+ p.tick("conditioning")
271
+
272
+ if self.diffusion.pretransform is not None:
273
+ self.diffusion.pretransform.to(self.device)
274
+
275
+ if not self.pre_encoded:
276
+ with torch.cuda.amp.autocast(), torch.set_grad_enabled(self.diffusion.pretransform.enable_grad):
277
+ self.diffusion.pretransform.train(self.diffusion.pretransform.enable_grad)
278
+ diffusion_input = self.diffusion.pretransform.encode(diffusion_input)
279
+ p.tick("pretransform")
280
+ padding_masks = resize_padding_mask(padding_masks, diffusion_input.shape[-1])
281
+ else:
282
+ # Apply scale to pre-encoded latents if needed, as the pretransform encode function will not be run
283
+ if hasattr(self.diffusion.pretransform, "scale") and self.diffusion.pretransform.scale != 1.0:
284
+ diffusion_input = diffusion_input / self.diffusion.pretransform.scale
285
+
286
+
287
+ if padding_masks.shape[-1] != diffusion_input.shape[-1]:
288
+ padding_masks = resize_padding_mask(padding_masks, diffusion_input.shape[-1])
289
+
290
+ if self.timestep_sampler == "uniform":
291
+ # Draw uniformly distributed continuous timesteps
292
+ t = self.rng.draw(reals.shape[0])[:, 0].to(self.device)
293
+ elif self.timestep_sampler == "logit_normal":
294
+ t = torch.sigmoid(torch.randn(reals.shape[0], device=self.device))
295
+ elif self.timestep_sampler == "trunc_logit_normal":
296
+ # Draw from logistic truncated normal distribution
297
+ t = truncated_logistic_normal_rescaled(reals.shape[0]).to(self.device)
298
+
299
+ # Flip the distribution
300
+ t = 1 - t
301
+ elif self.timestep_sampler == "log_snr":
302
+ t = sample_timesteps_logsnr(reals.shape[0], mean_logsnr=self.mean_logsnr, std_logsnr=self.std_logsnr).to(self.device)
303
+ elif self.timestep_sampler == "log_snr_uniform":
304
+ t = sample_timesteps_logsnr_uniform(reals.shape[0], min_logsnr=self.min_logsnr, max_logsnr=self.max_logsnr).to(self.device)
305
+ else:
306
+ raise ValueError(f"Invalid timestep_sampler: {self.timestep_sampler}")
307
+
308
+ if self.diffusion.dist_shift is not None:
309
+ # Compute sequence length for schedule shift
310
+ if self.use_effective_length_for_schedule:
311
+ # Use per-element effective lengths derived from seconds_total (rounded up)
312
+ # This matches inference which computes effective length from seconds_total conditioning
313
+ # Fall back to padding_masks.sum() if seconds_total is not available
314
+ if all("seconds_total" in md for md in metadata):
315
+ downsampling_ratio = self.diffusion.pretransform.downsampling_ratio if self.diffusion.pretransform is not None else 1
316
+ effective_seq_len = torch.tensor(
317
+ [int(math.ceil(int(md["seconds_total"] * self.sample_rate) / downsampling_ratio)) for md in metadata],
318
+ device=self.device
319
+ )
320
+ else:
321
+ # Fallback: use padding mask sum
322
+ effective_seq_len = padding_masks.sum(dim=-1)
323
+ else:
324
+ # Use total sequence length (original behavior)
325
+ effective_seq_len = diffusion_input.shape[2]
326
+
327
+ # Shift the distribution
328
+ t = self.diffusion.dist_shift.shift(t, effective_seq_len)
329
+
330
+ if self.p_one_shot > 0:
331
+ # Set t to 1 with probability p_one_shot
332
+ t = torch.where(torch.rand_like(t) < self.p_one_shot, torch.ones_like(t), t)
333
+
334
+ # Calculate the noise schedule parameters for those timesteps
335
+ if self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
336
+ alphas, sigmas = 1-t, t
337
+
338
+ # Combine the ground truth data and the noise
339
+ alphas = alphas[:, None, None]
340
+ sigmas = sigmas[:, None, None]
341
+ noise = torch.randn_like(diffusion_input)
342
+
343
+ # Minibatch OT coupling: find optimal noise permutation for straighter transport paths
344
+ # Based on MelodyFlow (arXiv:2407.03648v2) Section 2.5.2
345
+ # Uses GPU-only Sinkhorn approximation to avoid CPU sync
346
+ if self.ot_coupling and diffusion_input.shape[0] > 1:
347
+ with torch.no_grad():
348
+ # Flatten to [batch, features] for distance computation
349
+ data_flat = diffusion_input.reshape(diffusion_input.shape[0], -1)
350
+ noise_flat = noise.reshape(noise.shape[0], -1)
351
+ # Squared L2 cost via matmul (faster than cdist, same optimal assignment)
352
+ aa = (data_flat * data_flat).sum(dim=1, keepdim=True)
353
+ bb = (noise_flat * noise_flat).sum(dim=1, keepdim=True)
354
+ cost_matrix = aa + bb.T - 2.0 * (data_flat @ noise_flat.T)
355
+ # Sinkhorn assignment (GPU-only, no CPU sync)
356
+ log_P = -cost_matrix / cost_matrix.detach().mean() # normalize for numerical stability
357
+ for _ in range(20):
358
+ log_P = log_P - torch.logsumexp(log_P, dim=1, keepdim=True)
359
+ log_P = log_P - torch.logsumexp(log_P, dim=0, keepdim=True)
360
+ # Sequential assignment from soft permutation matrix (guarantees valid permutation)
361
+ P = log_P.exp()
362
+ B = P.shape[0]
363
+ col_indices = torch.empty(B, dtype=torch.long, device=P.device)
364
+ used = torch.zeros(B, dtype=torch.bool, device=P.device)
365
+ for i in range(B):
366
+ P[i, used] = -1
367
+ col_indices[i] = P[i].argmax()
368
+ used[col_indices[i]] = True
369
+ noise = noise[col_indices]
370
+
371
+ noised_inputs = diffusion_input * alphas + noise * sigmas
372
+
373
+ if self.diffusion_objective == "v":
374
+ targets = noise * alphas - diffusion_input * sigmas
375
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
376
+ targets = noise - diffusion_input
377
+
378
+ p.tick("noise")
379
+
380
+ extra_args = {}
381
+
382
+ # Compute downsampling ratio for attention mask creation
383
+ downsampling_ratio = self.diffusion.pretransform.downsampling_ratio if self.diffusion.pretransform is not None else 1
384
+
385
+ # Create augmented padding mask with random silence extension
386
+ if self.mask_padding_attention and self.silence_extension_scale_seconds > 0:
387
+ augmented_padding_mask = create_augmented_padding_mask(
388
+ padding_masks,
389
+ silence_extension_scale_seconds=self.silence_extension_scale_seconds,
390
+ sample_rate=self.sample_rate,
391
+ downsampling_ratio=downsampling_ratio,
392
+ )
393
+ else:
394
+ augmented_padding_mask = padding_masks
395
+
396
+ # Loss mask defines signal vs padding regions for loss computation
397
+ # - mask_loss_weight controls padding contribution (0 = signal only)
398
+ # - When mask_padding_attention=True: only compute loss on signal (padding saw no attention)
399
+ loss_mask = augmented_padding_mask.to(torch.bool)
400
+
401
+ # Pass padding mask for attention masking - model handles prepend extension
402
+ if self.mask_padding_attention:
403
+ extra_args["padding_mask"] = augmented_padding_mask
404
+
405
+ if self.inpainting_config is not None:
406
+
407
+ # Max mask size is the full sequence length
408
+ max_mask_length = diffusion_input.shape[2]
409
+
410
+ # Create a mask of random length for a random slice of the input
411
+ inpaint_masked_input, inpaint_mask = random_inpaint_mask(diffusion_input, padding_masks=augmented_padding_mask, mask_padding=self.mask_padding_attention, **self.inpaint_mask_kwargs)
412
+
413
+ conditioning['inpaint_mask'] = [inpaint_mask]
414
+ conditioning['inpaint_masked_input'] = [inpaint_masked_input]
415
+
416
+ # Only compute loss on inpainted region (where model is generating)
417
+ loss_mask = loss_mask & ~inpaint_mask.squeeze(1).to(torch.bool)
418
+
419
+ output = self.diffusion(noised_inputs, t, cond=conditioning, cfg_dropout_prob = self.cfg_dropout_prob, **extra_args)
420
+ p.tick("diffusion")
421
+
422
+ if self.log_loss_info:
423
+ # Loss debugging logs
424
+ num_loss_buckets = 10
425
+ bucket_size = 1 / num_loss_buckets
426
+ loss_all = F.mse_loss(output, targets, reduction="none")
427
+
428
+ sigmas = rearrange(self.all_gather(sigmas), "w b c n -> (w b) c n").squeeze()
429
+
430
+ # gather loss_all across all GPUs
431
+ loss_all = rearrange(self.all_gather(loss_all), "w b c n -> (w b) c n")
432
+
433
+ # Bucket loss values based on corresponding sigma values, bucketing sigma values by bucket_size
434
+ loss_all = torch.stack([loss_all[(sigmas >= i) & (sigmas < i + bucket_size)].mean() for i in torch.arange(0, 1, bucket_size).to(self.device)])
435
+
436
+ # Log bucketed losses with corresponding sigma bucket values, if it's not NaN
437
+ debug_log_dict = {
438
+ f"model/loss_all_{i/num_loss_buckets:.1f}": loss_all[i].detach() for i in range(num_loss_buckets) if not torch.isnan(loss_all[i])
439
+ }
440
+
441
+ self.log_dict(debug_log_dict)
442
+
443
+ p.tick("loss_debug")
444
+
445
+ # Compute std only over non-padded positions when masking is active
446
+ if loss_mask is not None and self.mask_padding_attention:
447
+ mask_expanded = loss_mask.unsqueeze(1) # [B, 1, T]
448
+ std_data = diffusion_input[mask_expanded.expand_as(diffusion_input)].std()
449
+ std_targets = targets[mask_expanded.expand_as(targets)].std().detach()
450
+ else:
451
+ std_data = diffusion_input.std()
452
+ std_targets = targets.std().detach()
453
+
454
+ log_dict = {
455
+ 'train/std_data': std_data,
456
+ 'train/std_targets': std_targets,
457
+ 'train/lr': self.trainer.optimizers[0].param_groups[0]['lr']
458
+ }
459
+
460
+ p.tick("std_compute")
461
+
462
+ # Compute normalized MSE (normalization only affects non-"none" modes)
463
+ mse_loss_full = compute_normalized_mse(output, targets, loss_mask, self.loss_normalization, self.loss_norm_eps)
464
+
465
+ p.tick("mse_loss")
466
+
467
+ # Compute loss with signal/padding separation (returns already-detached metrics)
468
+ loss, signal_mean, padding_mean = compute_masked_loss(
469
+ mse_loss_full, loss_mask, self.mask_padding_attention, self.mask_loss_weight
470
+ )
471
+ mse_loss = loss
472
+
473
+ p.tick("masked_loss")
474
+
475
+ # When attention masking is on, compute_masked_loss excludes everything outside
476
+ # loss_mask (which now excludes inpaint context). Add context reconstruction loss
477
+ # so the model learns to preserve context regions during inpainting.
478
+ # (When mask_padding_attention=False, context is already included via mask_loss_weight.)
479
+ context_loss_mean = torch.tensor(0.0, device=loss.device)
480
+ if (self.inpainting_config is not None
481
+ and self.mask_padding_attention
482
+ and self.mask_loss_weight > 0):
483
+ # Context = inpaint_mask=1 (keep) AND padding_mask=1 (real audio, not padding)
484
+ inpaint_context = inpaint_mask.squeeze(1).to(torch.bool) & augmented_padding_mask.to(torch.bool)
485
+ n_ctx = inpaint_context.sum(dim=1) * mse_loss_full.shape[1] # per-sample count
486
+ if n_ctx.sum() > 0:
487
+ context_vals = torch.where(inpaint_context.unsqueeze(1), mse_loss_full, 0.0)
488
+ context_loss_mean = (context_vals.sum(dim=(1, 2)) / (n_ctx + 1e-8)).mean()
489
+ loss = loss + context_loss_mean * self.mask_loss_weight
490
+
491
+ # Log separate signal/padding/context losses for monitoring
492
+ log_dict["train/mse_signal"] = signal_mean
493
+ log_dict["train/mse_masked_loss"] = padding_mean
494
+ log_dict["train/mse_context_loss"] = context_loss_mean.detach()
495
+
496
+ log_dict["train/mse_loss"] = mse_loss.detach()
497
+ log_dict["train/loss"] = loss.detach()
498
+
499
+ # Stash for external callbacks (e.g. loss-by-timestep logging)
500
+ self._last_t = t.detach()
501
+ self._last_per_elem_loss = mse_loss_full.detach().mean(dim=(1, 2))
502
+
503
+ self._staggered_logger.log(log_dict, self)
504
+
505
+ #p.tick("log_dict")
506
+ #print(f"Profiler: {p}")
507
+ return loss
508
+
509
+ def validation_step(self, batch, batch_idx):
510
+
511
+ reals, metadata = batch
512
+
513
+ if reals.ndim == 4 and reals.shape[0] == 1:
514
+ reals = reals[0]
515
+
516
+ diffusion_input = reals
517
+
518
+ with torch.amp.autocast("cuda"), torch.no_grad():
519
+ conditioning = self.diffusion.conditioner(metadata, self.device)
520
+
521
+ # Create batch tensor of padding masks from the metadata
522
+ if all("padding_mask" in md for md in metadata):
523
+ padding_masks = torch.stack([md["padding_mask"][0] for md in metadata], dim=0).to(self.device)
524
+ else:
525
+ padding_masks = torch.ones(diffusion_input.shape[0], diffusion_input.shape[-1], dtype=torch.bool, device=self.device)
526
+
527
+ if self.diffusion.pretransform is not None:
528
+ self.diffusion.pretransform.to(self.device)
529
+
530
+ if not self.pre_encoded:
531
+ with torch.amp.autocast("cuda"), torch.no_grad():
532
+ self.diffusion.pretransform.train(self.diffusion.pretransform.enable_grad)
533
+ diffusion_input = self.diffusion.pretransform.encode(diffusion_input)
534
+ padding_masks = resize_padding_mask(padding_masks, diffusion_input.shape[-1])
535
+ else:
536
+ # Apply scale to pre-encoded latents if needed, as the pretransform encode function will not be run
537
+ if hasattr(self.diffusion.pretransform, "scale") and self.diffusion.pretransform.scale != 1.0:
538
+ diffusion_input = diffusion_input / self.diffusion.pretransform.scale
539
+
540
+ if padding_masks.shape[-1] != diffusion_input.shape[-1]:
541
+ padding_masks = resize_padding_mask(padding_masks, diffusion_input.shape[-1])
542
+
543
+ # Use padding mask directly for validation (no silence extension augmentation)
544
+ loss_mask = padding_masks.to(torch.bool)
545
+
546
+ extra_args = {}
547
+ if self.mask_padding_attention:
548
+ extra_args["padding_mask"] = padding_masks
549
+
550
+ # Set up inpainting conditioning for validation (FULL_MASK: all zeros)
551
+ if self.inpainting_config is not None:
552
+ inpaint_mask = torch.zeros(diffusion_input.shape[0], 1, diffusion_input.shape[2], device=self.device)
553
+ inpaint_masked_input = torch.zeros_like(diffusion_input)
554
+ conditioning['inpaint_mask'] = [inpaint_mask]
555
+ conditioning['inpaint_masked_input'] = [inpaint_masked_input]
556
+
557
+ for validation_timestep in self.validation_timesteps:
558
+
559
+ t = torch.full((reals.shape[0],), validation_timestep, device=self.device)
560
+
561
+ # Calculate the noise schedule parameters for those timesteps
562
+ if self.diffusion_objective in ["v"]:
563
+ alphas, sigmas = get_alphas_sigmas(t)
564
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
565
+ alphas, sigmas = 1-t, t
566
+
567
+ # Combine the ground truth data and the noise
568
+ alphas = alphas[:, None, None]
569
+ sigmas = sigmas[:, None, None]
570
+ noise = torch.randn_like(diffusion_input)
571
+ noised_inputs = diffusion_input * alphas + noise * sigmas
572
+
573
+ if self.diffusion_objective == "v":
574
+ targets = noise * alphas - diffusion_input * sigmas
575
+ elif self.diffusion_objective in ["rectified_flow", "rf_denoiser"]:
576
+ targets = noise - diffusion_input
577
+
578
+ with torch.amp.autocast("cuda"), torch.no_grad():
579
+ output = self.diffusion(noised_inputs, t, cond=conditioning, cfg_dropout_prob = 0, **extra_args)
580
+
581
+ mse_loss_full = compute_normalized_mse(output, targets, loss_mask, self.loss_normalization, self.loss_norm_eps)
582
+ val_loss, _, _ = compute_masked_loss(
583
+ mse_loss_full, loss_mask, self.mask_padding_attention, self.mask_loss_weight
584
+ )
585
+
586
+ self.validation_step_outputs[f'val/loss_{validation_timestep:.1f}'].append(val_loss.item())
587
+
588
+ def on_validation_epoch_end(self):
589
+ log_dict = {}
590
+ for validation_timestep in self.validation_timesteps:
591
+ outputs_key = f'val/loss_{validation_timestep:.1f}'
592
+ val_loss = sum(self.validation_step_outputs[outputs_key]) / len(self.validation_step_outputs[outputs_key])
593
+
594
+ # Gather losses across all GPUs
595
+ val_loss = self.all_gather(val_loss).mean().item()
596
+
597
+ log_metric(self.logger, outputs_key, val_loss, step=self.global_step)
598
+
599
+ # Get average over all timesteps
600
+ val_loss = torch.tensor([val for val in self.validation_step_outputs.values()]).mean()
601
+
602
+ # Gather losses across all GPUs
603
+ val_loss = self.all_gather(val_loss).mean().item()
604
+
605
+ log_metric(self.logger, 'val/avg_loss', val_loss, step=self.global_step)
606
+
607
+ # Reset validation losses
608
+ for validation_timestep in self.validation_timesteps:
609
+ self.validation_step_outputs[f'val/loss_{validation_timestep:.1f}'] = []
610
+
611
+
612
+ def export_model(self, path, use_safetensors=False):
613
+ if self.diffusion_ema is not None:
614
+ self.diffusion.model = self.diffusion_ema.ema_model
615
+
616
+ if use_safetensors:
617
+ save_file(self.diffusion.state_dict(), path)
618
+ else:
619
+ torch.save({"state_dict": self.diffusion.state_dict()}, path)
620
+
621
+ def export_lora_safetensors(self, path):
622
+ """Export LoRA weights as a safetensors file with embedded config."""
623
+ if self.lora_config is None:
624
+ raise ValueError("No LoRA config -- this wrapper is not in LoRA mode")
625
+ state_dict = {
626
+ **get_lora_state_dict(self.diffusion.model),
627
+ **get_lora_state_dict(self.diffusion.conditioner)
628
+ }
629
+ save_lora_safetensors(state_dict, self.lora_config, path)
630
+
631
+ def on_save_checkpoint(self, checkpoint):
632
+ if self.lora_config is not None:
633
+ checkpoint.clear()
634
+ checkpoint['state_dict'] = {
635
+ **get_lora_state_dict(self.diffusion.model),
636
+ **get_lora_state_dict(self.diffusion.conditioner)
637
+ }
638
+ checkpoint['lora_config'] = self.lora_config
639
+
640
+ class DiffusionCondInpaintDemoCallback(pl.Callback):
641
+ def __init__(
642
+ self,
643
+ demo_every=2000,
644
+ demo_steps=250,
645
+ sample_size=65536,
646
+ sample_rate=48000,
647
+ demo_cfg_scales: tp.Optional[tp.List[int]] = [3, 5, 7],
648
+ demo_conditioning: tp.Optional[tp.List[tp.Dict[str, tp.Any]]] = None,
649
+ inpaint_demo_config: tp.Optional[tp.Dict[str, int]] = None,
650
+ num_demos: int = 0,
651
+ demo_dl=None,
652
+ ):
653
+ super().__init__()
654
+ self.demo_every = demo_every
655
+ self.demo_steps = demo_steps
656
+ self.demo_samples = sample_size
657
+ self.sample_rate = sample_rate
658
+ self.demo_cfg_scales = demo_cfg_scales
659
+ self.demo_conditioning = demo_conditioning or []
660
+ self.last_demo_step = -1
661
+
662
+ # Map config keys to MaskType enum
663
+ self._mask_type_map = {
664
+ "num_random_segments": MaskType.RANDOM_SEGMENTS,
665
+ "num_full_mask": MaskType.FULL_MASK,
666
+ "num_causal": MaskType.CAUSAL_MASK,
667
+ "num_random_spans": MaskType.RANDOM_SPANS,
668
+ }
669
+
670
+ # Legacy fallback: if no inpaint_demo_config but num_demos is set,
671
+ # use num_demos items with random mask sampling (old behavior)
672
+ if inpaint_demo_config is not None:
673
+ self.inpaint_demo_config = inpaint_demo_config
674
+ self.legacy_inpaint_demos = False
675
+ elif num_demos > 0:
676
+ self.inpaint_demo_config = {}
677
+ self.legacy_inpaint_demos = True
678
+ self.legacy_num_demos = num_demos
679
+ else:
680
+ self.inpaint_demo_config = {}
681
+ self.legacy_inpaint_demos = False
682
+
683
+ # Total inpainting demos needed from batch
684
+ if self.legacy_inpaint_demos:
685
+ self.num_inpaint_demos = self.legacy_num_demos
686
+ else:
687
+ self.num_inpaint_demos = sum(
688
+ self.inpaint_demo_config.get(k, 0) for k in self._mask_type_map
689
+ )
690
+
691
+ if demo_dl is not None:
692
+ self.demo_dl = iter(demo_dl)
693
+ else:
694
+ self.demo_dl = None
695
+
696
+ self._teacher_demo_done = False
697
+
698
+ def _generate_prompt_demos(self, module, trainer, is_rank_zero=True):
699
+ """Generate full t2m demos from specified prompts (FULL_MASK)."""
700
+ if not self.demo_conditioning:
701
+ return [], []
702
+
703
+ demo_cond = self.demo_conditioning
704
+ num_demos = len(demo_cond)
705
+
706
+ demo_samples = self.demo_samples
707
+ if module.diffusion.pretransform is not None:
708
+ demo_samples = demo_samples // module.diffusion.pretransform.downsampling_ratio
709
+
710
+ # Conditioning from prompts
711
+ conditioning = module.diffusion.conditioner(demo_cond, module.device)
712
+
713
+ # FULL_MASK: all-zero inpaint conditioning
714
+ io_channels = module.diffusion.io_channels
715
+ inpaint_mask = torch.zeros(num_demos, 1, demo_samples, device=module.device)
716
+ inpaint_masked_input = torch.zeros(num_demos, io_channels, demo_samples, device=module.device)
717
+ conditioning['inpaint_mask'] = [inpaint_mask]
718
+ conditioning['inpaint_masked_input'] = [inpaint_masked_input]
719
+
720
+ cond_inputs = module.diffusion.get_conditioning_inputs(conditioning)
721
+
722
+ noise = torch.randn(num_demos, io_channels, demo_samples, device=module.device)
723
+ model_dtype = next(module.diffusion.parameters()).dtype
724
+ noise = noise.to(model_dtype)
725
+
726
+ per_elem_trim = compute_per_elem_trim(demo_cond, self.sample_rate, margin_seconds=2)
727
+
728
+ model = module.diffusion_ema.ema_model if module.diffusion_ema is not None else module.diffusion.model
729
+
730
+ all_audio = []
731
+ all_context_masks = []
732
+
733
+ for cfg_scale in self.demo_cfg_scales:
734
+ if is_rank_zero:
735
+ print(f"Generating prompt demos for cfg scale {cfg_scale}")
736
+
737
+ with torch.amp.autocast("cuda"):
738
+ fakes = sample_diffusion(
739
+ model=model,
740
+ noise=noise,
741
+ cond_inputs=cond_inputs,
742
+ diffusion_objective=module.diffusion_objective,
743
+ steps=self.demo_steps,
744
+ cfg_scale=cfg_scale,
745
+ conditioning=demo_cond,
746
+ sample_rate=self.sample_rate,
747
+ pretransform=module.diffusion.pretransform,
748
+ mask_padding_attention=module.diffusion.mask_padding_attention,
749
+ use_effective_length_for_schedule=module.diffusion.use_effective_length_for_schedule,
750
+ headroom_seconds=5.0,
751
+ dist_shift=module.diffusion.sampling_dist_shift,
752
+ batch_cfg=True,
753
+ disable_tqdm=not is_rank_zero,
754
+ decode=True
755
+ )
756
+
757
+ fakes = trim_and_concat(fakes, per_elem_trim)
758
+
759
+ all_audio.append(fakes)
760
+
761
+ # Latent-resolution all-zeros mask (no context for prompt demos),
762
+ # trimmed to match the per-element audio durations
763
+ ds_ratio = module.diffusion.pretransform.downsampling_ratio if module.diffusion.pretransform is not None else 1
764
+ latent_trim = [t // ds_ratio if t is not None else None for t in per_elem_trim] if per_elem_trim is not None else None
765
+ latent_mask = torch.zeros(num_demos, 1, demo_samples)
766
+ context_mask = trim_and_concat(latent_mask, latent_trim).squeeze(0).cpu()
767
+ all_context_masks = [context_mask] * len(self.demo_cfg_scales)
768
+
769
+ del noise, conditioning, cond_inputs, inpaint_mask, inpaint_masked_input
770
+ torch.cuda.empty_cache()
771
+
772
+ return all_audio, all_context_masks
773
+
774
+ def _generate_inpaint_demos(self, module, trainer, is_rank_zero=True):
775
+ """Generate inpainting demos from batch data with forced mask types."""
776
+ if self.num_inpaint_demos == 0 or self.demo_dl is None:
777
+ return [], []
778
+
779
+ demo_reals, metadata = next(self.demo_dl)
780
+
781
+ if demo_reals.ndim == 4 and demo_reals.shape[0] == 1:
782
+ demo_reals = demo_reals[0]
783
+
784
+ demo_reals = demo_reals[:self.num_inpaint_demos]
785
+ metadata = metadata[:self.num_inpaint_demos]
786
+ model_dtype = next(module.diffusion.parameters()).dtype
787
+ demo_reals = demo_reals.to(module.device, dtype=model_dtype)
788
+
789
+ if not module.pre_encoded:
790
+ if module.diffusion.pretransform is not None:
791
+ module.diffusion.pretransform.to(module.device)
792
+ demo_reals = module.diffusion.pretransform.encode(demo_reals)
793
+ else:
794
+ if hasattr(module.diffusion.pretransform, "scale") and module.diffusion.pretransform.scale != 1.0:
795
+ demo_reals = demo_reals / module.diffusion.pretransform.scale
796
+
797
+ padding_masks = torch.stack([md["padding_mask"][0] for md in metadata], dim=0).to(module.device)
798
+ if padding_masks.shape[-1] != demo_reals.shape[-1]:
799
+ padding_masks = resize_padding_mask(padding_masks, demo_reals.shape[-1])
800
+ mask_padding = module.diffusion.mask_padding_attention
801
+
802
+ if self.legacy_inpaint_demos:
803
+ # Legacy: random mask type sampling (old behavior)
804
+ masked_input, mask = random_inpaint_mask(
805
+ demo_reals, padding_masks=padding_masks,
806
+ mask_padding=mask_padding,
807
+ **module.inpaint_mask_kwargs
808
+ )
809
+ else:
810
+ # New: forced mask types per config
811
+ all_masks = []
812
+ all_masked_inputs = []
813
+ idx = 0
814
+ for config_key, mask_type in self._mask_type_map.items():
815
+ count = self.inpaint_demo_config.get(config_key, 0)
816
+ if count == 0:
817
+ continue
818
+ subset_reals = demo_reals[idx:idx+count]
819
+ subset_padding = padding_masks[idx:idx+count]
820
+ mi, m = random_inpaint_mask(
821
+ subset_reals, padding_masks=subset_padding,
822
+ mask_padding=mask_padding, force_mask_type=mask_type,
823
+ **module.inpaint_mask_kwargs
824
+ )
825
+ all_masks.append(m)
826
+ all_masked_inputs.append(mi)
827
+ idx += count
828
+
829
+ mask = torch.cat(all_masks, dim=0)
830
+ masked_input = torch.cat(all_masked_inputs, dim=0)
831
+
832
+ conditioning = module.diffusion.conditioner(metadata, module.device)
833
+ conditioning['inpaint_mask'] = [mask]
834
+ conditioning['inpaint_masked_input'] = [masked_input]
835
+
836
+ cond_inputs = module.diffusion.get_conditioning_inputs(conditioning)
837
+
838
+ demo_samples = demo_reals.shape[2]
839
+ noise = torch.randn(demo_reals.shape[0], module.diffusion.io_channels, demo_samples, device=module.device)
840
+ model_dtype = next(module.diffusion.parameters()).dtype
841
+ noise = noise.to(model_dtype)
842
+
843
+ per_elem_trim = compute_per_elem_trim(metadata, self.sample_rate, margin_seconds=2)
844
+
845
+ # Trim and concatenate context mask at latent resolution,
846
+ # using same trimming basis as audio (per_elem_trim // ds_ratio)
847
+ ds_ratio = module.diffusion.pretransform.downsampling_ratio if module.diffusion.pretransform is not None else 1
848
+ latent_trim = [t // ds_ratio if t is not None else None for t in per_elem_trim] if per_elem_trim is not None else None
849
+
850
+ # Zero out padding region in mask for display — the mask is initialized to 1,
851
+ # so without mask_padding the padding frames show as false context in the overlay
852
+ display_mask = mask * padding_masks.unsqueeze(1)
853
+
854
+ context_mask = trim_and_concat(display_mask, latent_trim).squeeze(0).cpu()
855
+
856
+ model = module.diffusion_ema.ema_model if module.diffusion_ema is not None else module.diffusion.model
857
+
858
+ all_audio = []
859
+ all_context_masks = []
860
+
861
+ for cfg_scale in self.demo_cfg_scales:
862
+ if is_rank_zero:
863
+ print(f"Generating inpaint demos for cfg scale {cfg_scale}")
864
+
865
+ with torch.amp.autocast("cuda"):
866
+ fakes = sample_diffusion(
867
+ model=model,
868
+ noise=noise,
869
+ cond_inputs=cond_inputs,
870
+ diffusion_objective=module.diffusion_objective,
871
+ steps=self.demo_steps,
872
+ cfg_scale=cfg_scale,
873
+ conditioning=metadata,
874
+ sample_rate=self.sample_rate,
875
+ pretransform=module.diffusion.pretransform,
876
+ mask_padding_attention=module.diffusion.mask_padding_attention,
877
+ use_effective_length_for_schedule=module.diffusion.use_effective_length_for_schedule,
878
+ headroom_seconds=5.0,
879
+ dist_shift=module.diffusion.sampling_dist_shift,
880
+ batch_cfg=True,
881
+ disable_tqdm=not is_rank_zero,
882
+ decode=True
883
+ )
884
+
885
+ fakes = trim_and_concat(fakes, per_elem_trim)
886
+
887
+ all_audio.append(fakes)
888
+ all_context_masks.append(context_mask)
889
+
890
+ del noise, conditioning, cond_inputs, mask, masked_input, padding_masks, demo_reals
891
+ torch.cuda.empty_cache()
892
+
893
+ return all_audio, all_context_masks
894
+
895
+ @torch.no_grad()
896
+ def on_train_batch_end(self, trainer, module: DiffusionCondTrainingWrapper, outputs, batch, batch_idx):
897
+ if (trainer.global_step - 1) % self.demo_every != 0 or self.last_demo_step == trainer.global_step:
898
+ return
899
+
900
+ is_rank_zero = get_rank() == 0
901
+
902
+ module.eval()
903
+
904
+ self.last_demo_step = trainer.global_step
905
+
906
+ try:
907
+ # Generate both types of demos, freeing intermediates between phases
908
+ prompt_audio, prompt_masks = self._generate_prompt_demos(module, trainer, is_rank_zero)
909
+ torch.cuda.empty_cache()
910
+
911
+ inpaint_audio, inpaint_masks = self._generate_inpaint_demos(module, trainer, is_rank_zero)
912
+ torch.cuda.empty_cache()
913
+
914
+ # Combine per cfg scale (prompt_audio and inpaint_audio have one entry per cfg scale)
915
+ if is_rank_zero:
916
+ for i, cfg_scale in enumerate(self.demo_cfg_scales):
917
+ parts = []
918
+ mask_parts = []
919
+
920
+ if i < len(prompt_audio):
921
+ parts.append(prompt_audio[i])
922
+ mask_parts.append(prompt_masks[i])
923
+
924
+ if i < len(inpaint_audio):
925
+ parts.append(inpaint_audio[i])
926
+ mask_parts.append(inpaint_masks[i])
927
+
928
+ if not parts:
929
+ continue
930
+
931
+ combined_audio = torch.cat(parts, dim=-1)
932
+ combined_mask = torch.cat(mask_parts, dim=-1) if mask_parts else None
933
+
934
+ filename = f'demo_cfg_{cfg_scale}_{trainer.global_step:08}.wav'
935
+ combined_audio = combined_audio.to(torch.float32).div(torch.max(torch.abs(combined_audio))).mul(32767).to(torch.int16).cpu()
936
+ torchaudio.save(filename, combined_audio, self.sample_rate)
937
+
938
+ log_audio(trainer.logger, f'demo_cfg_{cfg_scale}', filename, self.sample_rate)
939
+ log_image(trainer.logger, f'demo_melspec_left_cfg_{cfg_scale}', audio_spectrogram_image(combined_audio, context_mask=combined_mask))
940
+ if isinstance(trainer.logger, (WandbLogger, CometLogger)):
941
+ os.remove(filename)
942
+
943
+ # Teacher ODE warmup diagnostic: mirror the exact ODE warmup sample_diffusion call
944
+ # and decode the target to verify teacher output quality.
945
+ # Only runs on the first demo.
946
+ # Generates both prompt and inpaint demos, consistent with the main callback.
947
+ teacher_ref = getattr(module, '_teacher', None) or getattr(module, 'teacher_model', None)
948
+ if not self._teacher_demo_done and teacher_ref is not None:
949
+ self._teacher_demo_done = True
950
+ if is_rank_zero:
951
+ print("Generating teacher ODE warmup diagnostic")
952
+ try:
953
+ pretransform = module.diffusion.pretransform # Shared pretransform (not on teacher)
954
+ io_channels = teacher_ref.io_channels
955
+ ode_warmup_config = getattr(module, 'ode_warmup_config', {})
956
+ teacher_cfg = getattr(module, 'ode_warmup_cfg', self.demo_cfg_scales[0])
957
+ ode_steps = getattr(module, 'ode_n_sampling_steps', 20)
958
+ mask_padding = module.diffusion.mask_padding_attention
959
+ ds_ratio = pretransform.downsampling_ratio if pretransform is not None else 1
960
+
961
+ # --- Teacher prompt demos (FULL_MASK, same as _generate_prompt_demos) ---
962
+ prompt_target = None
963
+ prompt_per_elem_trim = None
964
+ prompt_context_mask = None
965
+
966
+ demo_cond = self.demo_conditioning
967
+ if demo_cond:
968
+ num_demos = len(demo_cond)
969
+ demo_samples = self.demo_samples
970
+ if pretransform is not None:
971
+ demo_samples = demo_samples // ds_ratio
972
+
973
+ with torch.no_grad():
974
+ teacher_conditioning = teacher_ref.conditioner(demo_cond, module.device)
975
+ inpaint_mask = torch.zeros(num_demos, 1, demo_samples, device=module.device)
976
+ inpaint_masked_input = torch.zeros(num_demos, io_channels, demo_samples, device=module.device)
977
+ teacher_conditioning['inpaint_mask'] = [inpaint_mask]
978
+ teacher_conditioning['inpaint_masked_input'] = [inpaint_masked_input]
979
+ with torch.no_grad():
980
+ teacher_cond_inputs = teacher_ref.get_conditioning_inputs(teacher_conditioning)
981
+
982
+ noise = torch.randn(num_demos, io_channels, demo_samples, device=module.device)
983
+ noise = noise.to(next(teacher_ref.parameters()).dtype)
984
+ prompt_per_elem_trim = compute_per_elem_trim(demo_cond, self.sample_rate, margin_seconds=2)
985
+
986
+ prompt_target = sample_diffusion(
987
+ model=teacher_ref.model,
988
+ noise=noise,
989
+ cond_inputs=teacher_cond_inputs,
990
+ diffusion_objective=teacher_ref.diffusion_objective,
991
+ steps=ode_steps,
992
+ cfg_scale=teacher_cfg,
993
+ conditioning=demo_cond,
994
+ sample_rate=teacher_ref.sample_rate,
995
+ pretransform=pretransform,
996
+ mask_padding_attention=mask_padding,
997
+ use_effective_length_for_schedule=module.diffusion.use_effective_length_for_schedule,
998
+ padding_mask=None,
999
+ dist_shift=teacher_ref.sampling_dist_shift,
1000
+ sampler_type=ode_warmup_config.get('sampler', 'dpmpp'),
1001
+ batch_cfg=True,
1002
+ disable_tqdm=not is_rank_zero,
1003
+ decode=False,
1004
+ )
1005
+
1006
+ prompt_latent_trim = [t // ds_ratio if t is not None else None for t in prompt_per_elem_trim] if prompt_per_elem_trim is not None else None
1007
+ prompt_context_mask = trim_and_concat(
1008
+ torch.zeros(num_demos, 1, demo_samples), prompt_latent_trim
1009
+ ).squeeze(0).cpu()
1010
+
1011
+ # --- Teacher inpaint demos (same mask logic as _generate_inpaint_demos) ---
1012
+ inpaint_target = None
1013
+ inpaint_per_elem_trim = None
1014
+ inpaint_context_mask = None
1015
+
1016
+ if self.num_inpaint_demos > 0 and self.demo_dl is not None:
1017
+ try:
1018
+ inpaint_reals, inpaint_metadata = next(self.demo_dl)
1019
+ if inpaint_reals.ndim == 4 and inpaint_reals.shape[0] == 1:
1020
+ inpaint_reals = inpaint_reals[0]
1021
+ inpaint_reals = inpaint_reals[:self.num_inpaint_demos]
1022
+ inpaint_metadata = inpaint_metadata[:self.num_inpaint_demos]
1023
+ inpaint_reals = inpaint_reals.to(module.device)
1024
+
1025
+ if not module.pre_encoded:
1026
+ if pretransform is not None:
1027
+ inpaint_reals = pretransform.encode(inpaint_reals)
1028
+ else:
1029
+ if hasattr(pretransform, "scale") and pretransform.scale != 1.0:
1030
+ inpaint_reals = inpaint_reals / pretransform.scale
1031
+
1032
+ inpaint_padding_masks = torch.stack(
1033
+ [md["padding_mask"][0] for md in inpaint_metadata], dim=0
1034
+ ).to(module.device)
1035
+
1036
+ if self.legacy_inpaint_demos:
1037
+ masked_input, mask = random_inpaint_mask(
1038
+ inpaint_reals, padding_masks=inpaint_padding_masks,
1039
+ mask_padding=mask_padding, **module.inpaint_mask_kwargs
1040
+ )
1041
+ else:
1042
+ all_masks = []
1043
+ all_masked_inputs = []
1044
+ idx = 0
1045
+ for config_key, mask_type in self._mask_type_map.items():
1046
+ count = self.inpaint_demo_config.get(config_key, 0)
1047
+ if count == 0:
1048
+ continue
1049
+ mi, m = random_inpaint_mask(
1050
+ inpaint_reals[idx:idx+count],
1051
+ padding_masks=inpaint_padding_masks[idx:idx+count],
1052
+ mask_padding=mask_padding, force_mask_type=mask_type,
1053
+ **module.inpaint_mask_kwargs
1054
+ )
1055
+ all_masks.append(m)
1056
+ all_masked_inputs.append(mi)
1057
+ idx += count
1058
+ mask = torch.cat(all_masks, dim=0)
1059
+ masked_input = torch.cat(all_masked_inputs, dim=0)
1060
+
1061
+ with torch.no_grad():
1062
+ inpaint_teacher_cond = teacher_ref.conditioner(inpaint_metadata, module.device)
1063
+ inpaint_teacher_cond['inpaint_mask'] = [mask]
1064
+ inpaint_teacher_cond['inpaint_masked_input'] = [masked_input]
1065
+ with torch.no_grad():
1066
+ inpaint_cond_inputs = teacher_ref.get_conditioning_inputs(inpaint_teacher_cond)
1067
+
1068
+ inpaint_samples = inpaint_reals.shape[2]
1069
+ inpaint_noise = torch.randn(
1070
+ inpaint_reals.shape[0], io_channels, inpaint_samples, device=module.device
1071
+ ).to(next(teacher_ref.parameters()).dtype)
1072
+ inpaint_per_elem_trim = compute_per_elem_trim(inpaint_metadata, self.sample_rate, margin_seconds=2)
1073
+
1074
+ inpaint_target = sample_diffusion(
1075
+ model=teacher_ref.model,
1076
+ noise=inpaint_noise,
1077
+ cond_inputs=inpaint_cond_inputs,
1078
+ diffusion_objective=teacher_ref.diffusion_objective,
1079
+ steps=ode_steps,
1080
+ cfg_scale=teacher_cfg,
1081
+ conditioning=inpaint_metadata,
1082
+ sample_rate=teacher_ref.sample_rate,
1083
+ pretransform=pretransform,
1084
+ mask_padding_attention=mask_padding,
1085
+ use_effective_length_for_schedule=module.diffusion.use_effective_length_for_schedule,
1086
+ padding_mask=None,
1087
+ dist_shift=teacher_ref.sampling_dist_shift,
1088
+ sampler_type=ode_warmup_config.get('sampler', 'dpmpp'),
1089
+ batch_cfg=True,
1090
+ disable_tqdm=not is_rank_zero,
1091
+ decode=False,
1092
+ )
1093
+
1094
+ # Context mask for overlay (same as _generate_inpaint_demos)
1095
+ display_mask = mask * inpaint_padding_masks.unsqueeze(1)
1096
+ inpaint_latent_trim = [t // ds_ratio if t is not None else None for t in inpaint_per_elem_trim] if inpaint_per_elem_trim is not None else None
1097
+ inpaint_context_mask = trim_and_concat(display_mask, inpaint_latent_trim).squeeze(0).cpu()
1098
+ except StopIteration:
1099
+ if is_rank_zero:
1100
+ print("Teacher diagnostic: no inpaint batch available from demo_dl")
1101
+
1102
+ # --- Combine and log (same pattern as main callback) ---
1103
+ if is_rank_zero:
1104
+ parts = []
1105
+ mask_parts = []
1106
+
1107
+ if prompt_target is not None:
1108
+ decoded_prompt = pretransform.decode(prompt_target.float())
1109
+ decoded_prompt = trim_and_concat(decoded_prompt, prompt_per_elem_trim)
1110
+ parts.append(decoded_prompt)
1111
+ mask_parts.append(prompt_context_mask)
1112
+
1113
+ if inpaint_target is not None:
1114
+ decoded_inpaint = pretransform.decode(inpaint_target.float())
1115
+ decoded_inpaint = trim_and_concat(decoded_inpaint, inpaint_per_elem_trim)
1116
+ parts.append(decoded_inpaint)
1117
+ mask_parts.append(inpaint_context_mask)
1118
+
1119
+ if parts:
1120
+ combined_audio = torch.cat(parts, dim=-1)
1121
+ combined_mask = torch.cat(mask_parts, dim=-1) if mask_parts else None
1122
+ filename = f'demo_teacher_target_{trainer.global_step:08}.wav'
1123
+ combined_audio = combined_audio.to(torch.float32).div(torch.max(torch.abs(combined_audio))).mul(32767).to(torch.int16).cpu()
1124
+ torchaudio.save(filename, combined_audio, self.sample_rate)
1125
+ log_audio(trainer.logger, f'demo_teacher_target', filename, self.sample_rate)
1126
+ log_image(trainer.logger, f'demo_teacher_target_melspec', audio_spectrogram_image(combined_audio, context_mask=combined_mask))
1127
+ os.remove(filename)
1128
+
1129
+ del prompt_target, inpaint_target
1130
+ except Exception as e:
1131
+ if is_rank_zero:
1132
+ print(f"Teacher ODE warmup diagnostic failed: {e}")
1133
+ import traceback
1134
+ traceback.print_exc()
1135
+
1136
+ except Exception as e:
1137
+ if is_rank_zero:
1138
+ print(f'{type(e).__name__}: {e}')
1139
+ raise e
1140
+ finally:
1141
+ gc.collect()
1142
+ torch.cuda.empty_cache()
1143
+ module.train()
stable_audio_3/training/utils.py ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pytorch_lightning.loggers import WandbLogger, CometLogger
2
+ from ..interface.aeiou import pca_point_cloud
3
+
4
+ import math
5
+ import wandb
6
+ import torch
7
+ import torch.nn.functional as F
8
+ import os
9
+ import typing as tp
10
+
11
+ def get_rank():
12
+ """Get rank of current process."""
13
+
14
+ if "SLURM_PROCID" in os.environ:
15
+ return int(os.environ["SLURM_PROCID"])
16
+
17
+ if not torch.distributed.is_available() or not torch.distributed.is_initialized():
18
+ return 0
19
+
20
+ return torch.distributed.get_rank()
21
+
22
+ class InverseLR(torch.optim.lr_scheduler._LRScheduler):
23
+ """Implements an inverse decay learning rate schedule with an optional exponential
24
+ warmup. When last_epoch=-1, sets initial lr as lr.
25
+ inv_gamma is the number of steps/epochs required for the learning rate to decay to
26
+ (1 / 2)**power of its original value.
27
+ Args:
28
+ optimizer (Optimizer): Wrapped optimizer.
29
+ inv_gamma (float): Inverse multiplicative factor of learning rate decay. Default: 1.
30
+ power (float): Exponential factor of learning rate decay. Default: 1.
31
+ warmup (float): Exponential warmup factor (0 <= warmup < 1, 0 to disable)
32
+ Default: 0.
33
+ final_lr (float): The final learning rate. Default: 0.
34
+ last_epoch (int): The index of last epoch. Default: -1.
35
+ """
36
+
37
+ def __init__(self, optimizer, inv_gamma=1., power=1., warmup=0., final_lr=0.,
38
+ last_epoch=-1):
39
+ self.inv_gamma = inv_gamma
40
+ self.power = power
41
+ if not 0. <= warmup < 1:
42
+ raise ValueError('Invalid value for warmup')
43
+ self.warmup = warmup
44
+ self.final_lr = final_lr
45
+ super().__init__(optimizer, last_epoch)
46
+
47
+ def get_lr(self):
48
+ if not self._get_lr_called_within_step:
49
+ import warnings
50
+ warnings.warn("To get the last learning rate computed by the scheduler, "
51
+ "please use `get_last_lr()`.")
52
+
53
+ return self._get_closed_form_lr()
54
+
55
+ def _get_closed_form_lr(self):
56
+ warmup = 1 - self.warmup ** (self.last_epoch + 1)
57
+ lr_mult = (1 + self.last_epoch / self.inv_gamma) ** -self.power
58
+ return [warmup * max(self.final_lr, base_lr * lr_mult)
59
+ for base_lr in self.base_lrs]
60
+
61
+ def create_optimizer_from_config(optimizer_config, parameters):
62
+ """Create optimizer from config.
63
+
64
+ Args:
65
+ parameters (iterable): parameters to optimize.
66
+ optimizer_config (dict): optimizer config.
67
+
68
+ Returns:
69
+ torch.optim.Optimizer: optimizer.
70
+ """
71
+
72
+ optimizer_type = optimizer_config["type"]
73
+
74
+ if optimizer_type == "FusedAdam":
75
+ from deepspeed.ops.adam import FusedAdam
76
+ optimizer = FusedAdam(parameters, **optimizer_config["config"])
77
+ elif optimizer_type == "CAdamW":
78
+ from stable_audio_tools.training.optims import CAdamW
79
+ optimizer = CAdamW(parameters, **optimizer_config["config"])
80
+ elif optimizer_type == "CLion":
81
+ from stable_audio_tools.training.optims import CLion
82
+ optimizer = CLion(parameters, **optimizer_config["config"])
83
+ elif optimizer_type == "AdamW8bit":
84
+ from bitsandbytes.optim import AdamW8bit
85
+ optimizer = AdamW8bit(parameters, **optimizer_config["config"])
86
+ elif optimizer_type == "MuonAdamW":
87
+ from stable_audio_tools.training.optims import MuonAdamW
88
+ optimizer = MuonAdamW(parameters, **optimizer_config["config"])
89
+ else:
90
+ optimizer_fn = getattr(torch.optim, optimizer_type)
91
+ optimizer = optimizer_fn(parameters, **optimizer_config["config"])
92
+ return optimizer
93
+
94
+ def create_scheduler_from_config(scheduler_config, optimizer):
95
+ """Create scheduler from config.
96
+
97
+ Args:
98
+ scheduler_config (dict): scheduler config.
99
+ optimizer (torch.optim.Optimizer): optimizer.
100
+
101
+ Returns:
102
+ torch.optim.lr_scheduler._LRScheduler: scheduler.
103
+ """
104
+ if scheduler_config["type"] == "InverseLR":
105
+ scheduler_fn = InverseLR
106
+ else:
107
+ scheduler_fn = getattr(torch.optim.lr_scheduler, scheduler_config["type"])
108
+ scheduler = scheduler_fn(optimizer, **scheduler_config["config"])
109
+ return scheduler
110
+
111
+ def logger_project_name(logger) -> str:
112
+ if isinstance(logger, WandbLogger):
113
+ return logger.experiment.project
114
+ elif isinstance(logger, CometLogger):
115
+ return logger.name
116
+
117
+ def log_metric(logger, key, value, step=None):
118
+ from pytorch_lightning.loggers import WandbLogger, CometLogger
119
+ if isinstance(logger, WandbLogger):
120
+ logger.experiment.log({key: value})
121
+ elif isinstance(logger, CometLogger):
122
+ logger.experiment.log_metrics({key: value}, step=step)
123
+
124
+ def log_audio(logger, key, audio_path, sample_rate, caption=None, step=None):
125
+ if isinstance(logger, WandbLogger):
126
+ logger.experiment.log({key: wandb.Audio(audio_path, sample_rate=sample_rate, caption=caption)})
127
+ elif isinstance(logger, CometLogger):
128
+ logger.experiment.log_audio(audio_path, file_name=key, sample_rate=sample_rate, step=step)
129
+
130
+ def log_image(logger, key, img_data, step=None):
131
+ if isinstance(logger, WandbLogger):
132
+ logger.experiment.log({key: wandb.Image(img_data)})
133
+ elif isinstance(logger, CometLogger):
134
+ logger.experiment.log_image(img_data, name=key, step=step)
135
+
136
+ def log_point_cloud(logger, key, tokens, caption=None):
137
+ if isinstance(logger, WandbLogger):
138
+ point_cloud = pca_point_cloud(tokens)
139
+ logger.experiment.log({key: point_cloud})
140
+ elif isinstance(logger, CometLogger):
141
+ point_cloud = pca_point_cloud(tokens, rgb_float=True, output_type="points")
142
+ #logger.experiment.log_points_3d(scene_name=key, points=point_cloud)
143
+
144
+
145
+ def compute_per_elem_trim(conditioning, sample_rate, margin_seconds=5.0):
146
+ """Compute per-element trim lengths from seconds_total in conditioning dicts.
147
+
148
+ Returns a list of trim lengths (in audio samples) or None if no seconds_total found.
149
+ """
150
+ if not any("seconds_total" in c for c in conditioning):
151
+ return None
152
+ margin_samples = int(margin_seconds * sample_rate)
153
+ per_elem_trim = []
154
+ for c in conditioning:
155
+ if "seconds_total" in c:
156
+ per_elem_trim.append(int(c["seconds_total"] * sample_rate) + margin_samples)
157
+ else:
158
+ per_elem_trim.append(None)
159
+ return per_elem_trim
160
+
161
+ def trim_and_concat(x, per_elem_trim):
162
+ """Per-element trim and concatenate along time axis.
163
+
164
+ Trims each batch element to its own length (from seconds_total + margin),
165
+ removing trailing padding silence before concatenation.
166
+
167
+ Args:
168
+ x: (b, d, n) tensor or list of (d, n) tensors
169
+ per_elem_trim: list of trim lengths per element, or None for no trimming
170
+ """
171
+ items = [x[i] for i in range(x.shape[0])] if isinstance(x, torch.Tensor) and x.dim() == 3 else x
172
+ if per_elem_trim is None:
173
+ return torch.cat(items, dim=-1)
174
+ parts = []
175
+ for i, elem in enumerate(items):
176
+ if per_elem_trim[i] is not None:
177
+ parts.append(elem[..., :min(per_elem_trim[i], elem.shape[-1])])
178
+ else:
179
+ parts.append(elem)
180
+ return torch.cat(parts, dim=-1)
181
+
182
+
183
+ class StaggeredLogger:
184
+ """Accumulates log values over N steps and flushes averaged metrics.
185
+
186
+ Avoids per-step CUDA syncs (.item()) and prevents PL from caching
187
+ stale values between flush intervals by logging directly to the
188
+ experiment logger.
189
+
190
+ Usage in a PL module::
191
+
192
+ # In __init__:
193
+ self._staggered_logger = StaggeredLogger(every_n_steps=10)
194
+
195
+ # At end of training_step (pass detached tensors or Python scalars):
196
+ self._staggered_logger.log(log_dict, self)
197
+ """
198
+
199
+ def __init__(self, every_n_steps: int = 10):
200
+ self.every_n_steps = every_n_steps
201
+ self._accum: dict = {}
202
+ self._counts: dict = {}
203
+ self._steps: int = 0
204
+
205
+ def log(self, log_dict: dict, module) -> None:
206
+ """Accumulate *log_dict* and flush every *every_n_steps* calls.
207
+
208
+ NaN/inf values are handled at flush time: if an accumulated sum
209
+ is non-finite (due to a NaN step poisoning the window), that
210
+ metric is simply omitted from the flush. This avoids the
211
+ expensive per-step CUDA syncs that an eager ``isfinite`` check
212
+ would require.
213
+
214
+ Args:
215
+ log_dict: Mapping of metric names to detached GPU tensors or
216
+ Python scalars. Tensors are kept on-device until flush.
217
+ module: The ``pl.LightningModule`` instance (used for
218
+ ``log_dict``, ``logger``, and ``global_step``).
219
+ """
220
+ for k, v in log_dict.items():
221
+ val = v.detach() if torch.is_tensor(v) else v
222
+ if k not in self._accum:
223
+ self._accum[k] = val
224
+ self._counts[k] = 1
225
+ else:
226
+ self._accum[k] = self._accum[k] + val
227
+ self._counts[k] += 1
228
+ self._steps += 1
229
+
230
+ if self._steps >= self.every_n_steps:
231
+ flushed = {}
232
+ for k, v in self._accum.items():
233
+ c = self._counts[k]
234
+ if c > 0:
235
+ val = v.item() / c if torch.is_tensor(v) else v / c
236
+ # Skip non-finite values (NaN from a bad step poisoned this window)
237
+ if math.isfinite(val):
238
+ flushed[k] = val
239
+ # Update progress bar only (logger=False avoids PL caching stale values)
240
+ if flushed:
241
+ module.log_dict(flushed, prog_bar=True, on_step=True, logger=False)
242
+ # Log directly to experiment (bypasses PL metric caching)
243
+ if module.logger is not None:
244
+ module.logger.log_metrics(flushed, step=module.global_step)
245
+ self._accum = {}
246
+ self._counts = {}
247
+ self._steps = 0
248
+
249
+
250
+ def resize_padding_mask(padding_mask: torch.Tensor, target_length: int) -> torch.Tensor:
251
+ """Resize a padding mask to target_length using ceiling-based length scaling.
252
+
253
+ Unlike F.interpolate(mode="nearest"), this ensures any target position
254
+ that partially overlaps valid audio is marked valid (rounds up).
255
+
256
+ Args:
257
+ padding_mask: (B, T) or (T,) tensor where 1/True = valid, 0/False = padding
258
+ target_length: desired output length
259
+ Returns:
260
+ Resized boolean mask with same ndim as input
261
+ """
262
+ if padding_mask.ndim == 1:
263
+ valid_length = padding_mask.sum()
264
+ source_length = padding_mask.shape[0]
265
+ valid_target_length = torch.ceil(
266
+ valid_length.float() * target_length / source_length
267
+ ).long().clamp(max=target_length)
268
+ positions = torch.arange(target_length, device=padding_mask.device)
269
+ return positions < valid_target_length
270
+ else:
271
+ valid_lengths = padding_mask.sum(dim=-1) # (B,)
272
+ source_length = padding_mask.shape[-1]
273
+ valid_target_lengths = torch.ceil(
274
+ valid_lengths.float() * target_length / source_length
275
+ ).long().clamp(max=target_length)
276
+ positions = torch.arange(target_length, device=padding_mask.device).unsqueeze(0)
277
+ return positions < valid_target_lengths.unsqueeze(1)
278
+
279
+
280
+ def create_augmented_padding_mask(
281
+ padding_mask: torch.Tensor,
282
+ silence_extension_scale_seconds: float = 0.0,
283
+ sample_rate: int = 44100,
284
+ downsampling_ratio: int = 1,
285
+ ) -> torch.Tensor:
286
+ """
287
+ Augment padding mask by randomly extending the valid region to include silence.
288
+
289
+ This helps the model learn to handle silence at the end of audio by randomly
290
+ including some of the padded (silent) region as valid.
291
+
292
+ Uses an exponential distribution so most extensions are small (within ~scale seconds)
293
+ but can occasionally extend to the full sequence length.
294
+
295
+ Args:
296
+ padding_mask: Boolean tensor of shape (batch, seq_len) where True = valid audio
297
+ silence_extension_scale_seconds: Scale (mean) of exponential distribution in seconds.
298
+ ~63% of samples within 1x scale, ~86% within 2x, ~95% within 3x.
299
+ sample_rate: Audio sample rate (used to convert seconds to tokens)
300
+ downsampling_ratio: Downsampling ratio from pretransform (to convert samples to latent tokens)
301
+
302
+ Returns:
303
+ Augmented padding mask of shape (batch, seq_len) where True = valid region (audio + extended silence)
304
+ """
305
+ if silence_extension_scale_seconds <= 0:
306
+ return padding_mask
307
+
308
+ batch_size, seq_len = padding_mask.shape
309
+ device = padding_mask.device
310
+
311
+ # Find the last valid position for each batch element
312
+ valid_lengths = padding_mask.sum(dim=-1) # (batch,)
313
+
314
+ # Calculate scale in tokens for exponential distribution
315
+ scale_tokens = silence_extension_scale_seconds * sample_rate / downsampling_ratio
316
+
317
+ # Sample from exponential distribution (biased toward smaller values)
318
+ # Exponential: ~63% within 1x scale, ~86% within 2x, ~95% within 3x
319
+ random_extensions = torch.empty(batch_size, device=device).exponential_(lambd=1.0/scale_tokens)
320
+ random_extensions = random_extensions.long()
321
+
322
+ # Extend valid lengths, clamping to sequence length (allows extension to full padding)
323
+ augmented_lengths = torch.clamp(valid_lengths + random_extensions, max=seq_len)
324
+
325
+ # Create augmented mask: True where position < augmented_length
326
+ positions = torch.arange(seq_len, device=device).unsqueeze(0) # (1, seq_len)
327
+ augmented_mask = positions < augmented_lengths.unsqueeze(1) # (batch, seq_len)
328
+
329
+ return augmented_mask
330
+
331
+
332
+ def compute_normalized_mse(
333
+ pred: torch.Tensor,
334
+ target: torch.Tensor,
335
+ loss_mask: torch.Tensor,
336
+ loss_normalization: str = "none",
337
+ loss_norm_eps: float = 1e-6,
338
+ ) -> torch.Tensor:
339
+ """
340
+ Compute MSE normalized by detached target magnitude.
341
+
342
+ Prevents high-magnitude latents from dominating the loss,
343
+ ensuring quiet passages receive equal learning signal.
344
+
345
+ Normalization is only applied to valid (non-padded) regions.
346
+ Padded regions retain raw MSE for compatibility with mask_loss_weight.
347
+
348
+ Args:
349
+ pred: [B, C, T] predicted tensor
350
+ target: [B, C, T] target tensor
351
+ loss_mask: [B, T] boolean, True = valid (non-padded), or None
352
+ loss_normalization: One of "none", "timestep", "sample", "sample_channel"
353
+ loss_norm_eps: Epsilon for numerical stability
354
+
355
+ Returns:
356
+ Per-element MSE [B, C, T], normalized for signal, raw for padding
357
+ """
358
+ mse = (pred - target) ** 2 # [B, C, T]
359
+
360
+ if loss_normalization == "none":
361
+ return mse
362
+
363
+ if loss_mask is None:
364
+ mask_expanded = torch.ones(pred.shape[0], 1, pred.shape[2], device=pred.device, dtype=torch.bool)
365
+ else:
366
+ mask_expanded = loss_mask.unsqueeze(1) # [B, 1, T]
367
+
368
+ with torch.no_grad():
369
+ if loss_normalization == "timestep":
370
+ # Var across channels at each timestep [B, 1, T]
371
+ mag_sq = torch.mean((target - torch.mean(target, dim=1, keepdim=True)) ** 2, dim=1, keepdim=True) + loss_norm_eps
372
+ else:
373
+ # For sample/sample_channel modes, exclude padding via NaN masking
374
+ masked_targets = torch.where(mask_expanded, target, float('nan'))
375
+
376
+ if loss_normalization == "sample":
377
+ sample_mean = torch.nanmean(masked_targets, dim=(1, 2), keepdim=True)
378
+ mag_sq = torch.nanmean((masked_targets - sample_mean) ** 2, dim=(1, 2), keepdim=True) + loss_norm_eps
379
+ elif loss_normalization == "sample_channel":
380
+ channel_mean = torch.nanmean(masked_targets, dim=2, keepdim=True)
381
+ mag_sq = torch.nanmean((masked_targets - channel_mean) ** 2, dim=2, keepdim=True) + loss_norm_eps
382
+ else:
383
+ raise ValueError(f"Unknown loss normalization mode: {loss_normalization}")
384
+
385
+ # Handle edge case where entire sample is padding (all NaN -> NaN variance)
386
+ mag_sq = torch.where(torch.isnan(mag_sq), torch.ones_like(mag_sq), mag_sq)
387
+
388
+ # Only normalize signal regions; keep padding MSE raw for mask_loss_weight compatibility
389
+ normalized_mse = mse / mag_sq
390
+ return torch.where(mask_expanded, normalized_mse, mse)
391
+
392
+
393
+ def compute_masked_loss(
394
+ loss_full: torch.Tensor,
395
+ loss_mask: torch.Tensor,
396
+ mask_padding_attention: bool,
397
+ mask_loss_weight: float = 0.0,
398
+ ) -> tuple:
399
+ """
400
+ Compute loss with separate signal and padding contributions.
401
+
402
+ Args:
403
+ loss_full: Full loss tensor of shape (B, C, T)
404
+ loss_mask: Boolean mask of shape (B, T) where True = signal, False = padding
405
+ mask_padding_attention: If True, only compute loss on signal region
406
+ mask_loss_weight: Weight for padding loss when mask_padding_attention is False
407
+
408
+ Returns:
409
+ Tuple of (loss, signal_mean, padding_mean) where:
410
+ - loss: The final scalar loss
411
+ - signal_mean: Mean loss on signal region (for logging)
412
+ - padding_mean: Mean loss on padding region (for logging)
413
+ """
414
+ # Compute separate signal and padding losses
415
+ signal = torch.where(loss_mask.unsqueeze(1), loss_full, 0.0)
416
+ signal_sum = signal.sum(dim=(1, 2))
417
+ n_channels = loss_full.shape[1]
418
+ signal_count = loss_mask.sum(dim=1) * n_channels
419
+
420
+ padding = torch.where(~loss_mask.unsqueeze(1), loss_full, 0.0)
421
+ padding_sum = padding.sum(dim=(1, 2))
422
+ padding_count = (~loss_mask).sum(dim=1) * n_channels
423
+
424
+ if mask_padding_attention:
425
+ # When attention masking is on, only compute loss on signal region
426
+ # (padding saw no attention, so loss there is meaningless)
427
+ per_sample_loss = signal_sum / (signal_count + 1e-8)
428
+ loss = per_sample_loss.mean()
429
+ else:
430
+ # Weighted loss: signal + mask_loss_weight * padding
431
+ w = mask_loss_weight
432
+ denom = signal_count + w * padding_count + 1e-8
433
+ per_sample_loss = (signal_sum + w * padding_sum) / denom
434
+ loss = per_sample_loss.mean()
435
+
436
+ # Compute separate means for logging
437
+ signal_mean = signal.sum() / (signal_count.sum() + 1e-8)
438
+ padding_mean = padding.sum() / (padding_count.sum() + 1e-8)
439
+
440
+ return loss, signal_mean.detach(), padding_mean.detach()
441
+
442
+
443
+ def masked_mean(
444
+ values: torch.Tensor,
445
+ mask: torch.Tensor,
446
+ ) -> torch.Tensor:
447
+ """
448
+ Compute mean over masked (valid) positions only.
449
+
450
+ Args:
451
+ values: Tensor of shape (B, C, T) or (B, T)
452
+ mask: Boolean mask of shape (B, T) where True = valid, False = padding
453
+
454
+ Returns:
455
+ Scalar mean over valid positions only
456
+ """
457
+ if values.ndim == 2:
458
+ # (B, T) -> add channel dim
459
+ values = values.unsqueeze(1)
460
+
461
+ # Expand mask to match values shape: (B, T) -> (B, 1, T)
462
+ mask_expanded = mask.unsqueeze(1)
463
+
464
+ # Zero out padding positions
465
+ masked_values = torch.where(mask_expanded, values, torch.zeros_like(values))
466
+
467
+ # Count valid positions (per sample, times channels)
468
+ n_channels = values.shape[1]
469
+ valid_count = mask.sum(dim=1) * n_channels # (B,)
470
+
471
+ # Sum over all dimensions and divide by valid count
472
+ per_sample_sum = masked_values.sum(dim=(1, 2)) # (B,)
473
+ per_sample_mean = per_sample_sum / (valid_count + 1e-8)
474
+
475
+ return per_sample_mean.mean()
476
+
477
+
478
+ def masked_sum(
479
+ values: torch.Tensor,
480
+ mask: torch.Tensor,
481
+ dim: tp.List[int],
482
+ ) -> torch.Tensor:
483
+ """
484
+ Compute sum over masked (valid) positions only.
485
+
486
+ Args:
487
+ values: Tensor of shape (B, C, T) or (B, T)
488
+ mask: Boolean mask of shape (B, T) where True = valid, False = padding
489
+ dim: Dimensions to sum over (should include the time dimension)
490
+
491
+ Returns:
492
+ Sum over valid positions, reduced over specified dims
493
+ """
494
+ if values.ndim == 2:
495
+ # (B, T) -> add channel dim for consistency
496
+ mask_expanded = mask
497
+ else:
498
+ # (B, C, T) -> expand mask
499
+ mask_expanded = mask.unsqueeze(1)
500
+
501
+ # Zero out padding positions
502
+ masked_values = torch.where(mask_expanded, values, torch.zeros_like(values))
503
+
504
+ return masked_values.sum(dim=tuple(dim))
stable_audio_3/verbose.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Shared verbose flag for user-facing prints.
2
+ Defaults off. Toggle via `set_verbose(True)` (e.g. from run_gradio.py --verbose)
3
+ or by setting the environment variable `SAT_VERBOSE=1`.
4
+ Warnings and errors should continue to use plain `print()` — `vprint()` is only
5
+ for informational/progress messages that are noisy at default verbosity.
6
+ """
7
+
8
+ import os
9
+
10
+ VERBOSE = os.environ.get("SAT_VERBOSE", "0") == "1"
11
+
12
+
13
+ def set_verbose(v: bool):
14
+ global VERBOSE
15
+ VERBOSE = bool(v)
16
+
17
+
18
+ def vprint(*args, **kwargs):
19
+ if VERBOSE:
20
+ print(*args, **kwargs)