Spaces:
Paused
Paused
Vendor stable-audio-3 for ZeroGPU
Browse files- README.md +8 -0
- STABLE_AUDIO_3_LICENSE +21 -0
- requirements.txt +3 -0
- stable_audio_3/__init__.py +2 -0
- stable_audio_3/cli.py +292 -0
- stable_audio_3/data/__init__.py +0 -0
- stable_audio_3/data/dataset.py +510 -0
- stable_audio_3/data/utils.py +257 -0
- stable_audio_3/factory.py +157 -0
- stable_audio_3/inference/__init__.py +0 -0
- stable_audio_3/inference/audio_utils.py +98 -0
- stable_audio_3/inference/distribution_shift.py +198 -0
- stable_audio_3/inference/sampling.py +523 -0
- stable_audio_3/interface/__init__.py +0 -0
- stable_audio_3/interface/aeiou.py +303 -0
- stable_audio_3/interface/diffusion_cond.py +644 -0
- stable_audio_3/interface/reprompt.py +481 -0
- stable_audio_3/loading_utils.py +86 -0
- stable_audio_3/model.py +554 -0
- stable_audio_3/model_configs.py +116 -0
- stable_audio_3/models/__init__.py +0 -0
- stable_audio_3/models/autoencoders.py +639 -0
- stable_audio_3/models/blocks.py +84 -0
- stable_audio_3/models/bottleneck.py +71 -0
- stable_audio_3/models/conditioners.py +318 -0
- stable_audio_3/models/diffusion.py +253 -0
- stable_audio_3/models/dit.py +643 -0
- stable_audio_3/models/inpainting.py +242 -0
- stable_audio_3/models/lora/__init__.py +28 -0
- stable_audio_3/models/lora/loader.py +112 -0
- stable_audio_3/models/lora/model.py +471 -0
- stable_audio_3/models/lora/utils.py +420 -0
- stable_audio_3/models/pretransforms.py +84 -0
- stable_audio_3/models/transformer.py +1272 -0
- stable_audio_3/models/utils.py +19 -0
- stable_audio_3/training/diffusion.py +1143 -0
- stable_audio_3/training/utils.py +504 -0
- 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)
|