Spaces:
Running on Zero
Running on Zero
Vansh Chugh commited on
Commit ·
83e59db
1
Parent(s): 0b9c252
initial deploy
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +4 -0
- README.md +8 -7
- SOURCES.md +4 -0
- app.py +257 -0
- beat_this/__init__.py +0 -0
- beat_this/inference.py +315 -0
- beat_this/model/__init__.py +0 -0
- beat_this/model/beat_tracker.py +346 -0
- beat_this/model/postprocessor.py +197 -0
- beat_this/model/roformer.py +181 -0
- beat_this/preprocessing.py +59 -0
- beat_this/utils.py +111 -0
- model.json +18 -0
- requirements.txt +22 -0
- src/__init__.py +0 -0
- src/audiocraft/__init__.py +26 -0
- src/audiocraft/data/__init__.py +12 -0
- src/audiocraft/data/audio.py +231 -0
- src/audiocraft/data/audio_dataset.py +587 -0
- src/audiocraft/data/audio_utils.py +176 -0
- src/audiocraft/data/zip.py +76 -0
- src/audiocraft/environment.py +176 -0
- src/audiocraft/models/__init__.py +19 -0
- src/audiocraft/models/builders.py +261 -0
- src/audiocraft/models/encodec.py +506 -0
- src/audiocraft/models/genmodel.py +267 -0
- src/audiocraft/models/lm.py +547 -0
- src/audiocraft/models/lm_magnet.py +498 -0
- src/audiocraft/models/loaders.py +178 -0
- src/audiocraft/models/magnet.py +88 -0
- src/audiocraft/models/musicgen.py +308 -0
- src/audiocraft/models/unet.py +214 -0
- src/audiocraft/modules/__init__.py +22 -0
- src/audiocraft/modules/activations.py +96 -0
- src/audiocraft/modules/chroma.py +66 -0
- src/audiocraft/modules/codebooks_patterns.py +548 -0
- src/audiocraft/modules/conditioners.py +1416 -0
- src/audiocraft/modules/conv.py +243 -0
- src/audiocraft/modules/diffusion_schedule.py +272 -0
- src/audiocraft/modules/lstm.py +25 -0
- src/audiocraft/modules/rope.py +125 -0
- src/audiocraft/modules/seanet.py +258 -0
- src/audiocraft/modules/streaming.py +131 -0
- src/audiocraft/modules/transformer.py +755 -0
- src/audiocraft/py.typed +0 -0
- src/audiocraft/quantization/__init__.py +9 -0
- src/audiocraft/quantization/base.py +99 -0
- src/audiocraft/quantization/core_vq.py +405 -0
- src/audiocraft/quantization/vq.py +115 -0
- src/audiocraft/utils/__init__.py +6 -0
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.DS_Store
|
| 4 |
+
loopgen-repo/
|
README.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
| 1 |
---
|
| 2 |
+
title: LoopGen
|
| 3 |
+
emoji: 🔁
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.28.0
|
| 8 |
+
python_version: '3.11'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
+
license: mit
|
| 12 |
---
|
| 13 |
|
| 14 |
+
Training-free loopable music generation from a text description, using MusicGen + MAGNeT. Ported from [LoopGen](https://github.com/gladia-research-group/loopgen) (Marincione et al., 2025) for use with [HARP](https://github.com/TEAMuP-dev/HARP).
|
SOURCES.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Sources — loopgen
|
| 2 |
+
|
| 3 |
+
- Source repo: https://github.com/gladia-research-group/loopgen.git
|
| 4 |
+
- Paper: https://arxiv.org/abs/2504.04466
|
app.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
try:
|
| 2 |
+
import spaces
|
| 3 |
+
except ImportError:
|
| 4 |
+
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
|
| 5 |
+
class spaces:
|
| 6 |
+
class GPU:
|
| 7 |
+
def __init__(self, func=None, duration=60):
|
| 8 |
+
self.func = func
|
| 9 |
+
|
| 10 |
+
def __call__(self, *args, **kwargs):
|
| 11 |
+
if self.func is not None:
|
| 12 |
+
return self.func(*args, **kwargs)
|
| 13 |
+
func = args[0]
|
| 14 |
+
return func
|
| 15 |
+
|
| 16 |
+
import sys
|
| 17 |
+
sys.stdout.reconfigure(line_buffering=True)
|
| 18 |
+
|
| 19 |
+
import contextlib
|
| 20 |
+
import gc
|
| 21 |
+
import os
|
| 22 |
+
import tempfile
|
| 23 |
+
import threading
|
| 24 |
+
|
| 25 |
+
import gradio as gr
|
| 26 |
+
import numpy as np
|
| 27 |
+
import torch
|
| 28 |
+
from huggingface_hub import hf_hub_download
|
| 29 |
+
from pyharp import ModelCard, build_endpoint
|
| 30 |
+
|
| 31 |
+
from beat_this.inference import File2Beats
|
| 32 |
+
from src.audiocraft.data.audio import audio_write
|
| 33 |
+
from src.audiocraft.models import MusicGen
|
| 34 |
+
from src.loopgen import LoopGen
|
| 35 |
+
|
| 36 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 37 |
+
|
| 38 |
+
MUSICGEN_MODEL = "facebook/musicgen-medium"
|
| 39 |
+
MAGNET_MODEL = "facebook/magnet-medium-30secs"
|
| 40 |
+
MAX_CFG = 5.0 # classifier-free guidance ceiling (default: 5.0, per paper)
|
| 41 |
+
MIN_CFG = 1.0 # classifier-free guidance floor, annealed down to this (default: 1.0, per paper)
|
| 42 |
+
RESCORE_WEIGHT = 0.5 # MAGNeT/MusicGen rescoring interpolation weight (default: 0.5, per paper)
|
| 43 |
+
HINT_DURATION = 30.0 # seconds of MusicGen hint audio generated for beat detection (default: 30.0, per repo)
|
| 44 |
+
MAX_RETRIES = 5 # beat-alignment retries before falling back to a fixed length (default: 5, per repo)
|
| 45 |
+
|
| 46 |
+
beat_checkpoint_path = None
|
| 47 |
+
beat_checkpoint_ready = False
|
| 48 |
+
beat_checkpoint_error = None
|
| 49 |
+
|
| 50 |
+
musicgen = None
|
| 51 |
+
loopgen = None
|
| 52 |
+
file2beats = None
|
| 53 |
+
model_lock = threading.Lock()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def download_beat_checkpoint():
|
| 57 |
+
"""Fetch the beat-tracking checkpoint in the background so the server can
|
| 58 |
+
start immediately. Reuses the already-deployed teamup-tech/beat-this
|
| 59 |
+
Space's copy instead of re-uploading the same file here."""
|
| 60 |
+
global beat_checkpoint_path, beat_checkpoint_ready, beat_checkpoint_error
|
| 61 |
+
try:
|
| 62 |
+
beat_checkpoint_path = hf_hub_download(
|
| 63 |
+
repo_id="teamup-tech/beat-this", filename="final0.ckpt", repo_type="space"
|
| 64 |
+
)
|
| 65 |
+
print("Beat-tracking checkpoint downloaded.")
|
| 66 |
+
except Exception as e:
|
| 67 |
+
beat_checkpoint_error = str(e)
|
| 68 |
+
print(f"Beat checkpoint download error: {e}")
|
| 69 |
+
finally:
|
| 70 |
+
beat_checkpoint_ready = True
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
threading.Thread(target=download_beat_checkpoint, daemon=True).start()
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
model_card = ModelCard(
|
| 77 |
+
name="LoopGen",
|
| 78 |
+
description="Generates a seamlessly loopable music clip from a text description, training-free, using MusicGen + MAGNeT.",
|
| 79 |
+
author="Davide Marincione, Giorgio Strano, Donato Crisostomi, Roberto Ribuoli, Emanuele Rodolà",
|
| 80 |
+
tags=["music generation", "loop", "text-to-music"],
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def _write_temp_wav(wav, sample_rate):
|
| 85 |
+
"""Write a generated waveform to a fresh temp file and return its path.
|
| 86 |
+
Adapted from pipelines.py, which reused hardcoded local paths like
|
| 87 |
+
f"musicgen_base_{name}_{seed}.wav" across calls (reused via
|
| 88 |
+
os.path.exists) -- only valid for one person rerunning experiments
|
| 89 |
+
locally, since concurrent Space users could collide on the same
|
| 90 |
+
seed/name and clobber or read each other's files."""
|
| 91 |
+
fd, stem = tempfile.mkstemp()
|
| 92 |
+
os.close(fd)
|
| 93 |
+
os.remove(stem) # audio_write creates its own file at stem + extension
|
| 94 |
+
path = audio_write(stem, wav, sample_rate, loudness_compressor=True,
|
| 95 |
+
strategy="loudness", loudness_headroom_db=16)
|
| 96 |
+
return str(path)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _beat_aligned_length(path, min_length, max_length, unit_size=0.02, length_retries=2):
|
| 100 |
+
"""Find a loop length, in 20ms units, aligned to a whole number of bars,
|
| 101 |
+
within [min_length, max_length] seconds. Adapted from pipelines.py's
|
| 102 |
+
get_beat_perfect_hint, unchanged except for taking an already-constructed
|
| 103 |
+
file2beats (construction moved to process_fn, see its docstring)."""
|
| 104 |
+
min_units = min_length // unit_size
|
| 105 |
+
max_units = max_length // unit_size
|
| 106 |
+
|
| 107 |
+
beats, downbeats = file2beats(path)
|
| 108 |
+
|
| 109 |
+
diffs = []
|
| 110 |
+
prev = None
|
| 111 |
+
beats_per_bar = []
|
| 112 |
+
beats_per_this_bar = 0
|
| 113 |
+
downbeat_idx = 0
|
| 114 |
+
for beat in beats:
|
| 115 |
+
while downbeat_idx < len(downbeats) and beat > downbeats[downbeat_idx]:
|
| 116 |
+
beats_per_bar.append(beats_per_this_bar)
|
| 117 |
+
beats_per_this_bar = 0
|
| 118 |
+
downbeat_idx += 1
|
| 119 |
+
beats_per_this_bar += 1
|
| 120 |
+
if prev is not None:
|
| 121 |
+
diffs.append(beat - prev)
|
| 122 |
+
prev = beat
|
| 123 |
+
|
| 124 |
+
if len(beats_per_bar) < 1:
|
| 125 |
+
return None
|
| 126 |
+
|
| 127 |
+
diffs = sorted(diffs)
|
| 128 |
+
beats_per_bar = sorted(beats_per_bar)
|
| 129 |
+
|
| 130 |
+
beat_length = diffs[len(diffs) // 2] // unit_size + 1 # quantize the median to unit_size
|
| 131 |
+
beats_per_bar = beats_per_bar[len(beats_per_bar) // 2] # median beats-per-bar
|
| 132 |
+
bar_length = beats_per_bar * beat_length
|
| 133 |
+
length = bar_length * 12
|
| 134 |
+
if length <= 0:
|
| 135 |
+
return None
|
| 136 |
+
|
| 137 |
+
loops_done = 0
|
| 138 |
+
while length < min_units or length > max_units:
|
| 139 |
+
if length < min_units:
|
| 140 |
+
length *= 2
|
| 141 |
+
else:
|
| 142 |
+
length /= 2
|
| 143 |
+
loops_done += 1
|
| 144 |
+
|
| 145 |
+
if loops_done > length_retries:
|
| 146 |
+
return None
|
| 147 |
+
return length
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
@spaces.GPU(duration=300)
|
| 151 |
+
@torch.inference_mode()
|
| 152 |
+
def process_fn(description: str, min_length: float, max_length: float, seed: int) -> str:
|
| 153 |
+
"""Generates a seamlessly loopable music clip: a MusicGen hint is used to
|
| 154 |
+
find a beat-aligned loop length (retrying up to MAX_RETRIES times), then
|
| 155 |
+
MAGNeT generates the final loop as a continuation of that hint. Adapted
|
| 156 |
+
from pipelines.py's loopgen_loop.
|
| 157 |
+
|
| 158 |
+
Model construction happens here, inside the GPU call, rather than in the
|
| 159 |
+
background-download thread above: LoopGen/MusicGen.get_pretrained()
|
| 160 |
+
bakes device="cuda" in immediately, and ZeroGPU only allows CUDA calls
|
| 161 |
+
made inside an @spaces.GPU-decorated call, not from a background thread."""
|
| 162 |
+
global musicgen, loopgen, file2beats
|
| 163 |
+
|
| 164 |
+
if not beat_checkpoint_ready:
|
| 165 |
+
raise gr.Error("Beat-tracking checkpoint is still downloading, please try again shortly.")
|
| 166 |
+
if beat_checkpoint_error is not None:
|
| 167 |
+
raise gr.Error(f"Beat-tracking checkpoint failed to download: {beat_checkpoint_error}")
|
| 168 |
+
|
| 169 |
+
with model_lock:
|
| 170 |
+
if musicgen is None:
|
| 171 |
+
musicgen = MusicGen.get_pretrained(MUSICGEN_MODEL, device=DEVICE)
|
| 172 |
+
if loopgen is None:
|
| 173 |
+
loopgen = LoopGen.get_pretrained(MAGNET_MODEL, device=DEVICE)
|
| 174 |
+
if file2beats is None:
|
| 175 |
+
file2beats = File2Beats(checkpoint_path=beat_checkpoint_path, device=DEVICE, dbn=False)
|
| 176 |
+
|
| 177 |
+
torch.manual_seed(seed)
|
| 178 |
+
np.random.seed(seed)
|
| 179 |
+
|
| 180 |
+
unit_length = None
|
| 181 |
+
hint = None
|
| 182 |
+
sr = None
|
| 183 |
+
tries = 0
|
| 184 |
+
while unit_length is None:
|
| 185 |
+
gc.collect()
|
| 186 |
+
if DEVICE == "cuda":
|
| 187 |
+
torch.cuda.empty_cache()
|
| 188 |
+
if tries >= MAX_RETRIES:
|
| 189 |
+
unit_length = 350 * 3
|
| 190 |
+
break
|
| 191 |
+
del hint
|
| 192 |
+
tries += 1
|
| 193 |
+
musicgen.set_generation_params(duration=HINT_DURATION)
|
| 194 |
+
hint = musicgen.generate([description])[0].to(device="cpu", dtype=torch.float32)
|
| 195 |
+
sr = musicgen.sample_rate
|
| 196 |
+
hint_path = _write_temp_wav(hint, sr)
|
| 197 |
+
unit_length = _beat_aligned_length(hint_path, min_length, max_length)
|
| 198 |
+
os.remove(hint_path)
|
| 199 |
+
|
| 200 |
+
gc.collect()
|
| 201 |
+
if DEVICE == "cuda":
|
| 202 |
+
torch.cuda.empty_cache()
|
| 203 |
+
|
| 204 |
+
left_hint = [hint[..., :int(0.02 * sr) * int(unit_length / 2)]]
|
| 205 |
+
valid_tokens = [int(unit_length)]
|
| 206 |
+
|
| 207 |
+
setup = {
|
| 208 |
+
"span_arrangement": "stride1",
|
| 209 |
+
"use_sampling": True,
|
| 210 |
+
"top_k": 0,
|
| 211 |
+
"top_p": 0.9,
|
| 212 |
+
"temperature": 3.0,
|
| 213 |
+
"max_cfg_coef": MAX_CFG,
|
| 214 |
+
"min_cfg_coef": MIN_CFG,
|
| 215 |
+
"decoding_steps": [100, 50, 10, 10],
|
| 216 |
+
"rescorer": musicgen.lm,
|
| 217 |
+
"rescore_weights": RESCORE_WEIGHT,
|
| 218 |
+
"offset": False,
|
| 219 |
+
}
|
| 220 |
+
loopgen.set_generation_params(**setup)
|
| 221 |
+
|
| 222 |
+
autocast_ctx = (torch.autocast(device_type="cuda", dtype=torch.float16)
|
| 223 |
+
if DEVICE == "cuda" else contextlib.nullcontext())
|
| 224 |
+
with autocast_ctx:
|
| 225 |
+
results = loopgen.generate_continuation(
|
| 226 |
+
text_prompt=description, negative_text_prompt=None,
|
| 227 |
+
left_hint=left_hint, left_hint_sr=sr, valid_tokens=valid_tokens,
|
| 228 |
+
)
|
| 229 |
+
result = results[0].to(device="cpu", dtype=torch.float32)
|
| 230 |
+
|
| 231 |
+
return _write_temp_wav(result, loopgen.sample_rate)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
with gr.Blocks() as demo:
|
| 235 |
+
input_components = [
|
| 236 |
+
gr.Textbox(label="Description",
|
| 237 |
+
info="Text description of the music to generate."),
|
| 238 |
+
gr.Slider(minimum=5, maximum=30, step=1, value=15, label="Min Loop Length (s)",
|
| 239 |
+
info="Shortest acceptable loop length in seconds (default: 15, per repo)."),
|
| 240 |
+
gr.Slider(minimum=5, maximum=30, step=1, value=30, label="Max Loop Length (s)",
|
| 241 |
+
info="Longest acceptable loop length in seconds (default: 30, per repo)."),
|
| 242 |
+
gr.Number(value=42, precision=0, label="Seed",
|
| 243 |
+
info="Random seed for generation (default: 42, per repo)."),
|
| 244 |
+
]
|
| 245 |
+
output_components = [
|
| 246 |
+
gr.Audio(type="filepath", label="Output Audio").set_info("Generated loopable music clip."),
|
| 247 |
+
]
|
| 248 |
+
|
| 249 |
+
build_endpoint(
|
| 250 |
+
model_card=model_card,
|
| 251 |
+
input_components=input_components,
|
| 252 |
+
output_components=output_components,
|
| 253 |
+
process_fn=process_fn,
|
| 254 |
+
)
|
| 255 |
+
|
| 256 |
+
if __name__ == "__main__":
|
| 257 |
+
demo.queue().launch(pwa=True)
|
beat_this/__init__.py
ADDED
|
File without changes
|
beat_this/inference.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import inspect
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import soxr
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn.functional as F
|
| 7 |
+
|
| 8 |
+
from beat_this.model.beat_tracker import BeatThis
|
| 9 |
+
from beat_this.model.postprocessor import Postprocessor
|
| 10 |
+
from beat_this.preprocessing import LogMelSpect, load_audio
|
| 11 |
+
from beat_this.utils import replace_state_dict_key, save_beat_tsv
|
| 12 |
+
|
| 13 |
+
CHECKPOINT_URL = "https://cloud.cp.jku.at/public.php/dav/files/7ik4RrBKTS273gp"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def load_checkpoint(checkpoint_path: str, device: str | torch.device = "cpu") -> dict:
|
| 17 |
+
"""
|
| 18 |
+
Load a BeatThis checkpoint as a dictionary.
|
| 19 |
+
|
| 20 |
+
Args:
|
| 21 |
+
checkpoint_path (str, optional): The path to the checkpoint. Can be a local path, a URL, or a shortname.
|
| 22 |
+
device (torch.device or str): The device to load the model on.
|
| 23 |
+
|
| 24 |
+
Returns:
|
| 25 |
+
dict: The loaded checkpoint dictionary.
|
| 26 |
+
"""
|
| 27 |
+
try:
|
| 28 |
+
# try interpreting as local file name
|
| 29 |
+
weights_only = {"weights_only": True} if torch.__version__ >= "2" else {}
|
| 30 |
+
return torch.load(checkpoint_path, map_location=device, **weights_only)
|
| 31 |
+
except FileNotFoundError:
|
| 32 |
+
try:
|
| 33 |
+
if not (
|
| 34 |
+
str(checkpoint_path).startswith("https://")
|
| 35 |
+
or str(checkpoint_path).startswith("http://")
|
| 36 |
+
):
|
| 37 |
+
# interpret it as a name of one of our checkpoints
|
| 38 |
+
checkpoint_url = f"{CHECKPOINT_URL}/{checkpoint_path}.ckpt"
|
| 39 |
+
file_name = f"beat_this-{checkpoint_path}.ckpt"
|
| 40 |
+
else:
|
| 41 |
+
# try interpreting as a URL
|
| 42 |
+
checkpoint_url = checkpoint_path
|
| 43 |
+
file_name = None
|
| 44 |
+
return torch.hub.load_state_dict_from_url(
|
| 45 |
+
checkpoint_url,
|
| 46 |
+
file_name=file_name,
|
| 47 |
+
map_location=device,
|
| 48 |
+
)
|
| 49 |
+
except Exception:
|
| 50 |
+
raise ValueError(
|
| 51 |
+
"Could not load the checkpoint given the provided name",
|
| 52 |
+
checkpoint_path,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def load_model(
|
| 57 |
+
checkpoint_path: str | None = "final0", device: str | torch.device = "cpu"
|
| 58 |
+
) -> BeatThis:
|
| 59 |
+
"""
|
| 60 |
+
Load a BeatThis model from a checkpoint.
|
| 61 |
+
|
| 62 |
+
Args:
|
| 63 |
+
checkpoint_path (str, optional): The path to the checkpoint. Can be a local path, a URL, or a shortname.
|
| 64 |
+
device (torch.device or str): The device to load the model on.
|
| 65 |
+
|
| 66 |
+
Returns:
|
| 67 |
+
BeatThis: The loaded model.
|
| 68 |
+
"""
|
| 69 |
+
if checkpoint_path is not None:
|
| 70 |
+
checkpoint = load_checkpoint(checkpoint_path, device)
|
| 71 |
+
# Retrieve the model hyperparameters as it could be the small model
|
| 72 |
+
hparams = checkpoint["hyper_parameters"]
|
| 73 |
+
# Filter only those hyperparameters that apply to the model itself
|
| 74 |
+
hparams = {
|
| 75 |
+
k: v
|
| 76 |
+
for k, v in hparams.items()
|
| 77 |
+
if k in set(inspect.signature(BeatThis).parameters)
|
| 78 |
+
}
|
| 79 |
+
# Create the uninitialized model
|
| 80 |
+
model = BeatThis(**hparams)
|
| 81 |
+
# The PLBeatThis (LightningModule) state_dict contains the BeatThis
|
| 82 |
+
# state_dict under the "model." prefix; remove the prefix to load it
|
| 83 |
+
state_dict = replace_state_dict_key(checkpoint["state_dict"], "model.", "")
|
| 84 |
+
model.load_state_dict(state_dict)
|
| 85 |
+
else:
|
| 86 |
+
model = BeatThis()
|
| 87 |
+
return model.to(device).eval()
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def zeropad(spect: torch.Tensor, left: int = 0, right: int = 0):
|
| 91 |
+
"""
|
| 92 |
+
Pads a tensor spectrogram matrix of shape (time x bins) with `left` frames in the beginning and `right` frames in the end.
|
| 93 |
+
"""
|
| 94 |
+
if left == 0 and right == 0:
|
| 95 |
+
return spect
|
| 96 |
+
else:
|
| 97 |
+
return F.pad(spect, (0, 0, left, right), "constant", 0)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def split_piece(
|
| 101 |
+
spect: torch.Tensor,
|
| 102 |
+
chunk_size: int,
|
| 103 |
+
border_size: int = 6,
|
| 104 |
+
avoid_short_end: bool = True,
|
| 105 |
+
):
|
| 106 |
+
"""
|
| 107 |
+
Split a tensor spectrogram matrix of shape (time x bins) into time chunks of `chunk_size` and return the chunks and starting positions.
|
| 108 |
+
The `border_size` is the number of frames assumed to be discarded in the predictions on either side (since the model was not trained on the input edges due to the max-pool in the loss).
|
| 109 |
+
To cater for this, the first and last chunk are padded by `border_size` on the beginning and end, respectively, and consecutive chunks overlap by `border_size`.
|
| 110 |
+
If `avoid_short_end` is true, the last chunk start is shifted left to ends at the end of the piece, therefore the last chunk can potentially overlap with previous chunks more than border_size, otherwise it will be a shorter segment.
|
| 111 |
+
If the piece is shorter than `chunk_size`, avoid_short_end is ignored and the piece is returned as a single shorter chunk.
|
| 112 |
+
|
| 113 |
+
Args:
|
| 114 |
+
spect (torch.Tensor): The input spectrogram tensor of shape (time x bins).
|
| 115 |
+
chunk_size (int): The size of the chunks to produce.
|
| 116 |
+
border_size (int, optional): The size of the border to overlap between chunks. Defaults to 6.
|
| 117 |
+
avoid_short_end (bool, optional): If True, the last chunk is shifted left to end at the end of the piece. Defaults to True.
|
| 118 |
+
"""
|
| 119 |
+
# generate the start and end indices
|
| 120 |
+
starts = np.arange(
|
| 121 |
+
-border_size, len(spect) - border_size, chunk_size - 2 * border_size
|
| 122 |
+
)
|
| 123 |
+
if avoid_short_end and len(spect) > chunk_size - 2 * border_size:
|
| 124 |
+
# if we avoid short ends, move the last index to the end of the piece - (chunk_size - border_size)
|
| 125 |
+
starts[-1] = len(spect) - (chunk_size - border_size)
|
| 126 |
+
# generate the chunks
|
| 127 |
+
chunks = [
|
| 128 |
+
zeropad(
|
| 129 |
+
spect[max(start, 0) : min(start + chunk_size, len(spect))],
|
| 130 |
+
left=max(0, -start),
|
| 131 |
+
right=max(0, min(border_size, start + chunk_size - len(spect))),
|
| 132 |
+
)
|
| 133 |
+
for start in starts
|
| 134 |
+
]
|
| 135 |
+
return chunks, starts
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def aggregate_prediction(
|
| 139 |
+
pred_chunks: list,
|
| 140 |
+
starts: list,
|
| 141 |
+
full_size: int,
|
| 142 |
+
chunk_size: int,
|
| 143 |
+
border_size: int,
|
| 144 |
+
overlap_mode: str,
|
| 145 |
+
device: str | torch.device,
|
| 146 |
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 147 |
+
"""
|
| 148 |
+
Aggregates the predictions for the whole piece based on the given prediction chunks.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
pred_chunks (list): List of prediction chunks, where each chunk is a dictionary containing 'beat' and 'downbeat' predictions.
|
| 152 |
+
starts (list): List of start positions for each prediction chunk.
|
| 153 |
+
full_size (int): Size of the full piece.
|
| 154 |
+
chunk_size (int): Size of each prediction chunk.
|
| 155 |
+
border_size (int): Size of the border to be discarded from each prediction chunk.
|
| 156 |
+
overlap_mode (str): Mode for handling overlapping predictions. Can be 'keep_first' or 'keep_last'.
|
| 157 |
+
device (torch.device): Device to be used for the predictions.
|
| 158 |
+
|
| 159 |
+
Returns:
|
| 160 |
+
tuple: A tuple containing the aggregated beat predictions and downbeat predictions as torch tensors for the whole piece.
|
| 161 |
+
"""
|
| 162 |
+
if border_size > 0:
|
| 163 |
+
# cut the predictions to discard the border
|
| 164 |
+
pred_chunks = [
|
| 165 |
+
{
|
| 166 |
+
"beat": pchunk["beat"][border_size:-border_size],
|
| 167 |
+
"downbeat": pchunk["downbeat"][border_size:-border_size],
|
| 168 |
+
}
|
| 169 |
+
for pchunk in pred_chunks
|
| 170 |
+
]
|
| 171 |
+
# aggregate the predictions for the whole piece
|
| 172 |
+
piece_prediction_beat = torch.full((full_size,), -1000.0, device=device)
|
| 173 |
+
piece_prediction_downbeat = torch.full((full_size,), -1000.0, device=device)
|
| 174 |
+
if overlap_mode == "keep_first":
|
| 175 |
+
# process in reverse order, so predictions of earlier excerpts overwrite later ones
|
| 176 |
+
pred_chunks = reversed(list(pred_chunks))
|
| 177 |
+
starts = reversed(list(starts))
|
| 178 |
+
for start, pchunk in zip(starts, pred_chunks):
|
| 179 |
+
piece_prediction_beat[
|
| 180 |
+
start + border_size : start + chunk_size - border_size
|
| 181 |
+
] = pchunk["beat"]
|
| 182 |
+
piece_prediction_downbeat[
|
| 183 |
+
start + border_size : start + chunk_size - border_size
|
| 184 |
+
] = pchunk["downbeat"]
|
| 185 |
+
return piece_prediction_beat, piece_prediction_downbeat
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def split_predict_aggregate(
|
| 189 |
+
spect: torch.Tensor,
|
| 190 |
+
chunk_size: int,
|
| 191 |
+
border_size: int,
|
| 192 |
+
overlap_mode: str,
|
| 193 |
+
model: torch.nn.Module,
|
| 194 |
+
) -> dict:
|
| 195 |
+
"""
|
| 196 |
+
Function for pieces that are longer than the training length of the model.
|
| 197 |
+
Split the input piece into chunks, run the model on them, and aggregate the predictions.
|
| 198 |
+
The spect is supposed to be a torch tensor of shape (time x bins), i.e., unbatched, and the output is also unbatched.
|
| 199 |
+
|
| 200 |
+
Args:
|
| 201 |
+
spect (torch.Tensor): the input piece
|
| 202 |
+
chunk_size (int): the length of the chunks
|
| 203 |
+
border_size (int): the size of the border that is discarded from the predictions
|
| 204 |
+
overlap_mode (str): how to handle overlaps between chunks
|
| 205 |
+
model (torch.nn.Module): the model to run
|
| 206 |
+
|
| 207 |
+
Returns:
|
| 208 |
+
dict: the model framewise predictions for the hole piece as a dictionary containing 'beat' and 'downbeat' predictions.
|
| 209 |
+
"""
|
| 210 |
+
# split the piece into chunks
|
| 211 |
+
chunks, starts = split_piece(
|
| 212 |
+
spect, chunk_size, border_size=border_size, avoid_short_end=True
|
| 213 |
+
)
|
| 214 |
+
# run the model
|
| 215 |
+
pred_chunks = [model(chunk.unsqueeze(0)) for chunk in chunks]
|
| 216 |
+
# remove the extra dimension in beat and downbeat prediction due to batch size 1
|
| 217 |
+
pred_chunks = [
|
| 218 |
+
{"beat": p["beat"][0], "downbeat": p["downbeat"][0]} for p in pred_chunks
|
| 219 |
+
]
|
| 220 |
+
piece_prediction_beat, piece_prediction_downbeat = aggregate_prediction(
|
| 221 |
+
pred_chunks,
|
| 222 |
+
starts,
|
| 223 |
+
spect.shape[0],
|
| 224 |
+
chunk_size,
|
| 225 |
+
border_size,
|
| 226 |
+
overlap_mode,
|
| 227 |
+
spect.device,
|
| 228 |
+
)
|
| 229 |
+
# save it to model_prediction
|
| 230 |
+
return {"beat": piece_prediction_beat, "downbeat": piece_prediction_downbeat}
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class Spect2Frames:
|
| 234 |
+
"""
|
| 235 |
+
Class for extracting framewise beat and downbeat predictions (logits) from a spectrogram.
|
| 236 |
+
"""
|
| 237 |
+
|
| 238 |
+
def __init__(self, checkpoint_path="final0", device="cpu", float16=False):
|
| 239 |
+
super().__init__()
|
| 240 |
+
self.device = torch.device(device)
|
| 241 |
+
self.float16 = float16
|
| 242 |
+
self.model = load_model(checkpoint_path, self.device)
|
| 243 |
+
|
| 244 |
+
def spect2frames(self, spect):
|
| 245 |
+
with torch.inference_mode():
|
| 246 |
+
with torch.autocast(enabled=self.float16, device_type=self.device.type):
|
| 247 |
+
model_prediction = split_predict_aggregate(
|
| 248 |
+
spect=spect,
|
| 249 |
+
chunk_size=1500,
|
| 250 |
+
overlap_mode="keep_first",
|
| 251 |
+
border_size=6,
|
| 252 |
+
model=self.model,
|
| 253 |
+
)
|
| 254 |
+
return model_prediction["beat"].float(), model_prediction["downbeat"].float()
|
| 255 |
+
|
| 256 |
+
def __call__(self, spect):
|
| 257 |
+
return self.spect2frames(spect)
|
| 258 |
+
|
| 259 |
+
|
| 260 |
+
class Audio2Frames(Spect2Frames):
|
| 261 |
+
"""
|
| 262 |
+
Class for extracting framewise beat and downbeat predictions (logits) from an audio tensor.
|
| 263 |
+
"""
|
| 264 |
+
|
| 265 |
+
def __init__(self, checkpoint_path="final0", device="cpu", float16=False):
|
| 266 |
+
super().__init__(checkpoint_path, device, float16)
|
| 267 |
+
self.spect = LogMelSpect(device=self.device)
|
| 268 |
+
|
| 269 |
+
def signal2spect(self, signal, sr):
|
| 270 |
+
if signal.ndim == 2:
|
| 271 |
+
signal = signal.mean(1)
|
| 272 |
+
elif signal.ndim != 1:
|
| 273 |
+
raise ValueError(f"Expected 1D or 2D signal, got shape {signal.shape}")
|
| 274 |
+
if sr != 22050:
|
| 275 |
+
signal = soxr.resample(signal, in_rate=sr, out_rate=22050)
|
| 276 |
+
signal = torch.tensor(signal, dtype=torch.float32, device=self.device)
|
| 277 |
+
return self.spect(signal)
|
| 278 |
+
|
| 279 |
+
def __call__(self, signal, sr):
|
| 280 |
+
spect = self.signal2spect(signal, sr)
|
| 281 |
+
return self.spect2frames(spect)
|
| 282 |
+
|
| 283 |
+
|
| 284 |
+
class Audio2Beats(Audio2Frames):
|
| 285 |
+
"""
|
| 286 |
+
Class for extracting beat and downbeat positions (in seconds) from an audio tensor.
|
| 287 |
+
|
| 288 |
+
Args:
|
| 289 |
+
checkpoint_path (str): Path to the model checkpoint file. It can be a local path, a URL, or a key from the CHECKPOINT_URL dictionary. Default is "final0", which will load the model trained on all data except GTZAN with seed 0.
|
| 290 |
+
device (str): Device to use for inference. Default is "cpu".
|
| 291 |
+
float16 (bool): Whether to use half precision floating point arithmetic. Default is False.
|
| 292 |
+
dbn (bool): Whether to use the madmom DBN for post-processing. Default is False.
|
| 293 |
+
"""
|
| 294 |
+
|
| 295 |
+
def __init__(
|
| 296 |
+
self, checkpoint_path="final0", device="cpu", float16=False, dbn=False
|
| 297 |
+
):
|
| 298 |
+
super().__init__(checkpoint_path, device, float16)
|
| 299 |
+
self.frames2beats = Postprocessor(type="dbn" if dbn else "minimal")
|
| 300 |
+
|
| 301 |
+
def __call__(self, signal, sr):
|
| 302 |
+
beat_logits, downbeat_logits = super().__call__(signal, sr)
|
| 303 |
+
return self.frames2beats(beat_logits, downbeat_logits)
|
| 304 |
+
|
| 305 |
+
|
| 306 |
+
class File2Beats(Audio2Beats):
|
| 307 |
+
def __call__(self, audio_path):
|
| 308 |
+
signal, sr = load_audio(audio_path)
|
| 309 |
+
return super().__call__(signal, sr)
|
| 310 |
+
|
| 311 |
+
|
| 312 |
+
class File2File(File2Beats):
|
| 313 |
+
def __call__(self, audio_path, output_path):
|
| 314 |
+
downbeats, beats = super().__call__(audio_path)
|
| 315 |
+
save_beat_tsv(downbeats, beats, output_path)
|
beat_this/model/__init__.py
ADDED
|
File without changes
|
beat_this/model/beat_tracker.py
ADDED
|
@@ -0,0 +1,346 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Model definitions for the Beat This! beat tracker.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import contextlib
|
| 6 |
+
from collections import OrderedDict
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
from einops import rearrange
|
| 10 |
+
from einops.layers.torch import Rearrange
|
| 11 |
+
from rotary_embedding_torch import RotaryEmbedding
|
| 12 |
+
from torch import nn
|
| 13 |
+
|
| 14 |
+
from beat_this.model import roformer
|
| 15 |
+
from beat_this.utils import replace_state_dict_key
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class BeatThis(nn.Module):
|
| 19 |
+
"""
|
| 20 |
+
A neural network model for beat tracking. It is composed of three main components:
|
| 21 |
+
- a frontend that processes the input spectrogram,
|
| 22 |
+
- a series of transformer blocks that process the output of the frontend,
|
| 23 |
+
- a head that produces the final beat and downbeat predictions.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
spect_dim (int): The dimension of the input spectrogram (default: 128).
|
| 27 |
+
transformer_dim (int): The dimension of the main transformer blocks (default: 512).
|
| 28 |
+
ff_mult (int): The multiplier for the feed-forward dimension in the transformer blocks (default: 4).
|
| 29 |
+
n_layers (int): The number of transformer blocks (default: 6).
|
| 30 |
+
head_dim (int): The dimension of each attention head for the partial transformers in the frontend and the transformer blocks (default: 32).
|
| 31 |
+
stem_dim (int): The out dimension of the stem convolutional layer (default: 32).
|
| 32 |
+
dropout (dict): A dictionary specifying the dropout rates for different parts of the model
|
| 33 |
+
(default: {"frontend": 0.1, "transformer": 0.2}).
|
| 34 |
+
sum_head (bool): Whether to use a SumHead for the final predictions (default: True) or plain independent projections.
|
| 35 |
+
partial_transformers (bool): Whether to include partial frequency- and time-transformers in the frontend (default: True)
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
def __init__(
|
| 39 |
+
self,
|
| 40 |
+
spect_dim: int = 128,
|
| 41 |
+
transformer_dim: int = 512,
|
| 42 |
+
ff_mult: int = 4,
|
| 43 |
+
n_layers: int = 6,
|
| 44 |
+
head_dim: int = 32,
|
| 45 |
+
stem_dim: int = 32,
|
| 46 |
+
dropout: dict = {"frontend": 0.1, "transformer": 0.2},
|
| 47 |
+
sum_head: bool = True,
|
| 48 |
+
partial_transformers: bool = True,
|
| 49 |
+
):
|
| 50 |
+
super().__init__()
|
| 51 |
+
# shared rotary embedding for frontend blocks and transformer blocks
|
| 52 |
+
rotary_embed = RotaryEmbedding(head_dim)
|
| 53 |
+
|
| 54 |
+
# create the frontend
|
| 55 |
+
# - stem
|
| 56 |
+
stem = self.make_stem(spect_dim, stem_dim)
|
| 57 |
+
spect_dim //= 4 # frequencies were convolved with stride 4
|
| 58 |
+
# - three frontend blocks
|
| 59 |
+
frontend_blocks = []
|
| 60 |
+
dim = stem_dim
|
| 61 |
+
for _ in range(3):
|
| 62 |
+
frontend_blocks.append(
|
| 63 |
+
self.make_frontend_block(
|
| 64 |
+
dim,
|
| 65 |
+
dim * 2,
|
| 66 |
+
partial_transformers,
|
| 67 |
+
head_dim,
|
| 68 |
+
rotary_embed,
|
| 69 |
+
dropout["frontend"],
|
| 70 |
+
)
|
| 71 |
+
)
|
| 72 |
+
dim *= 2
|
| 73 |
+
spect_dim //= 2 # frequencies were convolved with stride 2
|
| 74 |
+
frontend_blocks = nn.Sequential(*frontend_blocks)
|
| 75 |
+
# - linear projection to transformer dimensionality
|
| 76 |
+
concat = Rearrange("b c f t -> b t (c f)")
|
| 77 |
+
linear = nn.Linear(dim * spect_dim, transformer_dim)
|
| 78 |
+
self.frontend = nn.Sequential(
|
| 79 |
+
OrderedDict(stem=stem, blocks=frontend_blocks, concat=concat, linear=linear)
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# create the transformer blocks
|
| 83 |
+
assert (
|
| 84 |
+
transformer_dim % head_dim == 0
|
| 85 |
+
), "transformer_dim must be divisible by head_dim"
|
| 86 |
+
n_heads = transformer_dim // head_dim
|
| 87 |
+
self.transformer_blocks = roformer.Transformer(
|
| 88 |
+
dim=transformer_dim,
|
| 89 |
+
depth=n_layers,
|
| 90 |
+
heads=n_heads,
|
| 91 |
+
attn_dropout=dropout["transformer"],
|
| 92 |
+
ff_dropout=dropout["transformer"],
|
| 93 |
+
rotary_embed=rotary_embed,
|
| 94 |
+
ff_mult=ff_mult,
|
| 95 |
+
dim_head=head_dim,
|
| 96 |
+
norm_output=True,
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# create the output heads
|
| 100 |
+
if sum_head:
|
| 101 |
+
self.task_heads = SumHead(transformer_dim)
|
| 102 |
+
else:
|
| 103 |
+
self.task_heads = Head(transformer_dim)
|
| 104 |
+
|
| 105 |
+
# init all weights
|
| 106 |
+
self.apply(self._init_weights)
|
| 107 |
+
|
| 108 |
+
@staticmethod
|
| 109 |
+
def make_stem(spect_dim: int, stem_dim: int) -> nn.Module:
|
| 110 |
+
return nn.Sequential(
|
| 111 |
+
OrderedDict(
|
| 112 |
+
rearrange_tf=Rearrange("b t f -> b f t"),
|
| 113 |
+
bn1d=nn.BatchNorm1d(spect_dim),
|
| 114 |
+
add_channel=Rearrange("b f t -> b 1 f t"),
|
| 115 |
+
conv2d=nn.Conv2d(
|
| 116 |
+
in_channels=1,
|
| 117 |
+
out_channels=stem_dim,
|
| 118 |
+
kernel_size=(4, 3),
|
| 119 |
+
stride=(4, 1),
|
| 120 |
+
padding=(0, 1),
|
| 121 |
+
bias=False,
|
| 122 |
+
),
|
| 123 |
+
bn2d=nn.BatchNorm2d(stem_dim),
|
| 124 |
+
activation=nn.GELU(),
|
| 125 |
+
)
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
@staticmethod
|
| 129 |
+
def make_frontend_block(
|
| 130 |
+
in_dim: int,
|
| 131 |
+
out_dim: int,
|
| 132 |
+
partial_transformers: bool = True,
|
| 133 |
+
head_dim: int | None = 32,
|
| 134 |
+
rotary_embed: RotaryEmbedding | None = None,
|
| 135 |
+
dropout: float = 0.1,
|
| 136 |
+
) -> nn.Module:
|
| 137 |
+
if partial_transformers and (head_dim is None or rotary_embed is None):
|
| 138 |
+
raise ValueError(
|
| 139 |
+
"Must specify head_dim and rotary_embed for using partial_transformers"
|
| 140 |
+
)
|
| 141 |
+
return nn.Sequential(
|
| 142 |
+
OrderedDict(
|
| 143 |
+
partial=(
|
| 144 |
+
PartialFTTransformer(
|
| 145 |
+
dim=in_dim,
|
| 146 |
+
dim_head=head_dim,
|
| 147 |
+
n_head=in_dim // head_dim,
|
| 148 |
+
rotary_embed=rotary_embed,
|
| 149 |
+
dropout=dropout,
|
| 150 |
+
)
|
| 151 |
+
if partial_transformers
|
| 152 |
+
else nn.Identity()
|
| 153 |
+
),
|
| 154 |
+
# conv block
|
| 155 |
+
conv2d=nn.Conv2d(
|
| 156 |
+
in_channels=in_dim,
|
| 157 |
+
out_channels=out_dim,
|
| 158 |
+
kernel_size=(2, 3),
|
| 159 |
+
stride=(2, 1),
|
| 160 |
+
padding=(0, 1),
|
| 161 |
+
bias=False,
|
| 162 |
+
),
|
| 163 |
+
# out_channels : 64, 128, 256
|
| 164 |
+
# freqs : 16, 8, 4 (due to the stride=2)
|
| 165 |
+
norm=nn.BatchNorm2d(out_dim),
|
| 166 |
+
activation=nn.GELU(),
|
| 167 |
+
)
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
@staticmethod
|
| 171 |
+
def _init_weights(module: nn.Module):
|
| 172 |
+
if isinstance(module, (nn.Linear, nn.Conv1d)):
|
| 173 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 174 |
+
if module.bias is not None:
|
| 175 |
+
torch.nn.init.zeros_(module.bias)
|
| 176 |
+
elif isinstance(module, nn.Conv2d):
|
| 177 |
+
torch.nn.init.kaiming_normal_(
|
| 178 |
+
module.weight, mode="fan_out", nonlinearity="relu"
|
| 179 |
+
)
|
| 180 |
+
if module.bias is not None:
|
| 181 |
+
torch.nn.init.zeros_(module.bias)
|
| 182 |
+
elif isinstance(module, nn.Embedding):
|
| 183 |
+
torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
| 184 |
+
if module.padding_idx is not None:
|
| 185 |
+
with torch.no_grad():
|
| 186 |
+
module.weight[module.padding_idx].fill_(0)
|
| 187 |
+
|
| 188 |
+
def forward(self, x):
|
| 189 |
+
x = self.frontend(x)
|
| 190 |
+
x = self.transformer_blocks(x)
|
| 191 |
+
x = self.task_heads(x)
|
| 192 |
+
return x
|
| 193 |
+
|
| 194 |
+
def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
|
| 195 |
+
# remove _orig_mod prefixes for compiled models
|
| 196 |
+
state_dict = replace_state_dict_key(state_dict, "_orig_mod.", "")
|
| 197 |
+
super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
| 198 |
+
|
| 199 |
+
def state_dict(self, *args, **kwargs):
|
| 200 |
+
state_dict = super().state_dict(*args, **kwargs)
|
| 201 |
+
# remove _orig_mod prefixes for compiled models
|
| 202 |
+
state_dict = replace_state_dict_key(state_dict, "_orig_mod.", "")
|
| 203 |
+
return state_dict
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class PartialRoformer(nn.Module):
|
| 207 |
+
"""
|
| 208 |
+
Takes a (batch, channels, freqs, time) input, applies self-attention and
|
| 209 |
+
a feed-forward block either only across frequencies or only across time.
|
| 210 |
+
Returns a tensor of the same shape as the input.
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
def __init__(
|
| 214 |
+
self,
|
| 215 |
+
dim: int,
|
| 216 |
+
dim_head: int,
|
| 217 |
+
n_head: int,
|
| 218 |
+
direction: str,
|
| 219 |
+
rotary_embed: RotaryEmbedding,
|
| 220 |
+
dropout: float,
|
| 221 |
+
):
|
| 222 |
+
super().__init__()
|
| 223 |
+
|
| 224 |
+
assert dim % dim_head == 0, "dim must be divisible by dim_head"
|
| 225 |
+
assert dim // dim_head == n_head, "n_head must be equal to dim // dim_head"
|
| 226 |
+
self.direction = direction[0].lower()
|
| 227 |
+
if self.direction not in "ft":
|
| 228 |
+
raise ValueError(f"direction must be F or T, got {direction}")
|
| 229 |
+
self.attn = roformer.Attention(
|
| 230 |
+
dim,
|
| 231 |
+
heads=n_head,
|
| 232 |
+
dim_head=dim_head,
|
| 233 |
+
dropout=dropout,
|
| 234 |
+
rotary_embed=rotary_embed,
|
| 235 |
+
)
|
| 236 |
+
self.ff = roformer.FeedForward(dim, dropout=dropout)
|
| 237 |
+
|
| 238 |
+
def forward(self, x):
|
| 239 |
+
b = len(x)
|
| 240 |
+
if self.direction == "f":
|
| 241 |
+
pattern = "(b t) f c"
|
| 242 |
+
elif self.direction == "t":
|
| 243 |
+
pattern = "(b f) t c"
|
| 244 |
+
x = rearrange(x, f"b c f t -> {pattern}")
|
| 245 |
+
x = x + self.attn(x)
|
| 246 |
+
x = x + self.ff(x)
|
| 247 |
+
x = rearrange(x, f"{pattern} -> b c f t", b=b)
|
| 248 |
+
return x
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
class PartialFTTransformer(nn.Module):
|
| 252 |
+
"""
|
| 253 |
+
Takes a (batch, channels, freqs, time) input, applies self-attention and
|
| 254 |
+
a feed-forward block once across frequencies and once across time. Same
|
| 255 |
+
as applying two PartialRoformer() in sequence, but encapsulated in a single
|
| 256 |
+
module. Returns a tensor of the same shape as the input.
|
| 257 |
+
"""
|
| 258 |
+
|
| 259 |
+
def __init__(
|
| 260 |
+
self,
|
| 261 |
+
dim: int,
|
| 262 |
+
dim_head: int,
|
| 263 |
+
n_head: int,
|
| 264 |
+
rotary_embed: RotaryEmbedding,
|
| 265 |
+
dropout: float,
|
| 266 |
+
):
|
| 267 |
+
super().__init__()
|
| 268 |
+
|
| 269 |
+
assert dim % dim_head == 0, "dim must be divisible by dim_head"
|
| 270 |
+
assert dim // dim_head == n_head, "n_head must be equal to dim // dim_head"
|
| 271 |
+
# frequency directed partial transformer
|
| 272 |
+
self.attnF = roformer.Attention(
|
| 273 |
+
dim,
|
| 274 |
+
heads=n_head,
|
| 275 |
+
dim_head=dim_head,
|
| 276 |
+
dropout=dropout,
|
| 277 |
+
rotary_embed=rotary_embed,
|
| 278 |
+
)
|
| 279 |
+
self.ffF = roformer.FeedForward(dim, dropout=dropout)
|
| 280 |
+
# time directed partial transformer
|
| 281 |
+
self.attnT = roformer.Attention(
|
| 282 |
+
dim,
|
| 283 |
+
heads=n_head,
|
| 284 |
+
dim_head=dim_head,
|
| 285 |
+
dropout=dropout,
|
| 286 |
+
rotary_embed=rotary_embed,
|
| 287 |
+
)
|
| 288 |
+
self.ffT = roformer.FeedForward(dim, dropout=dropout)
|
| 289 |
+
|
| 290 |
+
def forward(self, x):
|
| 291 |
+
b = len(x)
|
| 292 |
+
# frequency directed partial transformer
|
| 293 |
+
x = rearrange(x, "b c f t -> (b t) f c")
|
| 294 |
+
x = x + self.attnF(x)
|
| 295 |
+
x = x + self.ffF(x)
|
| 296 |
+
# time directed partial transformer
|
| 297 |
+
x = rearrange(x, "(b t) f c ->(b f) t c", b=b)
|
| 298 |
+
x = x + self.attnT(x)
|
| 299 |
+
x = x + self.ffT(x)
|
| 300 |
+
x = rearrange(x, "(b f) t c -> b c f t", b=b)
|
| 301 |
+
return x
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
class SumHead(nn.Module):
|
| 305 |
+
"""
|
| 306 |
+
A PyTorch module that produces the final beat and downbeat prediction logits.
|
| 307 |
+
The beats are a sum of all beats and all downbeats predictions, to reduce the prediction
|
| 308 |
+
of downbeats which are not beats.
|
| 309 |
+
"""
|
| 310 |
+
|
| 311 |
+
def __init__(self, input_dim):
|
| 312 |
+
super().__init__()
|
| 313 |
+
self.beat_downbeat_lin = nn.Linear(input_dim, 2)
|
| 314 |
+
|
| 315 |
+
def forward(self, x):
|
| 316 |
+
beat_downbeat = self.beat_downbeat_lin(x)
|
| 317 |
+
# separate beat from downbeat
|
| 318 |
+
beat, downbeat = rearrange(beat_downbeat, "b t c -> c b t", c=2)
|
| 319 |
+
# aggregate beats and downbeats prediction
|
| 320 |
+
# autocast to float16 disabled to avoid numerical issues causing NaNs
|
| 321 |
+
if hasattr(
|
| 322 |
+
torch.amp, "is_autocast_available"
|
| 323 |
+
) and not torch.amp.is_autocast_available(beat.device.type):
|
| 324 |
+
# but do not try disabling if the device does not support autocast
|
| 325 |
+
disable_autocast = contextlib.nullcontext()
|
| 326 |
+
else:
|
| 327 |
+
disable_autocast = torch.autocast(beat.device.type, enabled=False)
|
| 328 |
+
with disable_autocast:
|
| 329 |
+
beat = beat.float() + downbeat.float()
|
| 330 |
+
return {"beat": beat, "downbeat": downbeat}
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
class Head(nn.Module):
|
| 334 |
+
"""
|
| 335 |
+
A PyToch module that produces the final beat and downbeat prediction logits with independent linear layers outputs.
|
| 336 |
+
"""
|
| 337 |
+
|
| 338 |
+
def __init__(self, input_dim):
|
| 339 |
+
super().__init__()
|
| 340 |
+
self.beat_downbeat_lin = nn.Linear(input_dim, 2)
|
| 341 |
+
|
| 342 |
+
def forward(self, x):
|
| 343 |
+
beat_downbeat = self.beat_downbeat_lin(x)
|
| 344 |
+
# separate beat from downbeat
|
| 345 |
+
beat, downbeat = rearrange(beat_downbeat, "b t c -> c b t", c=2)
|
| 346 |
+
return {"beat": beat, "downbeat": downbeat}
|
beat_this/model/postprocessor.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
import torch
|
| 5 |
+
import torch.nn.functional as F
|
| 6 |
+
from einops import rearrange
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class Postprocessor:
|
| 10 |
+
"""Postprocessor for the beat and downbeat predictions of the model.
|
| 11 |
+
The postprocessor takes the (framewise) model predictions (beat and downbeats) and the padding mask,
|
| 12 |
+
and returns the postprocessed beat and downbeat as list of times in seconds.
|
| 13 |
+
The beats and downbeats can be 1D arrays (for only 1 piece) or 2D arrays, if a batch of pieces is considered.
|
| 14 |
+
The output dimensionality is the same as the input dimensionality.
|
| 15 |
+
Two types of postprocessing are implemented:
|
| 16 |
+
- minimal: a simple postprocessing that takes the maximum of the framewise predictions,
|
| 17 |
+
and removes adjacent peaks.
|
| 18 |
+
- dbn: a postprocessing based on the Dynamic Bayesian Network proposed by Böck et al.
|
| 19 |
+
Args:
|
| 20 |
+
type (str): the type of postprocessing to apply. Either "minimal" or "dbn". Default is "minimal".
|
| 21 |
+
fps (int): the frames per second of the model framewise predictions. Default is 50.
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, type: str = "minimal", fps: int = 50):
|
| 25 |
+
assert type in ["minimal", "dbn"]
|
| 26 |
+
self.type = type
|
| 27 |
+
self.fps = fps
|
| 28 |
+
if type == "dbn":
|
| 29 |
+
from madmom.features.downbeats import DBNDownBeatTrackingProcessor
|
| 30 |
+
|
| 31 |
+
self.dbn = DBNDownBeatTrackingProcessor(
|
| 32 |
+
beats_per_bar=[3, 4],
|
| 33 |
+
min_bpm=55.0,
|
| 34 |
+
max_bpm=215.0,
|
| 35 |
+
fps=self.fps,
|
| 36 |
+
transition_lambda=100,
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
def __call__(
|
| 40 |
+
self,
|
| 41 |
+
beat: torch.Tensor,
|
| 42 |
+
downbeat: torch.Tensor,
|
| 43 |
+
padding_mask: torch.Tensor | None = None,
|
| 44 |
+
) -> tuple[np.ndarray, np.ndarray]:
|
| 45 |
+
"""
|
| 46 |
+
Apply postprocessing to the input beat and downbeat tensors. Works with batched and unbatched inputs.
|
| 47 |
+
The output is a list of times in seconds, or a list of lists of times in seconds, if the input is batched.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
beat (torch.Tensor): The input beat tensor.
|
| 51 |
+
downbeat (torch.Tensor): The input downbeat tensor.
|
| 52 |
+
padding_mask (torch.Tensor, optional): The padding mask tensor. Defaults to None.
|
| 53 |
+
|
| 54 |
+
Returns:
|
| 55 |
+
torch.Tensor: The postprocessed beat tensor.
|
| 56 |
+
torch.Tensor: The postprocessed downbeat tensor.
|
| 57 |
+
"""
|
| 58 |
+
batched = False if beat.ndim == 1 else True
|
| 59 |
+
if padding_mask is None:
|
| 60 |
+
padding_mask = torch.ones_like(beat, dtype=torch.bool)
|
| 61 |
+
|
| 62 |
+
# if beat and downbeat are 1D tensors, add a batch dimension
|
| 63 |
+
if not batched:
|
| 64 |
+
beat = beat.unsqueeze(0)
|
| 65 |
+
downbeat = downbeat.unsqueeze(0)
|
| 66 |
+
padding_mask = padding_mask.unsqueeze(0)
|
| 67 |
+
|
| 68 |
+
if self.type == "minimal":
|
| 69 |
+
postp_beat, postp_downbeat = self.postp_minimal(
|
| 70 |
+
beat, downbeat, padding_mask
|
| 71 |
+
)
|
| 72 |
+
elif self.type == "dbn":
|
| 73 |
+
postp_beat, postp_downbeat = self.postp_dbn(beat, downbeat, padding_mask)
|
| 74 |
+
else:
|
| 75 |
+
raise ValueError("Invalid postprocessing type")
|
| 76 |
+
|
| 77 |
+
# remove the batch dimension if it was added
|
| 78 |
+
if not batched:
|
| 79 |
+
postp_beat = postp_beat[0]
|
| 80 |
+
postp_downbeat = postp_downbeat[0]
|
| 81 |
+
|
| 82 |
+
# update the model prediction dict
|
| 83 |
+
return postp_beat, postp_downbeat
|
| 84 |
+
|
| 85 |
+
def postp_minimal(self, beat, downbeat, padding_mask):
|
| 86 |
+
# concatenate beat and downbeat in the same tensor of shape (B, T, 2)
|
| 87 |
+
packed_pred = rearrange(
|
| 88 |
+
[beat, downbeat], "c b t -> b t c", b=beat.shape[0], t=beat.shape[1], c=2
|
| 89 |
+
)
|
| 90 |
+
# set padded elements to -1000 (= probability zero even in float64) so they don't influence the maxpool
|
| 91 |
+
pred_logits = packed_pred.masked_fill(~padding_mask.unsqueeze(-1), -1000)
|
| 92 |
+
# reshape to (2*B, T) to apply max pooling
|
| 93 |
+
pred_logits = rearrange(pred_logits, "b t c -> (c b) t")
|
| 94 |
+
# pick maxima within +/- 70ms
|
| 95 |
+
pred_peaks = pred_logits.masked_fill(
|
| 96 |
+
pred_logits != F.max_pool1d(pred_logits, 7, 1, 3), -1000
|
| 97 |
+
)
|
| 98 |
+
# keep maxima with over 0.5 probability (logit > 0)
|
| 99 |
+
pred_peaks = pred_peaks > 0
|
| 100 |
+
# rearrange back to two tensors of shape (B, T)
|
| 101 |
+
beat_peaks, downbeat_peaks = rearrange(
|
| 102 |
+
pred_peaks, "(c b) t -> c b t", b=beat.shape[0], t=beat.shape[1], c=2
|
| 103 |
+
)
|
| 104 |
+
# run the piecewise operations
|
| 105 |
+
with ThreadPoolExecutor() as executor:
|
| 106 |
+
postp_beat, postp_downbeat = zip(
|
| 107 |
+
*executor.map(
|
| 108 |
+
self._postp_minimal_item, beat_peaks, downbeat_peaks, padding_mask
|
| 109 |
+
)
|
| 110 |
+
)
|
| 111 |
+
return postp_beat, postp_downbeat
|
| 112 |
+
|
| 113 |
+
def _postp_minimal_item(self, padded_beat_peaks, padded_downbeat_peaks, mask):
|
| 114 |
+
"""Function to compute the operations that must be computed piece by piece, and cannot be done in batch."""
|
| 115 |
+
# unpad the predictions by truncating the padding positions
|
| 116 |
+
beat_peaks = padded_beat_peaks[mask]
|
| 117 |
+
downbeat_peaks = padded_downbeat_peaks[mask]
|
| 118 |
+
# pass from a boolean array to a list of times in frames.
|
| 119 |
+
beat_frame = torch.nonzero(beat_peaks).cpu().numpy()[:, 0]
|
| 120 |
+
downbeat_frame = torch.nonzero(downbeat_peaks).cpu().numpy()[:, 0]
|
| 121 |
+
# remove adjacent peaks
|
| 122 |
+
beat_frame = deduplicate_peaks(beat_frame, width=1)
|
| 123 |
+
downbeat_frame = deduplicate_peaks(downbeat_frame, width=1)
|
| 124 |
+
# convert from frame to seconds
|
| 125 |
+
beat_time = beat_frame / self.fps
|
| 126 |
+
downbeat_time = downbeat_frame / self.fps
|
| 127 |
+
# move the downbeat to the nearest beat
|
| 128 |
+
if (
|
| 129 |
+
len(beat_time) > 0
|
| 130 |
+
): # skip if there are no beats, like in the first training steps
|
| 131 |
+
for i, d_time in enumerate(downbeat_time):
|
| 132 |
+
beat_idx = np.argmin(np.abs(beat_time - d_time))
|
| 133 |
+
downbeat_time[i] = beat_time[beat_idx]
|
| 134 |
+
# remove duplicate downbeat times (if some db were moved to the same position)
|
| 135 |
+
downbeat_time = np.unique(downbeat_time)
|
| 136 |
+
return beat_time, downbeat_time
|
| 137 |
+
|
| 138 |
+
def postp_dbn(self, beat, downbeat, padding_mask):
|
| 139 |
+
beat_prob = beat.double().sigmoid()
|
| 140 |
+
downbeat_prob = downbeat.double().sigmoid()
|
| 141 |
+
# limit lower and upper bound, since 0 and 1 create problems in the DBN
|
| 142 |
+
epsilon = 1e-5
|
| 143 |
+
beat_prob = beat_prob * (1 - epsilon) + epsilon / 2
|
| 144 |
+
downbeat_prob = downbeat_prob * (1 - epsilon) + epsilon / 2
|
| 145 |
+
with ThreadPoolExecutor() as executor:
|
| 146 |
+
postp_beat, postp_downbeat = zip(
|
| 147 |
+
*executor.map(
|
| 148 |
+
self._postp_dbn_item, beat_prob, downbeat_prob, padding_mask
|
| 149 |
+
)
|
| 150 |
+
)
|
| 151 |
+
return postp_beat, postp_downbeat
|
| 152 |
+
|
| 153 |
+
def _postp_dbn_item(self, padded_beat_prob, padded_downbeat_prob, mask):
|
| 154 |
+
"""Function to compute the operations that must be computed piece by piece, and cannot be done in batch."""
|
| 155 |
+
# unpad the predictions by truncating the padding positions
|
| 156 |
+
beat_prob = padded_beat_prob[mask]
|
| 157 |
+
downbeat_prob = padded_downbeat_prob[mask]
|
| 158 |
+
# build an artificial multiclass prediction, as suggested by Böck et al.
|
| 159 |
+
# again we limit the lower bound to avoid problems with the DBN
|
| 160 |
+
epsilon = 1e-5
|
| 161 |
+
combined_act = np.vstack(
|
| 162 |
+
(
|
| 163 |
+
np.maximum(
|
| 164 |
+
beat_prob.cpu().numpy() - downbeat_prob.cpu().numpy(), epsilon / 2
|
| 165 |
+
),
|
| 166 |
+
downbeat_prob.cpu().numpy(),
|
| 167 |
+
)
|
| 168 |
+
).T
|
| 169 |
+
# run the DBN
|
| 170 |
+
dbn_out = self.dbn(combined_act)
|
| 171 |
+
postp_beat = dbn_out[:, 0]
|
| 172 |
+
postp_downbeat = dbn_out[dbn_out[:, 1] == 1][:, 0]
|
| 173 |
+
return postp_beat, postp_downbeat
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def deduplicate_peaks(peaks, width=1) -> np.ndarray:
|
| 177 |
+
"""
|
| 178 |
+
Replaces groups of adjacent peak frame indices that are each not more
|
| 179 |
+
than `width` frames apart by the average of the frame indices.
|
| 180 |
+
"""
|
| 181 |
+
result = []
|
| 182 |
+
peaks = map(int, peaks) # ensure we get ordinary Python int objects
|
| 183 |
+
try:
|
| 184 |
+
p = next(peaks)
|
| 185 |
+
except StopIteration:
|
| 186 |
+
return np.array(result)
|
| 187 |
+
c = 1
|
| 188 |
+
for p2 in peaks:
|
| 189 |
+
if p2 - p <= width:
|
| 190 |
+
c += 1
|
| 191 |
+
p += (p2 - p) / c # update mean
|
| 192 |
+
else:
|
| 193 |
+
result.append(p)
|
| 194 |
+
p = p2
|
| 195 |
+
c = 1
|
| 196 |
+
result.append(p)
|
| 197 |
+
return np.array(result)
|
beat_this/model/roformer.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Transformer with rotary position embedding, adapted from Phil Wang's repository
|
| 3 |
+
at https://github.com/lucidrains/BS-RoFormer (under MIT License).
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from einops import rearrange
|
| 9 |
+
from torch import nn
|
| 10 |
+
from torch.nn import Module, ModuleList
|
| 11 |
+
|
| 12 |
+
# helper functions
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def exists(val):
|
| 16 |
+
return val is not None
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
# norm
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class RMSNorm(Module):
|
| 23 |
+
def __init__(self, size, dim=-1):
|
| 24 |
+
super().__init__()
|
| 25 |
+
self.scale = size**0.5
|
| 26 |
+
if dim >= 0:
|
| 27 |
+
raise ValueError(f"dim must be negative, got {dim}")
|
| 28 |
+
self.gamma = nn.Parameter(torch.ones((size,) + (1,) * (abs(dim) - 1)))
|
| 29 |
+
self.dim = dim
|
| 30 |
+
|
| 31 |
+
def forward(self, x):
|
| 32 |
+
return F.normalize(x, dim=self.dim) * self.scale * self.gamma
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
# feedforward
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class FeedForward(Module):
|
| 39 |
+
def __init__(
|
| 40 |
+
self,
|
| 41 |
+
dim,
|
| 42 |
+
mult=4,
|
| 43 |
+
dropout=0.0,
|
| 44 |
+
dim_out=None,
|
| 45 |
+
):
|
| 46 |
+
super().__init__()
|
| 47 |
+
if dim_out is None:
|
| 48 |
+
dim_out = dim
|
| 49 |
+
dim_inner = int(dim * mult)
|
| 50 |
+
self.activation = nn.GELU()
|
| 51 |
+
self.net = nn.Sequential(
|
| 52 |
+
RMSNorm(dim),
|
| 53 |
+
nn.Linear(dim, dim_inner),
|
| 54 |
+
self.activation,
|
| 55 |
+
nn.Dropout(dropout),
|
| 56 |
+
nn.Linear(dim_inner, dim_out),
|
| 57 |
+
nn.Dropout(dropout),
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
def forward(self, x):
|
| 61 |
+
return self.net(x)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# attention
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
class Attend(nn.Module):
|
| 68 |
+
def __init__(self, dropout=0.0, scale=None):
|
| 69 |
+
super().__init__()
|
| 70 |
+
self.dropout = dropout
|
| 71 |
+
self.scale = scale
|
| 72 |
+
|
| 73 |
+
def forward(self, q, k, v):
|
| 74 |
+
if exists(self.scale):
|
| 75 |
+
default_scale = q.shape[-1] ** -0.5
|
| 76 |
+
q = q * (self.scale / default_scale)
|
| 77 |
+
|
| 78 |
+
return F.scaled_dot_product_attention(
|
| 79 |
+
q, k, v, dropout_p=self.dropout if self.training else 0.0
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
class Attention(Module):
|
| 84 |
+
def __init__(
|
| 85 |
+
self,
|
| 86 |
+
dim,
|
| 87 |
+
heads=8,
|
| 88 |
+
dim_head=64,
|
| 89 |
+
dropout=0.0,
|
| 90 |
+
rotary_embed=None,
|
| 91 |
+
gating=True,
|
| 92 |
+
):
|
| 93 |
+
super().__init__()
|
| 94 |
+
self.heads = heads
|
| 95 |
+
self.scale = dim_head**-0.5
|
| 96 |
+
dim_inner = heads * dim_head
|
| 97 |
+
|
| 98 |
+
self.rotary_embed = rotary_embed
|
| 99 |
+
|
| 100 |
+
self.attend = Attend(dropout=dropout)
|
| 101 |
+
|
| 102 |
+
self.norm = RMSNorm(dim)
|
| 103 |
+
self.to_qkv = nn.Linear(dim, dim_inner * 3, bias=False)
|
| 104 |
+
|
| 105 |
+
if gating:
|
| 106 |
+
self.to_gates = nn.Linear(dim, heads)
|
| 107 |
+
else:
|
| 108 |
+
self.to_gates = None
|
| 109 |
+
|
| 110 |
+
self.to_out = nn.Sequential(
|
| 111 |
+
nn.Linear(dim_inner, dim, bias=False), nn.Dropout(dropout)
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
def forward(self, x):
|
| 115 |
+
x = self.norm(x)
|
| 116 |
+
|
| 117 |
+
q, k, v = rearrange(
|
| 118 |
+
self.to_qkv(x), "b n (qkv h d) -> qkv b h n d", qkv=3, h=self.heads
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
if exists(self.rotary_embed):
|
| 122 |
+
q = self.rotary_embed.rotate_queries_or_keys(q)
|
| 123 |
+
k = self.rotary_embed.rotate_queries_or_keys(k)
|
| 124 |
+
|
| 125 |
+
out = self.attend(q, k, v)
|
| 126 |
+
|
| 127 |
+
if exists(self.to_gates):
|
| 128 |
+
gates = self.to_gates(x)
|
| 129 |
+
out = out * rearrange(gates, "b n h -> b h n 1").sigmoid()
|
| 130 |
+
|
| 131 |
+
out = rearrange(out, "b h n d -> b n (h d)")
|
| 132 |
+
return self.to_out(out)
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
# Roformer
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class Transformer(Module):
|
| 139 |
+
def __init__(
|
| 140 |
+
self,
|
| 141 |
+
*,
|
| 142 |
+
dim,
|
| 143 |
+
depth,
|
| 144 |
+
dim_head=32,
|
| 145 |
+
heads=16,
|
| 146 |
+
attn_dropout=0.1,
|
| 147 |
+
ff_dropout=0.1,
|
| 148 |
+
ff_mult=4,
|
| 149 |
+
norm_output=True,
|
| 150 |
+
rotary_embed=None,
|
| 151 |
+
gating=True,
|
| 152 |
+
):
|
| 153 |
+
super().__init__()
|
| 154 |
+
self.layers = ModuleList([])
|
| 155 |
+
|
| 156 |
+
for _ in range(depth):
|
| 157 |
+
ff = FeedForward(dim=dim, mult=ff_mult, dropout=ff_dropout)
|
| 158 |
+
self.layers.append(
|
| 159 |
+
ModuleList(
|
| 160 |
+
[
|
| 161 |
+
Attention(
|
| 162 |
+
dim=dim,
|
| 163 |
+
dim_head=dim_head,
|
| 164 |
+
heads=heads,
|
| 165 |
+
dropout=attn_dropout,
|
| 166 |
+
rotary_embed=rotary_embed,
|
| 167 |
+
gating=gating,
|
| 168 |
+
),
|
| 169 |
+
ff,
|
| 170 |
+
]
|
| 171 |
+
)
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
self.norm = RMSNorm(dim) if norm_output else nn.Identity()
|
| 175 |
+
|
| 176 |
+
def forward(self, x):
|
| 177 |
+
for attn, ff in self.layers:
|
| 178 |
+
x = attn(x) + x
|
| 179 |
+
x = ff(x) + x
|
| 180 |
+
x = self.norm(x)
|
| 181 |
+
return x
|
beat_this/preprocessing.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import torchaudio
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
def load_audio(path, dtype="float64"):
|
| 7 |
+
try:
|
| 8 |
+
waveform, samplerate = torchaudio.load(path, channels_first=False)
|
| 9 |
+
waveform = np.asanyarray(waveform.squeeze().numpy(), dtype=dtype)
|
| 10 |
+
return waveform, samplerate
|
| 11 |
+
except Exception:
|
| 12 |
+
# in case torchaudio fails, try soundfile
|
| 13 |
+
try:
|
| 14 |
+
import soundfile as sf
|
| 15 |
+
|
| 16 |
+
return sf.read(path, dtype=dtype)
|
| 17 |
+
except Exception:
|
| 18 |
+
# some files are not readable by soundfile, try madmom
|
| 19 |
+
try:
|
| 20 |
+
import madmom
|
| 21 |
+
|
| 22 |
+
return madmom.io.load_audio_file(str(path), dtype=dtype)
|
| 23 |
+
except Exception:
|
| 24 |
+
raise RuntimeError(f'Could not load audio from "{path}".')
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class LogMelSpect(torch.nn.Module):
|
| 28 |
+
def __init__(
|
| 29 |
+
self,
|
| 30 |
+
sample_rate=22050,
|
| 31 |
+
n_fft=1024,
|
| 32 |
+
hop_length=441,
|
| 33 |
+
f_min=30,
|
| 34 |
+
f_max=11000,
|
| 35 |
+
n_mels=128,
|
| 36 |
+
mel_scale="slaney",
|
| 37 |
+
normalized="frame_length",
|
| 38 |
+
power=1,
|
| 39 |
+
log_multiplier=1000,
|
| 40 |
+
device="cpu",
|
| 41 |
+
):
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.spect_class = torchaudio.transforms.MelSpectrogram(
|
| 44 |
+
sample_rate=sample_rate,
|
| 45 |
+
n_fft=n_fft,
|
| 46 |
+
hop_length=hop_length,
|
| 47 |
+
f_min=f_min,
|
| 48 |
+
f_max=f_max,
|
| 49 |
+
n_mels=n_mels,
|
| 50 |
+
mel_scale=mel_scale,
|
| 51 |
+
normalized=normalized,
|
| 52 |
+
power=power,
|
| 53 |
+
).to(device)
|
| 54 |
+
self.log_multiplier = log_multiplier
|
| 55 |
+
|
| 56 |
+
def forward(self, x):
|
| 57 |
+
"""Input is a waveform as a monodimensional array of shape T,
|
| 58 |
+
output is a 2D log mel spectrogram of shape (F,128)."""
|
| 59 |
+
return torch.log1p(self.log_multiplier * self.spect_class(x).T)
|
beat_this/utils.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from itertools import chain
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def index_to_framewise(index, length):
|
| 8 |
+
"""Convert an index to a framewise sequence"""
|
| 9 |
+
sequence = np.zeros(length, dtype=bool)
|
| 10 |
+
sequence[index] = True
|
| 11 |
+
return sequence
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def filename_to_augmentation(filename):
|
| 15 |
+
"""Convert a filename to an augmentation factor."""
|
| 16 |
+
parts = Path(filename).stem.split("_")
|
| 17 |
+
augmentations = {}
|
| 18 |
+
for part in parts[1:]:
|
| 19 |
+
if part.startswith("ps"):
|
| 20 |
+
augmentations["shift"] = int(part[2:])
|
| 21 |
+
elif part.startswith("ts"):
|
| 22 |
+
augmentations["stretch"] = int(part[2:])
|
| 23 |
+
return augmentations
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def infer_beat_numbers(beats: np.ndarray, downbeats: np.ndarray) -> np.ndarray:
|
| 27 |
+
"""
|
| 28 |
+
From beat and downbeat times, infer a number for each beat such that each downbeat
|
| 29 |
+
is associated with a 1 and beats in between are counted upwards.
|
| 30 |
+
The function requires that all downbeats are also listed as beats.
|
| 31 |
+
|
| 32 |
+
Args:
|
| 33 |
+
beats (numpy.ndarray): Array of beat positions in seconds (including downbeats).
|
| 34 |
+
downbeats (numpy.ndarray): Array of downbeat positions in seconds.
|
| 35 |
+
|
| 36 |
+
Returns:
|
| 37 |
+
numbers (numpy.ndarray): Array of integer beat numbers.
|
| 38 |
+
"""
|
| 39 |
+
# check if all downbeats are beats
|
| 40 |
+
if not np.all(np.isin(downbeats, beats)):
|
| 41 |
+
raise ValueError("Not all downbeats are beats.")
|
| 42 |
+
|
| 43 |
+
# handle pickup measure, by considering the beat count of the first full measure
|
| 44 |
+
if len(downbeats) >= 2:
|
| 45 |
+
# find the number of beats between the first two downbeats
|
| 46 |
+
first_downbeat, second_downbeat = np.searchsorted(beats, downbeats[:2])
|
| 47 |
+
beats_in_first_measure = second_downbeat - first_downbeat
|
| 48 |
+
# find the number of beats before the first downbeat
|
| 49 |
+
pickup_beats = first_downbeat
|
| 50 |
+
# derive where to start counting
|
| 51 |
+
if pickup_beats < beats_in_first_measure:
|
| 52 |
+
start_counter = beats_in_first_measure - pickup_beats
|
| 53 |
+
else:
|
| 54 |
+
print(
|
| 55 |
+
"WARNING: There are more beats in the pickup measure than in the first measure. The beat count will start from 2 without trying to estimate the length of the pickup measure."
|
| 56 |
+
)
|
| 57 |
+
start_counter = 1
|
| 58 |
+
else:
|
| 59 |
+
print(
|
| 60 |
+
"WARNING: There are less than two downbeats in the predictions. Something may be wrong. The beat count will start from 2 without trying to estimate the length of the pickup measure."
|
| 61 |
+
)
|
| 62 |
+
start_counter = 1
|
| 63 |
+
|
| 64 |
+
# assemble the beat numbers
|
| 65 |
+
numbers = []
|
| 66 |
+
counter = start_counter
|
| 67 |
+
downbeats = chain(downbeats, [-1])
|
| 68 |
+
next_downbeat = next(downbeats)
|
| 69 |
+
for beat in beats:
|
| 70 |
+
if beat == next_downbeat:
|
| 71 |
+
counter = 1
|
| 72 |
+
next_downbeat = next(downbeats)
|
| 73 |
+
else:
|
| 74 |
+
counter += 1
|
| 75 |
+
numbers.append(counter)
|
| 76 |
+
return np.asarray(numbers)
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def save_beat_tsv(beats: np.ndarray, downbeats: np.ndarray, outpath: str) -> None:
|
| 80 |
+
"""
|
| 81 |
+
Save beat information to a tab-separated file in the standard .beats format:
|
| 82 |
+
each line has a time in seconds, a tab, and a beat number (1 = downbeat).
|
| 83 |
+
The function requires that all downbeats are also listed as beats.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
beats (numpy.ndarray): Array of beat positions in seconds (including downbeats).
|
| 87 |
+
downbeats (numpy.ndarray): Array of downbeat positions in seconds.
|
| 88 |
+
outpath (str): Path to the output TSV file.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
None
|
| 92 |
+
"""
|
| 93 |
+
# infer beat numbers
|
| 94 |
+
numbers = infer_beat_numbers(beats, downbeats)
|
| 95 |
+
|
| 96 |
+
# write the beat file
|
| 97 |
+
Path(outpath).parent.mkdir(parents=True, exist_ok=True)
|
| 98 |
+
try:
|
| 99 |
+
with open(outpath, "w") as f:
|
| 100 |
+
f.writelines(f"{beat}\t{number}\n" for beat, number in zip(beats, numbers))
|
| 101 |
+
except KeyboardInterrupt:
|
| 102 |
+
outpath.unlink() # avoid half-written files
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def replace_state_dict_key(state_dict: dict, old: str, new: str):
|
| 106 |
+
"""Replaces `old` in all keys of `state_dict` with `new`."""
|
| 107 |
+
keys = list(state_dict.keys()) # take snapshot of the keys
|
| 108 |
+
for key in keys:
|
| 109 |
+
if old in key:
|
| 110 |
+
state_dict[key.replace(old, new)] = state_dict.pop(key)
|
| 111 |
+
return state_dict
|
model.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "loopgen",
|
| 3 |
+
"package_dir": "src",
|
| 4 |
+
"entry_point": ["src.loopgen.LoopGen", "src.audiocraft.models.musicgen.MusicGen"],
|
| 5 |
+
"notes": "Multi-package model. (1) src/loopgen.py + src/lm_loopgen.py are LoopGen's own MAGNeT wrapper, copied from loopgen-repo/src/. (2) src/audiocraft/ is audiocraft v1.3.0 (loopgen-repo's own pin) vendored from source rather than pip-installed, because PyPI audiocraft==1.3.0 hard-pins torch==2.1.0/av==11.0.0/xformers<0.0.23, which conflicts with the torch 2.8.0 ZeroGPU needs -- vendoring sidesteps that pin entirely (same pattern already used by teamup-tech/instruct-MusicGen). All internal absolute 'from audiocraft.X' imports were mechanically rewritten to 'from src.audiocraft.X'; relative imports untouched. models/__init__.py and data/__init__.py were trimmed to drop AudioGen/MultiBandDiffusion/DiffusionUnet/DAC and training-dataset classes (unused, and multibanddiffusion pulls in the training-only solvers package). loaders.py's three torch.load calls got weights_only=False (MAGNeT's checkpoint bundles an OmegaConf config, not just tensors). (3) beat_this/ is vendored separately from ~/Documents/GitHub/beat-this (NOT loopgen's own git+ pin of CPJKU/beat_this) -- same upstream model, already proven on torch 2.11.0 via teamup-tech's own beat-this Space. See SOURCES.md.",
|
| 6 |
+
"additional_vendored_packages": [
|
| 7 |
+
{
|
| 8 |
+
"package_dir": "beat_this",
|
| 9 |
+
"entry_point": "beat_this.inference.File2Beats",
|
| 10 |
+
"vendored_from": "~/Documents/GitHub/beat-this/beat_this/ (local clone, not loopgen-repo)"
|
| 11 |
+
}
|
| 12 |
+
],
|
| 13 |
+
"checkpoints": {
|
| 14 |
+
"musicgen-medium": "auto-downloaded from facebook/musicgen-medium via audiocraft's MusicGen.get_pretrained() -- no manual hosting",
|
| 15 |
+
"magnet-medium-30secs": "auto-downloaded from facebook/magnet-medium-30secs via audiocraft's LoopGen.get_pretrained() -- no manual hosting",
|
| 16 |
+
"beat_this final0.ckpt": {"repo": "teamup-tech/beat-this", "repo_type": "space", "filename": "final0.ckpt", "size_mb": 81, "note": "reused from the already-deployed teamup-tech/beat-this Space via hf_hub_download(repo_type=\"space\"), not re-uploaded"}
|
| 17 |
+
}
|
| 18 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
git+https://github.com/TEAMuP-dev/pyharp.git@develop
|
| 2 |
+
# model-specific deps below:
|
| 3 |
+
torch==2.8.0
|
| 4 |
+
torchaudio==2.8.0
|
| 5 |
+
av>=12,<13
|
| 6 |
+
xformers==0.0.32.post2
|
| 7 |
+
flashy
|
| 8 |
+
julius
|
| 9 |
+
numpy
|
| 10 |
+
tqdm
|
| 11 |
+
omegaconf
|
| 12 |
+
einops
|
| 13 |
+
num2words
|
| 14 |
+
transformers
|
| 15 |
+
sentencepiece
|
| 16 |
+
protobuf
|
| 17 |
+
spacy
|
| 18 |
+
huggingface_hub
|
| 19 |
+
soundfile
|
| 20 |
+
librosa
|
| 21 |
+
soxr
|
| 22 |
+
rotary-embedding-torch
|
src/__init__.py
ADDED
|
File without changes
|
src/audiocraft/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""
|
| 7 |
+
AudioCraft is a general framework for training audio generative models.
|
| 8 |
+
At the moment we provide the training code for:
|
| 9 |
+
|
| 10 |
+
- [MusicGen](https://arxiv.org/abs/2306.05284), a state-of-the-art
|
| 11 |
+
text-to-music and melody+text autoregressive generative model.
|
| 12 |
+
For the solver, see `audiocraft.solvers.musicgen.MusicGenSolver`, and for the model,
|
| 13 |
+
`audiocraft.models.musicgen.MusicGen`.
|
| 14 |
+
- [AudioGen](https://arxiv.org/abs/2209.15352), a state-of-the-art
|
| 15 |
+
text-to-general-audio generative model.
|
| 16 |
+
- [EnCodec](https://arxiv.org/abs/2210.13438), efficient and high fidelity
|
| 17 |
+
neural audio codec which provides an excellent tokenizer for autoregressive language models.
|
| 18 |
+
See `audiocraft.solvers.compression.CompressionSolver`, and `audiocraft.models.encodec.EncodecModel`.
|
| 19 |
+
- [MultiBandDiffusion](TODO), alternative diffusion-based decoder compatible with EnCodec that
|
| 20 |
+
improves the perceived quality and reduces the artifacts coming from adversarial decoders.
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
# flake8: noqa
|
| 24 |
+
from . import data, modules, models
|
| 25 |
+
|
| 26 |
+
__version__ = '1.3.0'
|
src/audiocraft/data/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Audio loading and writing support. Datasets for raw audio
|
| 7 |
+
or also including some metadata."""
|
| 8 |
+
|
| 9 |
+
# flake8: noqa
|
| 10 |
+
# dropped training-dataset classes (audio_dataset, info_audio_dataset, music_dataset,
|
| 11 |
+
# sound_dataset) to avoid unnecessary dependencies
|
| 12 |
+
from . import audio
|
src/audiocraft/data/audio.py
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Audio IO methods are defined in this module (info, read, write),
|
| 9 |
+
We rely on av library for faster read when possible, otherwise on torchaudio.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import logging
|
| 15 |
+
import typing as tp
|
| 16 |
+
|
| 17 |
+
import numpy as np
|
| 18 |
+
import soundfile
|
| 19 |
+
import torch
|
| 20 |
+
from torch.nn import functional as F
|
| 21 |
+
|
| 22 |
+
import av
|
| 23 |
+
import subprocess as sp
|
| 24 |
+
|
| 25 |
+
from .audio_utils import f32_pcm, normalize_audio
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
_av_initialized = False
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _init_av():
|
| 32 |
+
global _av_initialized
|
| 33 |
+
if _av_initialized:
|
| 34 |
+
return
|
| 35 |
+
logger = logging.getLogger('libav.mp3')
|
| 36 |
+
logger.setLevel(logging.ERROR)
|
| 37 |
+
_av_initialized = True
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass(frozen=True)
|
| 41 |
+
class AudioFileInfo:
|
| 42 |
+
sample_rate: int
|
| 43 |
+
duration: float
|
| 44 |
+
channels: int
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _av_info(filepath: tp.Union[str, Path]) -> AudioFileInfo:
|
| 48 |
+
_init_av()
|
| 49 |
+
with av.open(str(filepath)) as af:
|
| 50 |
+
stream = af.streams.audio[0]
|
| 51 |
+
sample_rate = stream.codec_context.sample_rate
|
| 52 |
+
duration = float(stream.duration * stream.time_base)
|
| 53 |
+
channels = stream.channels
|
| 54 |
+
return AudioFileInfo(sample_rate, duration, channels)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def _soundfile_info(filepath: tp.Union[str, Path]) -> AudioFileInfo:
|
| 58 |
+
info = soundfile.info(filepath)
|
| 59 |
+
return AudioFileInfo(info.samplerate, info.duration, info.channels)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def audio_info(filepath: tp.Union[str, Path]) -> AudioFileInfo:
|
| 63 |
+
# torchaudio no longer returns useful duration informations for some formats like mp3s.
|
| 64 |
+
filepath = Path(filepath)
|
| 65 |
+
if filepath.suffix in ['.flac', '.ogg']: # TODO: Validate .ogg can be safely read with av_info
|
| 66 |
+
# ffmpeg has some weird issue with flac.
|
| 67 |
+
return _soundfile_info(filepath)
|
| 68 |
+
else:
|
| 69 |
+
return _av_info(filepath)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def _av_read(filepath: tp.Union[str, Path], seek_time: float = 0, duration: float = -1.) -> tp.Tuple[torch.Tensor, int]:
|
| 73 |
+
"""FFMPEG-based audio file reading using PyAV bindings.
|
| 74 |
+
Soundfile cannot read mp3 and av_read is more efficient than torchaudio.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
filepath (str or Path): Path to audio file to read.
|
| 78 |
+
seek_time (float): Time at which to start reading in the file.
|
| 79 |
+
duration (float): Duration to read from the file. If set to -1, the whole file is read.
|
| 80 |
+
Returns:
|
| 81 |
+
tuple of torch.Tensor, int: Tuple containing audio data and sample rate
|
| 82 |
+
"""
|
| 83 |
+
_init_av()
|
| 84 |
+
with av.open(str(filepath)) as af:
|
| 85 |
+
stream = af.streams.audio[0]
|
| 86 |
+
sr = stream.codec_context.sample_rate
|
| 87 |
+
num_frames = int(sr * duration) if duration >= 0 else -1
|
| 88 |
+
frame_offset = int(sr * seek_time)
|
| 89 |
+
# we need a small negative offset otherwise we get some edge artifact
|
| 90 |
+
# from the mp3 decoder.
|
| 91 |
+
af.seek(int(max(0, (seek_time - 0.1)) / stream.time_base), stream=stream)
|
| 92 |
+
frames = []
|
| 93 |
+
length = 0
|
| 94 |
+
for frame in af.decode(streams=stream.index):
|
| 95 |
+
current_offset = int(frame.rate * frame.pts * frame.time_base)
|
| 96 |
+
strip = max(0, frame_offset - current_offset)
|
| 97 |
+
buf = torch.from_numpy(frame.to_ndarray())
|
| 98 |
+
if buf.shape[0] != stream.channels:
|
| 99 |
+
buf = buf.view(-1, stream.channels).t()
|
| 100 |
+
buf = buf[:, strip:]
|
| 101 |
+
frames.append(buf)
|
| 102 |
+
length += buf.shape[1]
|
| 103 |
+
if num_frames > 0 and length >= num_frames:
|
| 104 |
+
break
|
| 105 |
+
assert frames
|
| 106 |
+
# If the above assert fails, it is likely because we seeked past the end of file point,
|
| 107 |
+
# in which case ffmpeg returns a single frame with only zeros, and a weird timestamp.
|
| 108 |
+
# This will need proper debugging, in due time.
|
| 109 |
+
wav = torch.cat(frames, dim=1)
|
| 110 |
+
assert wav.shape[0] == stream.channels
|
| 111 |
+
if num_frames > 0:
|
| 112 |
+
wav = wav[:, :num_frames]
|
| 113 |
+
return f32_pcm(wav), sr
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def audio_read(filepath: tp.Union[str, Path], seek_time: float = 0.,
|
| 117 |
+
duration: float = -1., pad: bool = False) -> tp.Tuple[torch.Tensor, int]:
|
| 118 |
+
"""Read audio by picking the most appropriate backend tool based on the audio format.
|
| 119 |
+
|
| 120 |
+
Args:
|
| 121 |
+
filepath (str or Path): Path to audio file to read.
|
| 122 |
+
seek_time (float): Time at which to start reading in the file.
|
| 123 |
+
duration (float): Duration to read from the file. If set to -1, the whole file is read.
|
| 124 |
+
pad (bool): Pad output audio if not reaching expected duration.
|
| 125 |
+
Returns:
|
| 126 |
+
tuple of torch.Tensor, int: Tuple containing audio data and sample rate.
|
| 127 |
+
"""
|
| 128 |
+
fp = Path(filepath)
|
| 129 |
+
if fp.suffix in ['.flac', '.ogg']: # TODO: check if we can safely use av_read for .ogg
|
| 130 |
+
# There is some bug with ffmpeg and reading flac
|
| 131 |
+
info = _soundfile_info(filepath)
|
| 132 |
+
frames = -1 if duration <= 0 else int(duration * info.sample_rate)
|
| 133 |
+
frame_offset = int(seek_time * info.sample_rate)
|
| 134 |
+
wav, sr = soundfile.read(filepath, start=frame_offset, frames=frames, dtype=np.float32)
|
| 135 |
+
assert info.sample_rate == sr, f"Mismatch of sample rates {info.sample_rate} {sr}"
|
| 136 |
+
wav = torch.from_numpy(wav).t().contiguous()
|
| 137 |
+
if len(wav.shape) == 1:
|
| 138 |
+
wav = torch.unsqueeze(wav, 0)
|
| 139 |
+
else:
|
| 140 |
+
wav, sr = _av_read(filepath, seek_time, duration)
|
| 141 |
+
if pad and duration > 0:
|
| 142 |
+
expected_frames = int(duration * sr)
|
| 143 |
+
wav = F.pad(wav, (0, expected_frames - wav.shape[-1]))
|
| 144 |
+
return wav, sr
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _piping_to_ffmpeg(out_path: tp.Union[str, Path], wav: torch.Tensor, sample_rate: int, flags: tp.List[str]):
|
| 148 |
+
# ffmpeg is always installed and torchaudio is a bit unstable lately, so let's bypass it entirely.
|
| 149 |
+
assert wav.dim() == 2, wav.shape
|
| 150 |
+
command = [
|
| 151 |
+
'ffmpeg',
|
| 152 |
+
'-loglevel', 'error',
|
| 153 |
+
'-y', '-f', 'f32le', '-ar', str(sample_rate), '-ac', str(wav.shape[0]),
|
| 154 |
+
'-i', '-'] + flags + [str(out_path)]
|
| 155 |
+
input_ = f32_pcm(wav).t().detach().cpu().numpy().tobytes()
|
| 156 |
+
sp.run(command, input=input_, check=True)
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
def audio_write(stem_name: tp.Union[str, Path],
|
| 160 |
+
wav: torch.Tensor, sample_rate: int,
|
| 161 |
+
format: str = 'wav', mp3_rate: int = 320, ogg_rate: tp.Optional[int] = None,
|
| 162 |
+
normalize: bool = True, strategy: str = 'peak', peak_clip_headroom_db: float = 1,
|
| 163 |
+
rms_headroom_db: float = 18, loudness_headroom_db: float = 14,
|
| 164 |
+
loudness_compressor: bool = False,
|
| 165 |
+
log_clipping: bool = True, make_parent_dir: bool = True,
|
| 166 |
+
add_suffix: bool = True) -> Path:
|
| 167 |
+
"""Convenience function for saving audio to disk. Returns the filename the audio was written to.
|
| 168 |
+
|
| 169 |
+
Args:
|
| 170 |
+
stem_name (str or Path): Filename without extension which will be added automatically.
|
| 171 |
+
wav (torch.Tensor): Audio data to save.
|
| 172 |
+
sample_rate (int): Sample rate of audio data.
|
| 173 |
+
format (str): Either "wav", "mp3", "ogg", or "flac".
|
| 174 |
+
mp3_rate (int): kbps when using mp3s.
|
| 175 |
+
ogg_rate (int): kbps when using ogg/vorbis. If not provided, let ffmpeg decide for itself.
|
| 176 |
+
normalize (bool): if `True` (default), normalizes according to the prescribed
|
| 177 |
+
strategy (see after). If `False`, the strategy is only used in case clipping
|
| 178 |
+
would happen.
|
| 179 |
+
strategy (str): Can be either 'clip', 'peak', or 'rms'. Default is 'peak',
|
| 180 |
+
i.e. audio is normalized by its largest value. RMS normalizes by root-mean-square
|
| 181 |
+
with extra headroom to avoid clipping. 'clip' just clips.
|
| 182 |
+
peak_clip_headroom_db (float): Headroom in dB when doing 'peak' or 'clip' strategy.
|
| 183 |
+
rms_headroom_db (float): Headroom in dB when doing 'rms' strategy. This must be much larger
|
| 184 |
+
than the `peak_clip` one to avoid further clipping.
|
| 185 |
+
loudness_headroom_db (float): Target loudness for loudness normalization.
|
| 186 |
+
loudness_compressor (bool): Uses tanh for soft clipping when strategy is 'loudness'.
|
| 187 |
+
when strategy is 'loudness' log_clipping (bool): If True, basic logging on stderr when clipping still
|
| 188 |
+
occurs despite strategy (only for 'rms').
|
| 189 |
+
make_parent_dir (bool): Make parent directory if it doesn't exist.
|
| 190 |
+
Returns:
|
| 191 |
+
Path: Path of the saved audio.
|
| 192 |
+
"""
|
| 193 |
+
assert wav.dtype.is_floating_point, "wav is not floating point"
|
| 194 |
+
if wav.dim() == 1:
|
| 195 |
+
wav = wav[None]
|
| 196 |
+
elif wav.dim() > 2:
|
| 197 |
+
raise ValueError("Input wav should be at most 2 dimension.")
|
| 198 |
+
assert wav.isfinite().all()
|
| 199 |
+
wav = normalize_audio(wav, normalize, strategy, peak_clip_headroom_db,
|
| 200 |
+
rms_headroom_db, loudness_headroom_db, loudness_compressor,
|
| 201 |
+
log_clipping=log_clipping, sample_rate=sample_rate,
|
| 202 |
+
stem_name=str(stem_name))
|
| 203 |
+
if format == 'mp3':
|
| 204 |
+
suffix = '.mp3'
|
| 205 |
+
flags = ['-f', 'mp3', '-c:a', 'libmp3lame', '-b:a', f'{mp3_rate}k']
|
| 206 |
+
elif format == 'wav':
|
| 207 |
+
suffix = '.wav'
|
| 208 |
+
flags = ['-f', 'wav', '-c:a', 'pcm_s16le']
|
| 209 |
+
elif format == 'ogg':
|
| 210 |
+
suffix = '.ogg'
|
| 211 |
+
flags = ['-f', 'ogg', '-c:a', 'libvorbis']
|
| 212 |
+
if ogg_rate is not None:
|
| 213 |
+
flags += ['-b:a', f'{ogg_rate}k']
|
| 214 |
+
elif format == 'flac':
|
| 215 |
+
suffix = '.flac'
|
| 216 |
+
flags = ['-f', 'flac']
|
| 217 |
+
else:
|
| 218 |
+
raise RuntimeError(f"Invalid format {format}. Only wav or mp3 are supported.")
|
| 219 |
+
if not add_suffix:
|
| 220 |
+
suffix = ''
|
| 221 |
+
path = Path(str(stem_name) + suffix)
|
| 222 |
+
if make_parent_dir:
|
| 223 |
+
path.parent.mkdir(exist_ok=True, parents=True)
|
| 224 |
+
try:
|
| 225 |
+
_piping_to_ffmpeg(path, wav, sample_rate, flags)
|
| 226 |
+
except Exception:
|
| 227 |
+
if path.exists():
|
| 228 |
+
# we do not want to leave half written files around.
|
| 229 |
+
path.unlink()
|
| 230 |
+
raise
|
| 231 |
+
return path
|
src/audiocraft/data/audio_dataset.py
ADDED
|
@@ -0,0 +1,587 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""AudioDataset support. In order to handle a larger number of files
|
| 7 |
+
without having to scan again the folders, we precompute some metadata
|
| 8 |
+
(filename, sample rate, duration), and use that to efficiently sample audio segments.
|
| 9 |
+
"""
|
| 10 |
+
import argparse
|
| 11 |
+
import copy
|
| 12 |
+
from concurrent.futures import ThreadPoolExecutor, Future
|
| 13 |
+
from dataclasses import dataclass, fields
|
| 14 |
+
from contextlib import ExitStack
|
| 15 |
+
from functools import lru_cache
|
| 16 |
+
import gzip
|
| 17 |
+
import json
|
| 18 |
+
import logging
|
| 19 |
+
import os
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
import random
|
| 22 |
+
import sys
|
| 23 |
+
import typing as tp
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
import torch.nn.functional as F
|
| 27 |
+
|
| 28 |
+
from .audio import audio_read, audio_info
|
| 29 |
+
from .audio_utils import convert_audio
|
| 30 |
+
from .zip import PathInZip
|
| 31 |
+
|
| 32 |
+
try:
|
| 33 |
+
import dora
|
| 34 |
+
except ImportError:
|
| 35 |
+
dora = None # type: ignore
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
@dataclass(order=True)
|
| 39 |
+
class BaseInfo:
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def _dict2fields(cls, dictionary: dict):
|
| 43 |
+
return {
|
| 44 |
+
field.name: dictionary[field.name]
|
| 45 |
+
for field in fields(cls) if field.name in dictionary
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
@classmethod
|
| 49 |
+
def from_dict(cls, dictionary: dict):
|
| 50 |
+
_dictionary = cls._dict2fields(dictionary)
|
| 51 |
+
return cls(**_dictionary)
|
| 52 |
+
|
| 53 |
+
def to_dict(self):
|
| 54 |
+
return {
|
| 55 |
+
field.name: self.__getattribute__(field.name)
|
| 56 |
+
for field in fields(self)
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
@dataclass(order=True)
|
| 61 |
+
class AudioMeta(BaseInfo):
|
| 62 |
+
path: str
|
| 63 |
+
duration: float
|
| 64 |
+
sample_rate: int
|
| 65 |
+
amplitude: tp.Optional[float] = None
|
| 66 |
+
weight: tp.Optional[float] = None
|
| 67 |
+
# info_path is used to load additional information about the audio file that is stored in zip files.
|
| 68 |
+
info_path: tp.Optional[PathInZip] = None
|
| 69 |
+
|
| 70 |
+
@classmethod
|
| 71 |
+
def from_dict(cls, dictionary: dict):
|
| 72 |
+
base = cls._dict2fields(dictionary)
|
| 73 |
+
if 'info_path' in base and base['info_path'] is not None:
|
| 74 |
+
base['info_path'] = PathInZip(base['info_path'])
|
| 75 |
+
return cls(**base)
|
| 76 |
+
|
| 77 |
+
def to_dict(self):
|
| 78 |
+
d = super().to_dict()
|
| 79 |
+
if d['info_path'] is not None:
|
| 80 |
+
d['info_path'] = str(d['info_path'])
|
| 81 |
+
return d
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@dataclass(order=True)
|
| 85 |
+
class SegmentInfo(BaseInfo):
|
| 86 |
+
meta: AudioMeta
|
| 87 |
+
seek_time: float
|
| 88 |
+
# The following values are given once the audio is processed, e.g.
|
| 89 |
+
# at the target sample rate and target number of channels.
|
| 90 |
+
n_frames: int # actual number of frames without padding
|
| 91 |
+
total_frames: int # total number of frames, padding included
|
| 92 |
+
sample_rate: int # actual sample rate
|
| 93 |
+
channels: int # number of audio channels.
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
DEFAULT_EXTS = ['.wav', '.mp3', '.flac', '.ogg', '.m4a']
|
| 97 |
+
|
| 98 |
+
logger = logging.getLogger(__name__)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _get_audio_meta(file_path: str, minimal: bool = True) -> AudioMeta:
|
| 102 |
+
"""AudioMeta from a path to an audio file.
|
| 103 |
+
|
| 104 |
+
Args:
|
| 105 |
+
file_path (str): Resolved path of valid audio file.
|
| 106 |
+
minimal (bool): Whether to only load the minimal set of metadata (takes longer if not).
|
| 107 |
+
Returns:
|
| 108 |
+
AudioMeta: Audio file path and its metadata.
|
| 109 |
+
"""
|
| 110 |
+
info = audio_info(file_path)
|
| 111 |
+
amplitude: tp.Optional[float] = None
|
| 112 |
+
if not minimal:
|
| 113 |
+
wav, sr = audio_read(file_path)
|
| 114 |
+
amplitude = wav.abs().max().item()
|
| 115 |
+
return AudioMeta(file_path, info.duration, info.sample_rate, amplitude)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def _resolve_audio_meta(m: AudioMeta, fast: bool = True) -> AudioMeta:
|
| 119 |
+
"""If Dora is available as a dependency, try to resolve potential relative paths
|
| 120 |
+
in list of AudioMeta. This method is expected to be used when loading meta from file.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
m (AudioMeta): Audio meta to resolve.
|
| 124 |
+
fast (bool): If True, uses a really fast check for determining if a file
|
| 125 |
+
is already absolute or not. Only valid on Linux/Mac.
|
| 126 |
+
Returns:
|
| 127 |
+
AudioMeta: Audio meta with resolved path.
|
| 128 |
+
"""
|
| 129 |
+
def is_abs(m):
|
| 130 |
+
if fast:
|
| 131 |
+
return str(m)[0] == '/'
|
| 132 |
+
else:
|
| 133 |
+
os.path.isabs(str(m))
|
| 134 |
+
|
| 135 |
+
if not dora:
|
| 136 |
+
return m
|
| 137 |
+
|
| 138 |
+
if not is_abs(m.path):
|
| 139 |
+
m.path = dora.git_save.to_absolute_path(m.path)
|
| 140 |
+
if m.info_path is not None and not is_abs(m.info_path.zip_path):
|
| 141 |
+
m.info_path.zip_path = dora.git_save.to_absolute_path(m.path)
|
| 142 |
+
return m
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
def find_audio_files(path: tp.Union[Path, str],
|
| 146 |
+
exts: tp.List[str] = DEFAULT_EXTS,
|
| 147 |
+
resolve: bool = True,
|
| 148 |
+
minimal: bool = True,
|
| 149 |
+
progress: bool = False,
|
| 150 |
+
workers: int = 0) -> tp.List[AudioMeta]:
|
| 151 |
+
"""Build a list of AudioMeta from a given path,
|
| 152 |
+
collecting relevant audio files and fetching meta info.
|
| 153 |
+
|
| 154 |
+
Args:
|
| 155 |
+
path (str or Path): Path to folder containing audio files.
|
| 156 |
+
exts (list of str): List of file extensions to consider for audio files.
|
| 157 |
+
minimal (bool): Whether to only load the minimal set of metadata (takes longer if not).
|
| 158 |
+
progress (bool): Whether to log progress on audio files collection.
|
| 159 |
+
workers (int): number of parallel workers, if 0, use only the current thread.
|
| 160 |
+
Returns:
|
| 161 |
+
list of AudioMeta: List of audio file path and its metadata.
|
| 162 |
+
"""
|
| 163 |
+
audio_files = []
|
| 164 |
+
futures: tp.List[Future] = []
|
| 165 |
+
pool: tp.Optional[ThreadPoolExecutor] = None
|
| 166 |
+
with ExitStack() as stack:
|
| 167 |
+
if workers > 0:
|
| 168 |
+
pool = ThreadPoolExecutor(workers)
|
| 169 |
+
stack.enter_context(pool)
|
| 170 |
+
|
| 171 |
+
if progress:
|
| 172 |
+
print("Finding audio files...")
|
| 173 |
+
for root, folders, files in os.walk(path, followlinks=True):
|
| 174 |
+
for file in files:
|
| 175 |
+
full_path = Path(root) / file
|
| 176 |
+
if full_path.suffix.lower() in exts:
|
| 177 |
+
audio_files.append(full_path)
|
| 178 |
+
if pool is not None:
|
| 179 |
+
futures.append(pool.submit(_get_audio_meta, str(audio_files[-1]), minimal))
|
| 180 |
+
if progress:
|
| 181 |
+
print(format(len(audio_files), " 8d"), end='\r', file=sys.stderr)
|
| 182 |
+
|
| 183 |
+
if progress:
|
| 184 |
+
print("Getting audio metadata...")
|
| 185 |
+
meta: tp.List[AudioMeta] = []
|
| 186 |
+
for idx, file_path in enumerate(audio_files):
|
| 187 |
+
try:
|
| 188 |
+
if pool is None:
|
| 189 |
+
m = _get_audio_meta(str(file_path), minimal)
|
| 190 |
+
else:
|
| 191 |
+
m = futures[idx].result()
|
| 192 |
+
if resolve:
|
| 193 |
+
m = _resolve_audio_meta(m)
|
| 194 |
+
except Exception as err:
|
| 195 |
+
print("Error with", str(file_path), err, file=sys.stderr)
|
| 196 |
+
continue
|
| 197 |
+
meta.append(m)
|
| 198 |
+
if progress:
|
| 199 |
+
print(format((1 + idx) / len(audio_files), " 3.1%"), end='\r', file=sys.stderr)
|
| 200 |
+
meta.sort()
|
| 201 |
+
return meta
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def load_audio_meta(path: tp.Union[str, Path],
|
| 205 |
+
resolve: bool = True, fast: bool = True) -> tp.List[AudioMeta]:
|
| 206 |
+
"""Load list of AudioMeta from an optionally compressed json file.
|
| 207 |
+
|
| 208 |
+
Args:
|
| 209 |
+
path (str or Path): Path to JSON file.
|
| 210 |
+
resolve (bool): Whether to resolve the path from AudioMeta (default=True).
|
| 211 |
+
fast (bool): activates some tricks to make things faster.
|
| 212 |
+
Returns:
|
| 213 |
+
list of AudioMeta: List of audio file path and its total duration.
|
| 214 |
+
"""
|
| 215 |
+
open_fn = gzip.open if str(path).lower().endswith('.gz') else open
|
| 216 |
+
with open_fn(path, 'rb') as fp: # type: ignore
|
| 217 |
+
lines = fp.readlines()
|
| 218 |
+
meta = []
|
| 219 |
+
for line in lines:
|
| 220 |
+
d = json.loads(line)
|
| 221 |
+
m = AudioMeta.from_dict(d)
|
| 222 |
+
if resolve:
|
| 223 |
+
m = _resolve_audio_meta(m, fast=fast)
|
| 224 |
+
meta.append(m)
|
| 225 |
+
return meta
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def save_audio_meta(path: tp.Union[str, Path], meta: tp.List[AudioMeta]):
|
| 229 |
+
"""Save the audio metadata to the file pointer as json.
|
| 230 |
+
|
| 231 |
+
Args:
|
| 232 |
+
path (str or Path): Path to JSON file.
|
| 233 |
+
metadata (list of BaseAudioMeta): List of audio meta to save.
|
| 234 |
+
"""
|
| 235 |
+
Path(path).parent.mkdir(exist_ok=True, parents=True)
|
| 236 |
+
open_fn = gzip.open if str(path).lower().endswith('.gz') else open
|
| 237 |
+
with open_fn(path, 'wb') as fp: # type: ignore
|
| 238 |
+
for m in meta:
|
| 239 |
+
json_str = json.dumps(m.to_dict()) + '\n'
|
| 240 |
+
json_bytes = json_str.encode('utf-8')
|
| 241 |
+
fp.write(json_bytes)
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class AudioDataset:
|
| 245 |
+
"""Base audio dataset.
|
| 246 |
+
|
| 247 |
+
The dataset takes a list of AudioMeta and create a dataset composed of segments of audio
|
| 248 |
+
and potentially additional information, by creating random segments from the list of audio
|
| 249 |
+
files referenced in the metadata and applying minimal data pre-processing such as resampling,
|
| 250 |
+
mixing of channels, padding, etc.
|
| 251 |
+
|
| 252 |
+
If no segment_duration value is provided, the AudioDataset will return the full wav for each
|
| 253 |
+
audio file. Otherwise, it will randomly sample audio files and create a segment of the specified
|
| 254 |
+
duration, applying padding if required.
|
| 255 |
+
|
| 256 |
+
By default, only the torch Tensor corresponding to the waveform is returned. Setting return_info=True
|
| 257 |
+
allows to return a tuple containing the torch Tensor and additional metadata on the segment and the
|
| 258 |
+
original audio meta.
|
| 259 |
+
|
| 260 |
+
Note that you can call `start_epoch(epoch)` in order to get
|
| 261 |
+
a deterministic "randomization" for `shuffle=True`.
|
| 262 |
+
For a given epoch and dataset index, this will always return the same extract.
|
| 263 |
+
You can get back some diversity by setting the `shuffle_seed` param.
|
| 264 |
+
|
| 265 |
+
Args:
|
| 266 |
+
meta (list of AudioMeta): List of audio files metadata.
|
| 267 |
+
segment_duration (float, optional): Optional segment duration of audio to load.
|
| 268 |
+
If not specified, the dataset will load the full audio segment from the file.
|
| 269 |
+
shuffle (bool): Set to `True` to have the data reshuffled at every epoch.
|
| 270 |
+
sample_rate (int): Target sample rate of the loaded audio samples.
|
| 271 |
+
channels (int): Target number of channels of the loaded audio samples.
|
| 272 |
+
sample_on_duration (bool): Set to `True` to sample segments with probability
|
| 273 |
+
dependent on audio file duration. This is only used if `segment_duration` is provided.
|
| 274 |
+
sample_on_weight (bool): Set to `True` to sample segments using the `weight` entry of
|
| 275 |
+
`AudioMeta`. If `sample_on_duration` is also True, the actual weight will be the product
|
| 276 |
+
of the file duration and file weight. This is only used if `segment_duration` is provided.
|
| 277 |
+
min_segment_ratio (float): Minimum segment ratio to use when the audio file
|
| 278 |
+
is shorter than the desired segment.
|
| 279 |
+
max_read_retry (int): Maximum number of retries to sample an audio segment from the dataset.
|
| 280 |
+
return_info (bool): Whether to return the wav only or return wav along with segment info and metadata.
|
| 281 |
+
min_audio_duration (float, optional): Minimum audio file duration, in seconds, if provided
|
| 282 |
+
audio shorter than this will be filtered out.
|
| 283 |
+
max_audio_duration (float, optional): Maximal audio file duration in seconds, if provided
|
| 284 |
+
audio longer than this will be filtered out.
|
| 285 |
+
shuffle_seed (int): can be used to further randomize
|
| 286 |
+
load_wav (bool): if False, skip loading the wav but returns a tensor of 0
|
| 287 |
+
with the expected segment_duration (which must be provided if load_wav is False).
|
| 288 |
+
permutation_on_files (bool): only if `sample_on_weight` and `sample_on_duration`
|
| 289 |
+
are False. Will ensure a permutation on files when going through the dataset.
|
| 290 |
+
In that case the epoch number must be provided in order for the model
|
| 291 |
+
to continue the permutation across epochs. In that case, it is assumed
|
| 292 |
+
that `num_samples = total_batch_size * num_updates_per_epoch`, with
|
| 293 |
+
`total_batch_size` the overall batch size accounting for all gpus.
|
| 294 |
+
"""
|
| 295 |
+
def __init__(self,
|
| 296 |
+
meta: tp.List[AudioMeta],
|
| 297 |
+
segment_duration: tp.Optional[float] = None,
|
| 298 |
+
shuffle: bool = True,
|
| 299 |
+
num_samples: int = 10_000,
|
| 300 |
+
sample_rate: int = 48_000,
|
| 301 |
+
channels: int = 2,
|
| 302 |
+
pad: bool = True,
|
| 303 |
+
sample_on_duration: bool = True,
|
| 304 |
+
sample_on_weight: bool = True,
|
| 305 |
+
min_segment_ratio: float = 0.5,
|
| 306 |
+
max_read_retry: int = 10,
|
| 307 |
+
return_info: bool = False,
|
| 308 |
+
min_audio_duration: tp.Optional[float] = None,
|
| 309 |
+
max_audio_duration: tp.Optional[float] = None,
|
| 310 |
+
shuffle_seed: int = 0,
|
| 311 |
+
load_wav: bool = True,
|
| 312 |
+
permutation_on_files: bool = False,
|
| 313 |
+
):
|
| 314 |
+
assert len(meta) > 0, "No audio meta provided to AudioDataset. Please check loading of audio meta."
|
| 315 |
+
assert segment_duration is None or segment_duration > 0
|
| 316 |
+
assert segment_duration is None or min_segment_ratio >= 0
|
| 317 |
+
self.segment_duration = segment_duration
|
| 318 |
+
self.min_segment_ratio = min_segment_ratio
|
| 319 |
+
self.max_audio_duration = max_audio_duration
|
| 320 |
+
self.min_audio_duration = min_audio_duration
|
| 321 |
+
if self.min_audio_duration is not None and self.max_audio_duration is not None:
|
| 322 |
+
assert self.min_audio_duration <= self.max_audio_duration
|
| 323 |
+
self.meta: tp.List[AudioMeta] = self._filter_duration(meta)
|
| 324 |
+
assert len(self.meta) # Fail fast if all data has been filtered.
|
| 325 |
+
self.total_duration = sum(d.duration for d in self.meta)
|
| 326 |
+
|
| 327 |
+
if segment_duration is None:
|
| 328 |
+
num_samples = len(self.meta)
|
| 329 |
+
self.num_samples = num_samples
|
| 330 |
+
self.shuffle = shuffle
|
| 331 |
+
self.sample_rate = sample_rate
|
| 332 |
+
self.channels = channels
|
| 333 |
+
self.pad = pad
|
| 334 |
+
self.sample_on_weight = sample_on_weight
|
| 335 |
+
self.sample_on_duration = sample_on_duration
|
| 336 |
+
self.sampling_probabilities = self._get_sampling_probabilities()
|
| 337 |
+
self.max_read_retry = max_read_retry
|
| 338 |
+
self.return_info = return_info
|
| 339 |
+
self.shuffle_seed = shuffle_seed
|
| 340 |
+
self.current_epoch: tp.Optional[int] = None
|
| 341 |
+
self.load_wav = load_wav
|
| 342 |
+
if not load_wav:
|
| 343 |
+
assert segment_duration is not None
|
| 344 |
+
self.permutation_on_files = permutation_on_files
|
| 345 |
+
if permutation_on_files:
|
| 346 |
+
assert not self.sample_on_duration
|
| 347 |
+
assert not self.sample_on_weight
|
| 348 |
+
assert self.shuffle
|
| 349 |
+
|
| 350 |
+
def start_epoch(self, epoch: int):
|
| 351 |
+
self.current_epoch = epoch
|
| 352 |
+
|
| 353 |
+
def __len__(self):
|
| 354 |
+
return self.num_samples
|
| 355 |
+
|
| 356 |
+
def _get_sampling_probabilities(self, normalized: bool = True):
|
| 357 |
+
"""Return the sampling probabilities for each file inside `self.meta`."""
|
| 358 |
+
scores: tp.List[float] = []
|
| 359 |
+
for file_meta in self.meta:
|
| 360 |
+
score = 1.
|
| 361 |
+
if self.sample_on_weight and file_meta.weight is not None:
|
| 362 |
+
score *= file_meta.weight
|
| 363 |
+
if self.sample_on_duration:
|
| 364 |
+
score *= file_meta.duration
|
| 365 |
+
scores.append(score)
|
| 366 |
+
probabilities = torch.tensor(scores)
|
| 367 |
+
if normalized:
|
| 368 |
+
probabilities /= probabilities.sum()
|
| 369 |
+
return probabilities
|
| 370 |
+
|
| 371 |
+
@staticmethod
|
| 372 |
+
@lru_cache(16)
|
| 373 |
+
def _get_file_permutation(num_files: int, permutation_index: int, base_seed: int):
|
| 374 |
+
# Used to keep the most recent files permutation in memory implicitely.
|
| 375 |
+
# will work unless someone is using a lot of Datasets in parallel.
|
| 376 |
+
rng = torch.Generator()
|
| 377 |
+
rng.manual_seed(base_seed + permutation_index)
|
| 378 |
+
return torch.randperm(num_files, generator=rng)
|
| 379 |
+
|
| 380 |
+
def sample_file(self, index: int, rng: torch.Generator) -> AudioMeta:
|
| 381 |
+
"""Sample a given file from `self.meta`. Can be overridden in subclasses.
|
| 382 |
+
This is only called if `segment_duration` is not None.
|
| 383 |
+
|
| 384 |
+
You must use the provided random number generator `rng` for reproducibility.
|
| 385 |
+
You can further make use of the index accessed.
|
| 386 |
+
"""
|
| 387 |
+
if self.permutation_on_files:
|
| 388 |
+
assert self.current_epoch is not None
|
| 389 |
+
total_index = self.current_epoch * len(self) + index
|
| 390 |
+
permutation_index = total_index // len(self.meta)
|
| 391 |
+
relative_index = total_index % len(self.meta)
|
| 392 |
+
permutation = AudioDataset._get_file_permutation(
|
| 393 |
+
len(self.meta), permutation_index, self.shuffle_seed)
|
| 394 |
+
file_index = permutation[relative_index]
|
| 395 |
+
return self.meta[file_index]
|
| 396 |
+
|
| 397 |
+
if not self.sample_on_weight and not self.sample_on_duration:
|
| 398 |
+
file_index = int(torch.randint(len(self.sampling_probabilities), (1,), generator=rng).item())
|
| 399 |
+
else:
|
| 400 |
+
file_index = int(torch.multinomial(self.sampling_probabilities, 1, generator=rng).item())
|
| 401 |
+
|
| 402 |
+
return self.meta[file_index]
|
| 403 |
+
|
| 404 |
+
def _audio_read(self, path: str, seek_time: float = 0, duration: float = -1):
|
| 405 |
+
# Override this method in subclass if needed.
|
| 406 |
+
if self.load_wav:
|
| 407 |
+
return audio_read(path, seek_time, duration, pad=False)
|
| 408 |
+
else:
|
| 409 |
+
assert self.segment_duration is not None
|
| 410 |
+
n_frames = int(self.sample_rate * self.segment_duration)
|
| 411 |
+
return torch.zeros(self.channels, n_frames), self.sample_rate
|
| 412 |
+
|
| 413 |
+
def __getitem__(self, index: int) -> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, SegmentInfo]]:
|
| 414 |
+
if self.segment_duration is None:
|
| 415 |
+
file_meta = self.meta[index]
|
| 416 |
+
out, sr = audio_read(file_meta.path)
|
| 417 |
+
out = convert_audio(out, sr, self.sample_rate, self.channels)
|
| 418 |
+
n_frames = out.shape[-1]
|
| 419 |
+
segment_info = SegmentInfo(file_meta, seek_time=0., n_frames=n_frames, total_frames=n_frames,
|
| 420 |
+
sample_rate=self.sample_rate, channels=out.shape[0])
|
| 421 |
+
else:
|
| 422 |
+
rng = torch.Generator()
|
| 423 |
+
if self.shuffle:
|
| 424 |
+
# We use index, plus extra randomness, either totally random if we don't know the epoch.
|
| 425 |
+
# otherwise we make use of the epoch number and optional shuffle_seed.
|
| 426 |
+
if self.current_epoch is None:
|
| 427 |
+
rng.manual_seed(index + self.num_samples * random.randint(0, 2**24))
|
| 428 |
+
else:
|
| 429 |
+
rng.manual_seed(index + self.num_samples * (self.current_epoch + self.shuffle_seed))
|
| 430 |
+
else:
|
| 431 |
+
# We only use index
|
| 432 |
+
rng.manual_seed(index)
|
| 433 |
+
|
| 434 |
+
for retry in range(self.max_read_retry):
|
| 435 |
+
file_meta = self.sample_file(index, rng)
|
| 436 |
+
# We add some variance in the file position even if audio file is smaller than segment
|
| 437 |
+
# without ending up with empty segments
|
| 438 |
+
max_seek = max(0, file_meta.duration - self.segment_duration * self.min_segment_ratio)
|
| 439 |
+
seek_time = torch.rand(1, generator=rng).item() * max_seek
|
| 440 |
+
try:
|
| 441 |
+
out, sr = audio_read(file_meta.path, seek_time, self.segment_duration, pad=False)
|
| 442 |
+
out = convert_audio(out, sr, self.sample_rate, self.channels)
|
| 443 |
+
n_frames = out.shape[-1]
|
| 444 |
+
target_frames = int(self.segment_duration * self.sample_rate)
|
| 445 |
+
if self.pad:
|
| 446 |
+
out = F.pad(out, (0, target_frames - n_frames))
|
| 447 |
+
segment_info = SegmentInfo(file_meta, seek_time, n_frames=n_frames, total_frames=target_frames,
|
| 448 |
+
sample_rate=self.sample_rate, channels=out.shape[0])
|
| 449 |
+
except Exception as exc:
|
| 450 |
+
logger.warning("Error opening file %s: %r", file_meta.path, exc)
|
| 451 |
+
if retry == self.max_read_retry - 1:
|
| 452 |
+
raise
|
| 453 |
+
else:
|
| 454 |
+
break
|
| 455 |
+
|
| 456 |
+
if self.return_info:
|
| 457 |
+
# Returns the wav and additional information on the wave segment
|
| 458 |
+
return out, segment_info
|
| 459 |
+
else:
|
| 460 |
+
return out
|
| 461 |
+
|
| 462 |
+
def collater(self, samples):
|
| 463 |
+
"""The collater function has to be provided to the dataloader
|
| 464 |
+
if AudioDataset has return_info=True in order to properly collate
|
| 465 |
+
the samples of a batch.
|
| 466 |
+
"""
|
| 467 |
+
if self.segment_duration is None and len(samples) > 1:
|
| 468 |
+
assert self.pad, "Must allow padding when batching examples of different durations."
|
| 469 |
+
|
| 470 |
+
# In this case the audio reaching the collater is of variable length as segment_duration=None.
|
| 471 |
+
to_pad = self.segment_duration is None and self.pad
|
| 472 |
+
if to_pad:
|
| 473 |
+
max_len = max([wav.shape[-1] for wav, _ in samples])
|
| 474 |
+
|
| 475 |
+
def _pad_wav(wav):
|
| 476 |
+
return F.pad(wav, (0, max_len - wav.shape[-1]))
|
| 477 |
+
|
| 478 |
+
if self.return_info:
|
| 479 |
+
if len(samples) > 0:
|
| 480 |
+
assert len(samples[0]) == 2
|
| 481 |
+
assert isinstance(samples[0][0], torch.Tensor)
|
| 482 |
+
assert isinstance(samples[0][1], SegmentInfo)
|
| 483 |
+
|
| 484 |
+
wavs = [wav for wav, _ in samples]
|
| 485 |
+
segment_infos = [copy.deepcopy(info) for _, info in samples]
|
| 486 |
+
|
| 487 |
+
if to_pad:
|
| 488 |
+
# Each wav could be of a different duration as they are not segmented.
|
| 489 |
+
for i in range(len(samples)):
|
| 490 |
+
# Determines the total length of the signal with padding, so we update here as we pad.
|
| 491 |
+
segment_infos[i].total_frames = max_len
|
| 492 |
+
wavs[i] = _pad_wav(wavs[i])
|
| 493 |
+
|
| 494 |
+
wav = torch.stack(wavs)
|
| 495 |
+
return wav, segment_infos
|
| 496 |
+
else:
|
| 497 |
+
assert isinstance(samples[0], torch.Tensor)
|
| 498 |
+
if to_pad:
|
| 499 |
+
samples = [_pad_wav(s) for s in samples]
|
| 500 |
+
return torch.stack(samples)
|
| 501 |
+
|
| 502 |
+
def _filter_duration(self, meta: tp.List[AudioMeta]) -> tp.List[AudioMeta]:
|
| 503 |
+
"""Filters out audio files with audio durations that will not allow to sample examples from them."""
|
| 504 |
+
orig_len = len(meta)
|
| 505 |
+
|
| 506 |
+
# Filter data that is too short.
|
| 507 |
+
if self.min_audio_duration is not None:
|
| 508 |
+
meta = [m for m in meta if m.duration >= self.min_audio_duration]
|
| 509 |
+
|
| 510 |
+
# Filter data that is too long.
|
| 511 |
+
if self.max_audio_duration is not None:
|
| 512 |
+
meta = [m for m in meta if m.duration <= self.max_audio_duration]
|
| 513 |
+
|
| 514 |
+
filtered_len = len(meta)
|
| 515 |
+
removed_percentage = 100*(1-float(filtered_len)/orig_len)
|
| 516 |
+
msg = 'Removed %.2f percent of the data because it was too short or too long.' % removed_percentage
|
| 517 |
+
if removed_percentage < 10:
|
| 518 |
+
logging.debug(msg)
|
| 519 |
+
else:
|
| 520 |
+
logging.warning(msg)
|
| 521 |
+
return meta
|
| 522 |
+
|
| 523 |
+
@classmethod
|
| 524 |
+
def from_meta(cls, root: tp.Union[str, Path], **kwargs):
|
| 525 |
+
"""Instantiate AudioDataset from a path to a directory containing a manifest as a jsonl file.
|
| 526 |
+
|
| 527 |
+
Args:
|
| 528 |
+
root (str or Path): Path to root folder containing audio files.
|
| 529 |
+
kwargs: Additional keyword arguments for the AudioDataset.
|
| 530 |
+
"""
|
| 531 |
+
root = Path(root)
|
| 532 |
+
if root.is_dir():
|
| 533 |
+
if (root / 'data.jsonl').exists():
|
| 534 |
+
root = root / 'data.jsonl'
|
| 535 |
+
elif (root / 'data.jsonl.gz').exists():
|
| 536 |
+
root = root / 'data.jsonl.gz'
|
| 537 |
+
else:
|
| 538 |
+
raise ValueError("Don't know where to read metadata from in the dir. "
|
| 539 |
+
"Expecting either a data.jsonl or data.jsonl.gz file but none found.")
|
| 540 |
+
meta = load_audio_meta(root)
|
| 541 |
+
return cls(meta, **kwargs)
|
| 542 |
+
|
| 543 |
+
@classmethod
|
| 544 |
+
def from_path(cls, root: tp.Union[str, Path], minimal_meta: bool = True,
|
| 545 |
+
exts: tp.List[str] = DEFAULT_EXTS, **kwargs):
|
| 546 |
+
"""Instantiate AudioDataset from a path containing (possibly nested) audio files.
|
| 547 |
+
|
| 548 |
+
Args:
|
| 549 |
+
root (str or Path): Path to root folder containing audio files.
|
| 550 |
+
minimal_meta (bool): Whether to only load minimal metadata or not.
|
| 551 |
+
exts (list of str): Extensions for audio files.
|
| 552 |
+
kwargs: Additional keyword arguments for the AudioDataset.
|
| 553 |
+
"""
|
| 554 |
+
root = Path(root)
|
| 555 |
+
if root.is_file():
|
| 556 |
+
meta = load_audio_meta(root, resolve=True)
|
| 557 |
+
else:
|
| 558 |
+
meta = find_audio_files(root, exts, minimal=minimal_meta, resolve=True)
|
| 559 |
+
return cls(meta, **kwargs)
|
| 560 |
+
|
| 561 |
+
|
| 562 |
+
def main():
|
| 563 |
+
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
|
| 564 |
+
parser = argparse.ArgumentParser(
|
| 565 |
+
prog='audio_dataset',
|
| 566 |
+
description='Generate .jsonl files by scanning a folder.')
|
| 567 |
+
parser.add_argument('root', help='Root folder with all the audio files')
|
| 568 |
+
parser.add_argument('output_meta_file',
|
| 569 |
+
help='Output file to store the metadata, ')
|
| 570 |
+
parser.add_argument('--complete',
|
| 571 |
+
action='store_false', dest='minimal', default=True,
|
| 572 |
+
help='Retrieve all metadata, even the one that are expansive '
|
| 573 |
+
'to compute (e.g. normalization).')
|
| 574 |
+
parser.add_argument('--resolve',
|
| 575 |
+
action='store_true', default=False,
|
| 576 |
+
help='Resolve the paths to be absolute and with no symlinks.')
|
| 577 |
+
parser.add_argument('--workers',
|
| 578 |
+
default=10, type=int,
|
| 579 |
+
help='Number of workers.')
|
| 580 |
+
args = parser.parse_args()
|
| 581 |
+
meta = find_audio_files(args.root, DEFAULT_EXTS, progress=True,
|
| 582 |
+
resolve=args.resolve, minimal=args.minimal, workers=args.workers)
|
| 583 |
+
save_audio_meta(args.output_meta_file, meta)
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
if __name__ == '__main__':
|
| 587 |
+
main()
|
src/audiocraft/data/audio_utils.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Various utilities for audio convertion (pcm format, sample rate and channels),
|
| 7 |
+
and volume normalization."""
|
| 8 |
+
import sys
|
| 9 |
+
import typing as tp
|
| 10 |
+
|
| 11 |
+
import julius
|
| 12 |
+
import torch
|
| 13 |
+
import torchaudio
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def convert_audio_channels(wav: torch.Tensor, channels: int = 2) -> torch.Tensor:
|
| 17 |
+
"""Convert audio to the given number of channels.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
wav (torch.Tensor): Audio wave of shape [B, C, T].
|
| 21 |
+
channels (int): Expected number of channels as output.
|
| 22 |
+
Returns:
|
| 23 |
+
torch.Tensor: Downmixed or unchanged audio wave [B, C, T].
|
| 24 |
+
"""
|
| 25 |
+
*shape, src_channels, length = wav.shape
|
| 26 |
+
if src_channels == channels:
|
| 27 |
+
pass
|
| 28 |
+
elif channels == 1:
|
| 29 |
+
# Case 1:
|
| 30 |
+
# The caller asked 1-channel audio, and the stream has multiple
|
| 31 |
+
# channels, downmix all channels.
|
| 32 |
+
wav = wav.mean(dim=-2, keepdim=True)
|
| 33 |
+
elif src_channels == 1:
|
| 34 |
+
# Case 2:
|
| 35 |
+
# The caller asked for multiple channels, but the input file has
|
| 36 |
+
# a single channel, replicate the audio over all channels.
|
| 37 |
+
wav = wav.expand(*shape, channels, length)
|
| 38 |
+
elif src_channels >= channels:
|
| 39 |
+
# Case 3:
|
| 40 |
+
# The caller asked for multiple channels, and the input file has
|
| 41 |
+
# more channels than requested. In that case return the first channels.
|
| 42 |
+
wav = wav[..., :channels, :]
|
| 43 |
+
else:
|
| 44 |
+
# Case 4: What is a reasonable choice here?
|
| 45 |
+
raise ValueError('The audio file has less channels than requested but is not mono.')
|
| 46 |
+
return wav
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def convert_audio(wav: torch.Tensor, from_rate: float,
|
| 50 |
+
to_rate: float, to_channels: int) -> torch.Tensor:
|
| 51 |
+
"""Convert audio to new sample rate and number of audio channels."""
|
| 52 |
+
wav = julius.resample_frac(wav, int(from_rate), int(to_rate))
|
| 53 |
+
wav = convert_audio_channels(wav, to_channels)
|
| 54 |
+
return wav
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def normalize_loudness(wav: torch.Tensor, sample_rate: int, loudness_headroom_db: float = 14,
|
| 58 |
+
loudness_compressor: bool = False, energy_floor: float = 2e-3):
|
| 59 |
+
"""Normalize an input signal to a user loudness in dB LKFS.
|
| 60 |
+
Audio loudness is defined according to the ITU-R BS.1770-4 recommendation.
|
| 61 |
+
|
| 62 |
+
Args:
|
| 63 |
+
wav (torch.Tensor): Input multichannel audio data.
|
| 64 |
+
sample_rate (int): Sample rate.
|
| 65 |
+
loudness_headroom_db (float): Target loudness of the output in dB LUFS.
|
| 66 |
+
loudness_compressor (bool): Uses tanh for soft clipping.
|
| 67 |
+
energy_floor (float): anything below that RMS level will not be rescaled.
|
| 68 |
+
Returns:
|
| 69 |
+
torch.Tensor: Loudness normalized output data.
|
| 70 |
+
"""
|
| 71 |
+
energy = wav.pow(2).mean().sqrt().item()
|
| 72 |
+
if energy < energy_floor:
|
| 73 |
+
return wav
|
| 74 |
+
transform = torchaudio.transforms.Loudness(sample_rate)
|
| 75 |
+
input_loudness_db = transform(wav).item()
|
| 76 |
+
# calculate the gain needed to scale to the desired loudness level
|
| 77 |
+
delta_loudness = -loudness_headroom_db - input_loudness_db
|
| 78 |
+
gain = 10.0 ** (delta_loudness / 20.0)
|
| 79 |
+
output = gain * wav
|
| 80 |
+
if loudness_compressor:
|
| 81 |
+
output = torch.tanh(output)
|
| 82 |
+
assert output.isfinite().all(), (input_loudness_db, wav.pow(2).mean().sqrt())
|
| 83 |
+
return output
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _clip_wav(wav: torch.Tensor, log_clipping: bool = False, stem_name: tp.Optional[str] = None) -> None:
|
| 87 |
+
"""Utility function to clip the audio with logging if specified."""
|
| 88 |
+
max_scale = wav.abs().max()
|
| 89 |
+
if log_clipping and max_scale > 1:
|
| 90 |
+
clamp_prob = (wav.abs() > 1).float().mean().item()
|
| 91 |
+
print(f"CLIPPING {stem_name or ''} happening with proba (a bit of clipping is okay):",
|
| 92 |
+
clamp_prob, "maximum scale: ", max_scale.item(), file=sys.stderr)
|
| 93 |
+
wav.clamp_(-1, 1)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def normalize_audio(wav: torch.Tensor, normalize: bool = True,
|
| 97 |
+
strategy: str = 'peak', peak_clip_headroom_db: float = 1,
|
| 98 |
+
rms_headroom_db: float = 18, loudness_headroom_db: float = 14,
|
| 99 |
+
loudness_compressor: bool = False, log_clipping: bool = False,
|
| 100 |
+
sample_rate: tp.Optional[int] = None,
|
| 101 |
+
stem_name: tp.Optional[str] = None) -> torch.Tensor:
|
| 102 |
+
"""Normalize the audio according to the prescribed strategy (see after).
|
| 103 |
+
|
| 104 |
+
Args:
|
| 105 |
+
wav (torch.Tensor): Audio data.
|
| 106 |
+
normalize (bool): if `True` (default), normalizes according to the prescribed
|
| 107 |
+
strategy (see after). If `False`, the strategy is only used in case clipping
|
| 108 |
+
would happen.
|
| 109 |
+
strategy (str): Can be either 'clip', 'peak', or 'rms'. Default is 'peak',
|
| 110 |
+
i.e. audio is normalized by its largest value. RMS normalizes by root-mean-square
|
| 111 |
+
with extra headroom to avoid clipping. 'clip' just clips.
|
| 112 |
+
peak_clip_headroom_db (float): Headroom in dB when doing 'peak' or 'clip' strategy.
|
| 113 |
+
rms_headroom_db (float): Headroom in dB when doing 'rms' strategy. This must be much larger
|
| 114 |
+
than the `peak_clip` one to avoid further clipping.
|
| 115 |
+
loudness_headroom_db (float): Target loudness for loudness normalization.
|
| 116 |
+
loudness_compressor (bool): If True, uses tanh based soft clipping.
|
| 117 |
+
log_clipping (bool): If True, basic logging on stderr when clipping still
|
| 118 |
+
occurs despite strategy (only for 'rms').
|
| 119 |
+
sample_rate (int): Sample rate for the audio data (required for loudness).
|
| 120 |
+
stem_name (str, optional): Stem name for clipping logging.
|
| 121 |
+
Returns:
|
| 122 |
+
torch.Tensor: Normalized audio.
|
| 123 |
+
"""
|
| 124 |
+
scale_peak = 10 ** (-peak_clip_headroom_db / 20)
|
| 125 |
+
scale_rms = 10 ** (-rms_headroom_db / 20)
|
| 126 |
+
if strategy == 'peak':
|
| 127 |
+
rescaling = (scale_peak / wav.abs().max())
|
| 128 |
+
if normalize or rescaling < 1:
|
| 129 |
+
wav = wav * rescaling
|
| 130 |
+
elif strategy == 'clip':
|
| 131 |
+
wav = wav.clamp(-scale_peak, scale_peak)
|
| 132 |
+
elif strategy == 'rms':
|
| 133 |
+
mono = wav.mean(dim=0)
|
| 134 |
+
rescaling = scale_rms / mono.pow(2).mean().sqrt()
|
| 135 |
+
if normalize or rescaling < 1:
|
| 136 |
+
wav = wav * rescaling
|
| 137 |
+
_clip_wav(wav, log_clipping=log_clipping, stem_name=stem_name)
|
| 138 |
+
elif strategy == 'loudness':
|
| 139 |
+
assert sample_rate is not None, "Loudness normalization requires sample rate."
|
| 140 |
+
wav = normalize_loudness(wav, sample_rate, loudness_headroom_db, loudness_compressor)
|
| 141 |
+
_clip_wav(wav, log_clipping=log_clipping, stem_name=stem_name)
|
| 142 |
+
else:
|
| 143 |
+
assert wav.abs().max() < 1
|
| 144 |
+
assert strategy == '' or strategy == 'none', f"Unexpected strategy: '{strategy}'"
|
| 145 |
+
return wav
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def f32_pcm(wav: torch.Tensor) -> torch.Tensor:
|
| 149 |
+
"""Convert audio to float 32 bits PCM format.
|
| 150 |
+
"""
|
| 151 |
+
if wav.dtype.is_floating_point:
|
| 152 |
+
return wav
|
| 153 |
+
elif wav.dtype == torch.int16:
|
| 154 |
+
return wav.float() / 2**15
|
| 155 |
+
elif wav.dtype == torch.int32:
|
| 156 |
+
return wav.float() / 2**31
|
| 157 |
+
raise ValueError(f"Unsupported wav dtype: {wav.dtype}")
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
def i16_pcm(wav: torch.Tensor) -> torch.Tensor:
|
| 161 |
+
"""Convert audio to int 16 bits PCM format.
|
| 162 |
+
|
| 163 |
+
..Warning:: There exist many formula for doing this conversion. None are perfect
|
| 164 |
+
due to the asymmetry of the int16 range. One either have possible clipping, DC offset,
|
| 165 |
+
or inconsistencies with f32_pcm. If the given wav doesn't have enough headroom,
|
| 166 |
+
it is possible that `i16_pcm(f32_pcm)) != Identity`.
|
| 167 |
+
"""
|
| 168 |
+
if wav.dtype.is_floating_point:
|
| 169 |
+
assert wav.abs().max() <= 1
|
| 170 |
+
candidate = (wav * 2 ** 15).round()
|
| 171 |
+
if candidate.max() >= 2 ** 15: # clipping would occur
|
| 172 |
+
candidate = (wav * (2 ** 15 - 1)).round()
|
| 173 |
+
return candidate.short()
|
| 174 |
+
else:
|
| 175 |
+
assert wav.dtype == torch.int16
|
| 176 |
+
return wav
|
src/audiocraft/data/zip.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Utility for reading some info from inside a zip file.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
import typing
|
| 10 |
+
import zipfile
|
| 11 |
+
|
| 12 |
+
from dataclasses import dataclass
|
| 13 |
+
from functools import lru_cache
|
| 14 |
+
from typing_extensions import Literal
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
DEFAULT_SIZE = 32
|
| 18 |
+
MODE = Literal['r', 'w', 'x', 'a']
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass(order=True)
|
| 22 |
+
class PathInZip:
|
| 23 |
+
"""Hold a path of file within a zip file.
|
| 24 |
+
|
| 25 |
+
Args:
|
| 26 |
+
path (str): The convention is <path_to_zip>:<relative_path_inside_zip>.
|
| 27 |
+
Let's assume there is a zip file /some/location/foo.zip
|
| 28 |
+
and inside of it is a json file located at /data/file1.json,
|
| 29 |
+
Then we expect path = "/some/location/foo.zip:/data/file1.json".
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
INFO_PATH_SEP = ':'
|
| 33 |
+
zip_path: str
|
| 34 |
+
file_path: str
|
| 35 |
+
|
| 36 |
+
def __init__(self, path: str) -> None:
|
| 37 |
+
split_path = path.split(self.INFO_PATH_SEP)
|
| 38 |
+
assert len(split_path) == 2
|
| 39 |
+
self.zip_path, self.file_path = split_path
|
| 40 |
+
|
| 41 |
+
@classmethod
|
| 42 |
+
def from_paths(cls, zip_path: str, file_path: str):
|
| 43 |
+
return cls(zip_path + cls.INFO_PATH_SEP + file_path)
|
| 44 |
+
|
| 45 |
+
def __str__(self) -> str:
|
| 46 |
+
return self.zip_path + self.INFO_PATH_SEP + self.file_path
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _open_zip(path: str, mode: MODE = 'r'):
|
| 50 |
+
return zipfile.ZipFile(path, mode)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
_cached_open_zip = lru_cache(DEFAULT_SIZE)(_open_zip)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def set_zip_cache_size(max_size: int):
|
| 57 |
+
"""Sets the maximal LRU caching for zip file opening.
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
max_size (int): the maximal LRU cache.
|
| 61 |
+
"""
|
| 62 |
+
global _cached_open_zip
|
| 63 |
+
_cached_open_zip = lru_cache(max_size)(_open_zip)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def open_file_in_zip(path_in_zip: PathInZip, mode: str = 'r') -> typing.IO:
|
| 67 |
+
"""Opens a file stored inside a zip and returns a file-like object.
|
| 68 |
+
|
| 69 |
+
Args:
|
| 70 |
+
path_in_zip (PathInZip): A PathInZip object representing the file to return a file-like object of.
|
| 71 |
+
mode (str): The mode in which to open the file with.
|
| 72 |
+
Returns:
|
| 73 |
+
A file-like object for PathInZip.
|
| 74 |
+
"""
|
| 75 |
+
zf = _cached_open_zip(path_in_zip.zip_path)
|
| 76 |
+
return zf.open(path_in_zip.file_path)
|
src/audiocraft/environment.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Provides cluster and tools configuration across clusters (slurm, dora, utilities).
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import logging
|
| 12 |
+
import os
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import re
|
| 15 |
+
import typing as tp
|
| 16 |
+
|
| 17 |
+
import omegaconf
|
| 18 |
+
|
| 19 |
+
from .utils.cluster import _guess_cluster_type
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
logger = logging.getLogger(__name__)
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class AudioCraftEnvironment:
|
| 26 |
+
"""Environment configuration for teams and clusters.
|
| 27 |
+
|
| 28 |
+
AudioCraftEnvironment picks compute cluster settings (slurm, dora) from the current running environment
|
| 29 |
+
or declared variable and the loaded team configuration. Additionally, the AudioCraftEnvironment
|
| 30 |
+
provides pointers to a reference folder resolved automatically across clusters that is shared across team members,
|
| 31 |
+
allowing to share sigs or other files to run jobs. Finally, it provides dataset mappers to automatically
|
| 32 |
+
map dataset file paths to new locations across clusters, allowing to use the same manifest of files across cluters.
|
| 33 |
+
|
| 34 |
+
The cluster type is identified automatically and base configuration file is read from config/teams.yaml.
|
| 35 |
+
Use the following environment variables to specify the cluster, team or configuration:
|
| 36 |
+
|
| 37 |
+
AUDIOCRAFT_CLUSTER (optional): Cluster type to enforce. Useful if the cluster type
|
| 38 |
+
cannot be inferred automatically.
|
| 39 |
+
AUDIOCRAFT_CONFIG (optional): Path to yaml config holding the teams configuration.
|
| 40 |
+
If not set, configuration is read from config/teams.yaml.
|
| 41 |
+
AUDIOCRAFT_TEAM (optional): Name of the team. Recommended to set to your own team.
|
| 42 |
+
Cluster configuration are shared across teams to match compute allocation,
|
| 43 |
+
specify your cluster configuration in the configuration file under a key mapping
|
| 44 |
+
your team name.
|
| 45 |
+
"""
|
| 46 |
+
_instance = None
|
| 47 |
+
DEFAULT_TEAM = "default"
|
| 48 |
+
|
| 49 |
+
def __init__(self) -> None:
|
| 50 |
+
"""Loads configuration."""
|
| 51 |
+
self.team: str = os.getenv("AUDIOCRAFT_TEAM", self.DEFAULT_TEAM)
|
| 52 |
+
cluster_type = _guess_cluster_type()
|
| 53 |
+
cluster = os.getenv(
|
| 54 |
+
"AUDIOCRAFT_CLUSTER", cluster_type.value
|
| 55 |
+
)
|
| 56 |
+
logger.info("Detecting cluster type %s", cluster_type)
|
| 57 |
+
|
| 58 |
+
self.cluster: str = cluster
|
| 59 |
+
|
| 60 |
+
config_path = os.getenv(
|
| 61 |
+
"AUDIOCRAFT_CONFIG",
|
| 62 |
+
Path(__file__)
|
| 63 |
+
.parent.parent.joinpath("config/teams", self.team)
|
| 64 |
+
.with_suffix(".yaml"),
|
| 65 |
+
)
|
| 66 |
+
self.config = omegaconf.OmegaConf.load(config_path)
|
| 67 |
+
self._dataset_mappers = []
|
| 68 |
+
cluster_config = self._get_cluster_config()
|
| 69 |
+
if "dataset_mappers" in cluster_config:
|
| 70 |
+
for pattern, repl in cluster_config["dataset_mappers"].items():
|
| 71 |
+
regex = re.compile(pattern)
|
| 72 |
+
self._dataset_mappers.append((regex, repl))
|
| 73 |
+
|
| 74 |
+
def _get_cluster_config(self) -> omegaconf.DictConfig:
|
| 75 |
+
assert isinstance(self.config, omegaconf.DictConfig)
|
| 76 |
+
return self.config[self.cluster]
|
| 77 |
+
|
| 78 |
+
@classmethod
|
| 79 |
+
def instance(cls):
|
| 80 |
+
if cls._instance is None:
|
| 81 |
+
cls._instance = cls()
|
| 82 |
+
return cls._instance
|
| 83 |
+
|
| 84 |
+
@classmethod
|
| 85 |
+
def reset(cls):
|
| 86 |
+
"""Clears the environment and forces a reload on next invocation."""
|
| 87 |
+
cls._instance = None
|
| 88 |
+
|
| 89 |
+
@classmethod
|
| 90 |
+
def get_team(cls) -> str:
|
| 91 |
+
"""Gets the selected team as dictated by the AUDIOCRAFT_TEAM env var.
|
| 92 |
+
If not defined, defaults to "labs".
|
| 93 |
+
"""
|
| 94 |
+
return cls.instance().team
|
| 95 |
+
|
| 96 |
+
@classmethod
|
| 97 |
+
def get_cluster(cls) -> str:
|
| 98 |
+
"""Gets the detected cluster.
|
| 99 |
+
This value can be overridden by the AUDIOCRAFT_CLUSTER env var.
|
| 100 |
+
"""
|
| 101 |
+
return cls.instance().cluster
|
| 102 |
+
|
| 103 |
+
@classmethod
|
| 104 |
+
def get_dora_dir(cls) -> Path:
|
| 105 |
+
"""Gets the path to the dora directory for the current team and cluster.
|
| 106 |
+
Value is overridden by the AUDIOCRAFT_DORA_DIR env var.
|
| 107 |
+
"""
|
| 108 |
+
cluster_config = cls.instance()._get_cluster_config()
|
| 109 |
+
dora_dir = os.getenv("AUDIOCRAFT_DORA_DIR", cluster_config["dora_dir"])
|
| 110 |
+
logger.warning(f"Dora directory: {dora_dir}")
|
| 111 |
+
return Path(dora_dir)
|
| 112 |
+
|
| 113 |
+
@classmethod
|
| 114 |
+
def get_reference_dir(cls) -> Path:
|
| 115 |
+
"""Gets the path to the reference directory for the current team and cluster.
|
| 116 |
+
Value is overridden by the AUDIOCRAFT_REFERENCE_DIR env var.
|
| 117 |
+
"""
|
| 118 |
+
cluster_config = cls.instance()._get_cluster_config()
|
| 119 |
+
return Path(os.getenv("AUDIOCRAFT_REFERENCE_DIR", cluster_config["reference_dir"]))
|
| 120 |
+
|
| 121 |
+
@classmethod
|
| 122 |
+
def get_slurm_exclude(cls) -> tp.Optional[str]:
|
| 123 |
+
"""Get the list of nodes to exclude for that cluster."""
|
| 124 |
+
cluster_config = cls.instance()._get_cluster_config()
|
| 125 |
+
return cluster_config.get("slurm_exclude")
|
| 126 |
+
|
| 127 |
+
@classmethod
|
| 128 |
+
def get_slurm_partitions(cls, partition_types: tp.Optional[tp.List[str]] = None) -> str:
|
| 129 |
+
"""Gets the requested partitions for the current team and cluster as a comma-separated string.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
partition_types (list[str], optional): partition types to retrieve. Values must be
|
| 133 |
+
from ['global', 'team']. If not provided, the global partition is returned.
|
| 134 |
+
"""
|
| 135 |
+
if not partition_types:
|
| 136 |
+
partition_types = ["global"]
|
| 137 |
+
|
| 138 |
+
cluster_config = cls.instance()._get_cluster_config()
|
| 139 |
+
partitions = [
|
| 140 |
+
cluster_config["partitions"][partition_type]
|
| 141 |
+
for partition_type in partition_types
|
| 142 |
+
]
|
| 143 |
+
return ",".join(partitions)
|
| 144 |
+
|
| 145 |
+
@classmethod
|
| 146 |
+
def resolve_reference_path(cls, path: tp.Union[str, Path]) -> Path:
|
| 147 |
+
"""Converts reference placeholder in path with configured reference dir to resolve paths.
|
| 148 |
+
|
| 149 |
+
Args:
|
| 150 |
+
path (str or Path): Path to resolve.
|
| 151 |
+
Returns:
|
| 152 |
+
Path: Resolved path.
|
| 153 |
+
"""
|
| 154 |
+
path = str(path)
|
| 155 |
+
|
| 156 |
+
if path.startswith("//reference"):
|
| 157 |
+
reference_dir = cls.get_reference_dir()
|
| 158 |
+
logger.warn(f"Reference directory: {reference_dir}")
|
| 159 |
+
assert (
|
| 160 |
+
reference_dir.exists() and reference_dir.is_dir()
|
| 161 |
+
), f"Reference directory does not exist: {reference_dir}."
|
| 162 |
+
path = re.sub("^//reference", str(reference_dir), path)
|
| 163 |
+
|
| 164 |
+
return Path(path)
|
| 165 |
+
|
| 166 |
+
@classmethod
|
| 167 |
+
def apply_dataset_mappers(cls, path: str) -> str:
|
| 168 |
+
"""Applies dataset mapping regex rules as defined in the configuration.
|
| 169 |
+
If no rules are defined, the path is returned as-is.
|
| 170 |
+
"""
|
| 171 |
+
instance = cls.instance()
|
| 172 |
+
|
| 173 |
+
for pattern, repl in instance._dataset_mappers:
|
| 174 |
+
path = pattern.sub(repl, path)
|
| 175 |
+
|
| 176 |
+
return path
|
src/audiocraft/models/__init__.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""
|
| 7 |
+
Models for EnCodec, AudioGen, MusicGen, as well as the generic LMModel.
|
| 8 |
+
"""
|
| 9 |
+
# flake8: noqa
|
| 10 |
+
# dropped non-inference classes (AudioGen, MultiBandDiffusion, DiffusionUnet/unet.py,
|
| 11 |
+
# encodec.py's DAC) to avoid unnecessary dependencies
|
| 12 |
+
from . import builders, loaders
|
| 13 |
+
from .encodec import (
|
| 14 |
+
CompressionModel, EncodecModel,
|
| 15 |
+
HFEncodecModel, HFEncodecCompressionModel)
|
| 16 |
+
from .lm import LMModel
|
| 17 |
+
from .lm_magnet import MagnetLMModel
|
| 18 |
+
from .musicgen import MusicGen
|
| 19 |
+
from .magnet import MAGNeT
|
src/audiocraft/models/builders.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
All the functions to build the relevant models and modules
|
| 9 |
+
from the Hydra config.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import typing as tp
|
| 13 |
+
|
| 14 |
+
import src.audiocraft as audiocraft
|
| 15 |
+
import omegaconf
|
| 16 |
+
import torch
|
| 17 |
+
|
| 18 |
+
from .encodec import CompressionModel, EncodecModel, InterleaveStereoCompressionModel
|
| 19 |
+
from .lm import LMModel
|
| 20 |
+
from .lm_magnet import MagnetLMModel
|
| 21 |
+
from ..modules.codebooks_patterns import (
|
| 22 |
+
CodebooksPatternProvider,
|
| 23 |
+
DelayedPatternProvider,
|
| 24 |
+
MusicLMPattern,
|
| 25 |
+
ParallelPatternProvider,
|
| 26 |
+
UnrolledPatternProvider,
|
| 27 |
+
CoarseFirstPattern,
|
| 28 |
+
)
|
| 29 |
+
from ..modules.conditioners import (
|
| 30 |
+
BaseConditioner,
|
| 31 |
+
ChromaStemConditioner,
|
| 32 |
+
CLAPEmbeddingConditioner,
|
| 33 |
+
ConditionFuser,
|
| 34 |
+
ConditioningProvider,
|
| 35 |
+
LUTConditioner,
|
| 36 |
+
T5Conditioner,
|
| 37 |
+
)
|
| 38 |
+
from .unet import DiffusionUnet
|
| 39 |
+
from .. import quantization as qt
|
| 40 |
+
from ..utils.utils import dict_from_config
|
| 41 |
+
from ..modules.diffusion_schedule import MultiBandProcessor, SampleProcessor
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def get_quantizer(quantizer: str, cfg: omegaconf.DictConfig, dimension: int) -> qt.BaseQuantizer:
|
| 45 |
+
klass = {
|
| 46 |
+
'no_quant': qt.DummyQuantizer,
|
| 47 |
+
'rvq': qt.ResidualVectorQuantizer
|
| 48 |
+
}[quantizer]
|
| 49 |
+
kwargs = dict_from_config(getattr(cfg, quantizer))
|
| 50 |
+
if quantizer != 'no_quant':
|
| 51 |
+
kwargs['dimension'] = dimension
|
| 52 |
+
return klass(**kwargs)
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def get_encodec_autoencoder(encoder_name: str, cfg: omegaconf.DictConfig):
|
| 56 |
+
if encoder_name == 'seanet':
|
| 57 |
+
kwargs = dict_from_config(getattr(cfg, 'seanet'))
|
| 58 |
+
encoder_override_kwargs = kwargs.pop('encoder')
|
| 59 |
+
decoder_override_kwargs = kwargs.pop('decoder')
|
| 60 |
+
encoder_kwargs = {**kwargs, **encoder_override_kwargs}
|
| 61 |
+
decoder_kwargs = {**kwargs, **decoder_override_kwargs}
|
| 62 |
+
encoder = audiocraft.modules.SEANetEncoder(**encoder_kwargs)
|
| 63 |
+
decoder = audiocraft.modules.SEANetDecoder(**decoder_kwargs)
|
| 64 |
+
return encoder, decoder
|
| 65 |
+
else:
|
| 66 |
+
raise KeyError(f"Unexpected compression model {cfg.compression_model}")
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def get_compression_model(cfg: omegaconf.DictConfig) -> CompressionModel:
|
| 70 |
+
"""Instantiate a compression model."""
|
| 71 |
+
if cfg.compression_model == 'encodec':
|
| 72 |
+
kwargs = dict_from_config(getattr(cfg, 'encodec'))
|
| 73 |
+
encoder_name = kwargs.pop('autoencoder')
|
| 74 |
+
quantizer_name = kwargs.pop('quantizer')
|
| 75 |
+
encoder, decoder = get_encodec_autoencoder(encoder_name, cfg)
|
| 76 |
+
quantizer = get_quantizer(quantizer_name, cfg, encoder.dimension)
|
| 77 |
+
frame_rate = kwargs['sample_rate'] // encoder.hop_length
|
| 78 |
+
renormalize = kwargs.pop('renormalize', False)
|
| 79 |
+
# deprecated params
|
| 80 |
+
kwargs.pop('renorm', None)
|
| 81 |
+
return EncodecModel(encoder, decoder, quantizer,
|
| 82 |
+
frame_rate=frame_rate, renormalize=renormalize, **kwargs).to(cfg.device)
|
| 83 |
+
else:
|
| 84 |
+
raise KeyError(f"Unexpected compression model {cfg.compression_model}")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def get_lm_model(cfg: omegaconf.DictConfig) -> LMModel:
|
| 88 |
+
"""Instantiate a transformer LM."""
|
| 89 |
+
if cfg.lm_model in ['transformer_lm', 'transformer_lm_magnet']:
|
| 90 |
+
kwargs = dict_from_config(getattr(cfg, 'transformer_lm'))
|
| 91 |
+
n_q = kwargs['n_q']
|
| 92 |
+
q_modeling = kwargs.pop('q_modeling', None)
|
| 93 |
+
codebooks_pattern_cfg = getattr(cfg, 'codebooks_pattern')
|
| 94 |
+
attribute_dropout = dict_from_config(getattr(cfg, 'attribute_dropout'))
|
| 95 |
+
cls_free_guidance = dict_from_config(getattr(cfg, 'classifier_free_guidance'))
|
| 96 |
+
cfg_prob, cfg_coef = cls_free_guidance['training_dropout'], cls_free_guidance['inference_coef']
|
| 97 |
+
fuser = get_condition_fuser(cfg)
|
| 98 |
+
condition_provider = get_conditioner_provider(kwargs["dim"], cfg).to(cfg.device)
|
| 99 |
+
if len(fuser.fuse2cond['cross']) > 0: # enforce cross-att programmatically
|
| 100 |
+
kwargs['cross_attention'] = True
|
| 101 |
+
if codebooks_pattern_cfg.modeling is None:
|
| 102 |
+
assert q_modeling is not None, \
|
| 103 |
+
"LM model should either have a codebook pattern defined or transformer_lm.q_modeling"
|
| 104 |
+
codebooks_pattern_cfg = omegaconf.OmegaConf.create(
|
| 105 |
+
{'modeling': q_modeling, 'delay': {'delays': list(range(n_q))}}
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
pattern_provider = get_codebooks_pattern_provider(n_q, codebooks_pattern_cfg)
|
| 109 |
+
lm_class = MagnetLMModel if cfg.lm_model == 'transformer_lm_magnet' else LMModel
|
| 110 |
+
return lm_class(
|
| 111 |
+
pattern_provider=pattern_provider,
|
| 112 |
+
condition_provider=condition_provider,
|
| 113 |
+
fuser=fuser,
|
| 114 |
+
cfg_dropout=cfg_prob,
|
| 115 |
+
cfg_coef=cfg_coef,
|
| 116 |
+
attribute_dropout=attribute_dropout,
|
| 117 |
+
dtype=getattr(torch, cfg.dtype),
|
| 118 |
+
device=cfg.device,
|
| 119 |
+
**kwargs
|
| 120 |
+
).to(cfg.device)
|
| 121 |
+
else:
|
| 122 |
+
raise KeyError(f"Unexpected LM model {cfg.lm_model}")
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def get_conditioner_provider(output_dim: int, cfg: omegaconf.DictConfig) -> ConditioningProvider:
|
| 126 |
+
"""Instantiate a conditioning model."""
|
| 127 |
+
device = cfg.device
|
| 128 |
+
duration = cfg.dataset.segment_duration
|
| 129 |
+
cfg = getattr(cfg, 'conditioners')
|
| 130 |
+
dict_cfg = {} if cfg is None else dict_from_config(cfg)
|
| 131 |
+
conditioners: tp.Dict[str, BaseConditioner] = {}
|
| 132 |
+
condition_provider_args = dict_cfg.pop('args', {})
|
| 133 |
+
condition_provider_args.pop('merge_text_conditions_p', None)
|
| 134 |
+
condition_provider_args.pop('drop_desc_p', None)
|
| 135 |
+
|
| 136 |
+
for cond, cond_cfg in dict_cfg.items():
|
| 137 |
+
model_type = cond_cfg['model']
|
| 138 |
+
model_args = cond_cfg[model_type]
|
| 139 |
+
if model_type == 't5':
|
| 140 |
+
conditioners[str(cond)] = T5Conditioner(output_dim=output_dim, device=device, **model_args)
|
| 141 |
+
elif model_type == 'lut':
|
| 142 |
+
conditioners[str(cond)] = LUTConditioner(output_dim=output_dim, **model_args)
|
| 143 |
+
elif model_type == 'chroma_stem':
|
| 144 |
+
conditioners[str(cond)] = ChromaStemConditioner(
|
| 145 |
+
output_dim=output_dim,
|
| 146 |
+
duration=duration,
|
| 147 |
+
device=device,
|
| 148 |
+
**model_args
|
| 149 |
+
)
|
| 150 |
+
elif model_type == 'clap':
|
| 151 |
+
conditioners[str(cond)] = CLAPEmbeddingConditioner(
|
| 152 |
+
output_dim=output_dim,
|
| 153 |
+
device=device,
|
| 154 |
+
**model_args
|
| 155 |
+
)
|
| 156 |
+
else:
|
| 157 |
+
raise ValueError(f"Unrecognized conditioning model: {model_type}")
|
| 158 |
+
conditioner = ConditioningProvider(conditioners, device=device, **condition_provider_args)
|
| 159 |
+
return conditioner
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def get_condition_fuser(cfg: omegaconf.DictConfig) -> ConditionFuser:
|
| 163 |
+
"""Instantiate a condition fuser object."""
|
| 164 |
+
fuser_cfg = getattr(cfg, 'fuser')
|
| 165 |
+
fuser_methods = ['sum', 'cross', 'prepend', 'input_interpolate']
|
| 166 |
+
fuse2cond = {k: fuser_cfg[k] for k in fuser_methods}
|
| 167 |
+
kwargs = {k: v for k, v in fuser_cfg.items() if k not in fuser_methods}
|
| 168 |
+
fuser = ConditionFuser(fuse2cond=fuse2cond, **kwargs)
|
| 169 |
+
return fuser
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def get_codebooks_pattern_provider(n_q: int, cfg: omegaconf.DictConfig) -> CodebooksPatternProvider:
|
| 173 |
+
"""Instantiate a codebooks pattern provider object."""
|
| 174 |
+
pattern_providers = {
|
| 175 |
+
'parallel': ParallelPatternProvider,
|
| 176 |
+
'delay': DelayedPatternProvider,
|
| 177 |
+
'unroll': UnrolledPatternProvider,
|
| 178 |
+
'coarse_first': CoarseFirstPattern,
|
| 179 |
+
'musiclm': MusicLMPattern,
|
| 180 |
+
}
|
| 181 |
+
name = cfg.modeling
|
| 182 |
+
kwargs = dict_from_config(cfg.get(name)) if hasattr(cfg, name) else {}
|
| 183 |
+
klass = pattern_providers[name]
|
| 184 |
+
return klass(n_q, **kwargs)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
def get_debug_compression_model(device='cpu', sample_rate: int = 32000):
|
| 188 |
+
"""Instantiate a debug compression model to be used for unit tests."""
|
| 189 |
+
assert sample_rate in [16000, 32000], "unsupported sample rate for debug compression model"
|
| 190 |
+
model_ratios = {
|
| 191 |
+
16000: [10, 8, 8], # 25 Hz at 16kHz
|
| 192 |
+
32000: [10, 8, 16] # 25 Hz at 32kHz
|
| 193 |
+
}
|
| 194 |
+
ratios: tp.List[int] = model_ratios[sample_rate]
|
| 195 |
+
frame_rate = 25
|
| 196 |
+
seanet_kwargs: dict = {
|
| 197 |
+
'n_filters': 4,
|
| 198 |
+
'n_residual_layers': 1,
|
| 199 |
+
'dimension': 32,
|
| 200 |
+
'ratios': ratios,
|
| 201 |
+
}
|
| 202 |
+
encoder = audiocraft.modules.SEANetEncoder(**seanet_kwargs)
|
| 203 |
+
decoder = audiocraft.modules.SEANetDecoder(**seanet_kwargs)
|
| 204 |
+
quantizer = qt.ResidualVectorQuantizer(dimension=32, bins=400, n_q=4)
|
| 205 |
+
init_x = torch.randn(8, 32, 128)
|
| 206 |
+
quantizer(init_x, 1) # initialize kmeans etc.
|
| 207 |
+
compression_model = EncodecModel(
|
| 208 |
+
encoder, decoder, quantizer,
|
| 209 |
+
frame_rate=frame_rate, sample_rate=sample_rate, channels=1).to(device)
|
| 210 |
+
return compression_model.eval()
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def get_diffusion_model(cfg: omegaconf.DictConfig):
|
| 214 |
+
# TODO Find a way to infer the channels from dset
|
| 215 |
+
channels = cfg.channels
|
| 216 |
+
num_steps = cfg.schedule.num_steps
|
| 217 |
+
return DiffusionUnet(
|
| 218 |
+
chin=channels, num_steps=num_steps, **cfg.diffusion_unet)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def get_processor(cfg, sample_rate: int = 24000):
|
| 222 |
+
sample_processor = SampleProcessor()
|
| 223 |
+
if cfg.use:
|
| 224 |
+
kw = dict(cfg)
|
| 225 |
+
kw.pop('use')
|
| 226 |
+
kw.pop('name')
|
| 227 |
+
if cfg.name == "multi_band_processor":
|
| 228 |
+
sample_processor = MultiBandProcessor(sample_rate=sample_rate, **kw)
|
| 229 |
+
return sample_processor
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def get_debug_lm_model(device='cpu'):
|
| 233 |
+
"""Instantiate a debug LM to be used for unit tests."""
|
| 234 |
+
pattern = DelayedPatternProvider(n_q=4)
|
| 235 |
+
dim = 16
|
| 236 |
+
providers = {
|
| 237 |
+
'description': LUTConditioner(n_bins=128, dim=dim, output_dim=dim, tokenizer="whitespace"),
|
| 238 |
+
}
|
| 239 |
+
condition_provider = ConditioningProvider(providers)
|
| 240 |
+
fuser = ConditionFuser(
|
| 241 |
+
{'cross': ['description'], 'prepend': [],
|
| 242 |
+
'sum': [], 'input_interpolate': []})
|
| 243 |
+
lm = LMModel(
|
| 244 |
+
pattern, condition_provider, fuser,
|
| 245 |
+
n_q=4, card=400, dim=dim, num_heads=4, custom=True, num_layers=2,
|
| 246 |
+
cross_attention=True, causal=True)
|
| 247 |
+
return lm.to(device).eval()
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
def get_wrapped_compression_model(
|
| 251 |
+
compression_model: CompressionModel,
|
| 252 |
+
cfg: omegaconf.DictConfig) -> CompressionModel:
|
| 253 |
+
if hasattr(cfg, 'interleave_stereo_codebooks'):
|
| 254 |
+
if cfg.interleave_stereo_codebooks.use:
|
| 255 |
+
kwargs = dict_from_config(cfg.interleave_stereo_codebooks)
|
| 256 |
+
kwargs.pop('use')
|
| 257 |
+
compression_model = InterleaveStereoCompressionModel(compression_model, **kwargs)
|
| 258 |
+
if hasattr(cfg, 'compression_model_n_q'):
|
| 259 |
+
if cfg.compression_model_n_q is not None:
|
| 260 |
+
compression_model.set_num_codebooks(cfg.compression_model_n_q)
|
| 261 |
+
return compression_model
|
src/audiocraft/models/encodec.py
ADDED
|
@@ -0,0 +1,506 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Compression models or wrapper around existing models.
|
| 7 |
+
Also defines the main interface that a model must follow to be usable as an audio tokenizer.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from abc import ABC, abstractmethod
|
| 11 |
+
import logging
|
| 12 |
+
import math
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import typing as tp
|
| 15 |
+
|
| 16 |
+
from einops import rearrange
|
| 17 |
+
import numpy as np
|
| 18 |
+
import torch
|
| 19 |
+
from torch import nn
|
| 20 |
+
from transformers import EncodecModel as HFEncodecModel
|
| 21 |
+
|
| 22 |
+
from .. import quantization as qt
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger()
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class CompressionModel(ABC, nn.Module):
|
| 29 |
+
"""Base API for all compression models that aim at being used as audio tokenizers
|
| 30 |
+
with a language model.
|
| 31 |
+
"""
|
| 32 |
+
|
| 33 |
+
@abstractmethod
|
| 34 |
+
def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
|
| 35 |
+
...
|
| 36 |
+
|
| 37 |
+
@abstractmethod
|
| 38 |
+
def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 39 |
+
"""See `EncodecModel.encode`."""
|
| 40 |
+
...
|
| 41 |
+
|
| 42 |
+
@abstractmethod
|
| 43 |
+
def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
|
| 44 |
+
"""See `EncodecModel.decode`."""
|
| 45 |
+
...
|
| 46 |
+
|
| 47 |
+
@abstractmethod
|
| 48 |
+
def decode_latent(self, codes: torch.Tensor):
|
| 49 |
+
"""Decode from the discrete codes to continuous latent space."""
|
| 50 |
+
...
|
| 51 |
+
|
| 52 |
+
@property
|
| 53 |
+
@abstractmethod
|
| 54 |
+
def channels(self) -> int:
|
| 55 |
+
...
|
| 56 |
+
|
| 57 |
+
@property
|
| 58 |
+
@abstractmethod
|
| 59 |
+
def frame_rate(self) -> float:
|
| 60 |
+
...
|
| 61 |
+
|
| 62 |
+
@property
|
| 63 |
+
@abstractmethod
|
| 64 |
+
def sample_rate(self) -> int:
|
| 65 |
+
...
|
| 66 |
+
|
| 67 |
+
@property
|
| 68 |
+
@abstractmethod
|
| 69 |
+
def cardinality(self) -> int:
|
| 70 |
+
...
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
@abstractmethod
|
| 74 |
+
def num_codebooks(self) -> int:
|
| 75 |
+
...
|
| 76 |
+
|
| 77 |
+
@property
|
| 78 |
+
@abstractmethod
|
| 79 |
+
def total_codebooks(self) -> int:
|
| 80 |
+
...
|
| 81 |
+
|
| 82 |
+
@abstractmethod
|
| 83 |
+
def set_num_codebooks(self, n: int):
|
| 84 |
+
"""Set the active number of codebooks used by the quantizer."""
|
| 85 |
+
...
|
| 86 |
+
|
| 87 |
+
@staticmethod
|
| 88 |
+
def get_pretrained(
|
| 89 |
+
name: str, device: tp.Union[torch.device, str] = 'cpu'
|
| 90 |
+
) -> 'CompressionModel':
|
| 91 |
+
"""Instantiate a CompressionModel from a given pretrained model.
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
name (Path or str): name of the pretrained model. See after.
|
| 95 |
+
device (torch.device or str): Device on which the model is loaded.
|
| 96 |
+
|
| 97 |
+
Pretrained models:
|
| 98 |
+
- dac_44khz (https://github.com/descriptinc/descript-audio-codec)
|
| 99 |
+
- dac_24khz (same)
|
| 100 |
+
- facebook/encodec_24khz (https://huggingface.co/facebook/encodec_24khz)
|
| 101 |
+
- facebook/encodec_32khz (https://huggingface.co/facebook/encodec_32khz)
|
| 102 |
+
- your own model on Hugging Face. Export instructions to come...
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
from . import builders, loaders
|
| 106 |
+
model: CompressionModel
|
| 107 |
+
if name in ['dac_44khz', 'dac_24khz']:
|
| 108 |
+
model_type = name.split('_')[1]
|
| 109 |
+
logger.info("Getting pretrained compression model from DAC %s", model_type)
|
| 110 |
+
model = DAC(model_type)
|
| 111 |
+
elif name in ['debug_compression_model']:
|
| 112 |
+
logger.info("Getting pretrained compression model for debug")
|
| 113 |
+
model = builders.get_debug_compression_model()
|
| 114 |
+
elif Path(name).exists():
|
| 115 |
+
# We assume here if the path exists that it is in fact an AC checkpoint
|
| 116 |
+
# that was exported using `audiocraft.utils.export` functions.
|
| 117 |
+
model = loaders.load_compression_model(name, device=device)
|
| 118 |
+
else:
|
| 119 |
+
logger.info("Getting pretrained compression model from HF %s", name)
|
| 120 |
+
hf_model = HFEncodecModel.from_pretrained(name)
|
| 121 |
+
model = HFEncodecCompressionModel(hf_model).to(device)
|
| 122 |
+
return model.to(device).eval()
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class EncodecModel(CompressionModel):
|
| 126 |
+
"""Encodec model operating on the raw waveform.
|
| 127 |
+
|
| 128 |
+
Args:
|
| 129 |
+
encoder (nn.Module): Encoder network.
|
| 130 |
+
decoder (nn.Module): Decoder network.
|
| 131 |
+
quantizer (qt.BaseQuantizer): Quantizer network.
|
| 132 |
+
frame_rate (int): Frame rate for the latent representation.
|
| 133 |
+
sample_rate (int): Audio sample rate.
|
| 134 |
+
channels (int): Number of audio channels.
|
| 135 |
+
causal (bool): Whether to use a causal version of the model.
|
| 136 |
+
renormalize (bool): Whether to renormalize the audio before running the model.
|
| 137 |
+
"""
|
| 138 |
+
# we need assignment to override the property in the abstract class,
|
| 139 |
+
# I couldn't find a better way...
|
| 140 |
+
frame_rate: float = 0
|
| 141 |
+
sample_rate: int = 0
|
| 142 |
+
channels: int = 0
|
| 143 |
+
|
| 144 |
+
def __init__(self,
|
| 145 |
+
encoder: nn.Module,
|
| 146 |
+
decoder: nn.Module,
|
| 147 |
+
quantizer: qt.BaseQuantizer,
|
| 148 |
+
frame_rate: int,
|
| 149 |
+
sample_rate: int,
|
| 150 |
+
channels: int,
|
| 151 |
+
causal: bool = False,
|
| 152 |
+
renormalize: bool = False):
|
| 153 |
+
super().__init__()
|
| 154 |
+
self.encoder = encoder
|
| 155 |
+
self.decoder = decoder
|
| 156 |
+
self.quantizer = quantizer
|
| 157 |
+
self.frame_rate = frame_rate
|
| 158 |
+
self.sample_rate = sample_rate
|
| 159 |
+
self.channels = channels
|
| 160 |
+
self.renormalize = renormalize
|
| 161 |
+
self.causal = causal
|
| 162 |
+
if self.causal:
|
| 163 |
+
# we force disabling here to avoid handling linear overlap of segments
|
| 164 |
+
# as supported in original EnCodec codebase.
|
| 165 |
+
assert not self.renormalize, 'Causal model does not support renormalize'
|
| 166 |
+
|
| 167 |
+
@property
|
| 168 |
+
def total_codebooks(self):
|
| 169 |
+
"""Total number of quantizer codebooks available."""
|
| 170 |
+
return self.quantizer.total_codebooks
|
| 171 |
+
|
| 172 |
+
@property
|
| 173 |
+
def num_codebooks(self):
|
| 174 |
+
"""Active number of codebooks used by the quantizer."""
|
| 175 |
+
return self.quantizer.num_codebooks
|
| 176 |
+
|
| 177 |
+
def set_num_codebooks(self, n: int):
|
| 178 |
+
"""Set the active number of codebooks used by the quantizer."""
|
| 179 |
+
self.quantizer.set_num_codebooks(n)
|
| 180 |
+
|
| 181 |
+
@property
|
| 182 |
+
def cardinality(self):
|
| 183 |
+
"""Cardinality of each codebook."""
|
| 184 |
+
return self.quantizer.bins
|
| 185 |
+
|
| 186 |
+
def preprocess(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 187 |
+
scale: tp.Optional[torch.Tensor]
|
| 188 |
+
if self.renormalize:
|
| 189 |
+
mono = x.mean(dim=1, keepdim=True)
|
| 190 |
+
volume = mono.pow(2).mean(dim=2, keepdim=True).sqrt()
|
| 191 |
+
scale = 1e-8 + volume
|
| 192 |
+
x = x / scale
|
| 193 |
+
scale = scale.view(-1, 1)
|
| 194 |
+
else:
|
| 195 |
+
scale = None
|
| 196 |
+
return x, scale
|
| 197 |
+
|
| 198 |
+
def postprocess(self,
|
| 199 |
+
x: torch.Tensor,
|
| 200 |
+
scale: tp.Optional[torch.Tensor] = None) -> torch.Tensor:
|
| 201 |
+
if scale is not None:
|
| 202 |
+
assert self.renormalize
|
| 203 |
+
x = x * scale.view(-1, 1, 1)
|
| 204 |
+
return x
|
| 205 |
+
|
| 206 |
+
def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
|
| 207 |
+
assert x.dim() == 3
|
| 208 |
+
length = x.shape[-1]
|
| 209 |
+
x, scale = self.preprocess(x)
|
| 210 |
+
|
| 211 |
+
emb = self.encoder(x)
|
| 212 |
+
q_res = self.quantizer(emb, self.frame_rate)
|
| 213 |
+
out = self.decoder(q_res.x)
|
| 214 |
+
|
| 215 |
+
# remove extra padding added by the encoder and decoder
|
| 216 |
+
assert out.shape[-1] >= length, (out.shape[-1], length)
|
| 217 |
+
out = out[..., :length]
|
| 218 |
+
|
| 219 |
+
q_res.x = self.postprocess(out, scale)
|
| 220 |
+
|
| 221 |
+
return q_res
|
| 222 |
+
|
| 223 |
+
def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 224 |
+
"""Encode the given input tensor to quantized representation along with scale parameter.
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
x (torch.Tensor): Float tensor of shape [B, C, T]
|
| 228 |
+
|
| 229 |
+
Returns:
|
| 230 |
+
codes, scale (tuple of torch.Tensor, torch.Tensor): Tuple composed of:
|
| 231 |
+
codes: a float tensor of shape [B, K, T] with K the number of codebooks used and T the timestep.
|
| 232 |
+
scale: a float tensor containing the scale for audio renormalization.
|
| 233 |
+
"""
|
| 234 |
+
assert x.dim() == 3
|
| 235 |
+
x, scale = self.preprocess(x)
|
| 236 |
+
emb = self.encoder(x)
|
| 237 |
+
codes = self.quantizer.encode(emb)
|
| 238 |
+
return codes, scale
|
| 239 |
+
|
| 240 |
+
def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
|
| 241 |
+
"""Decode the given codes to a reconstructed representation, using the scale to perform
|
| 242 |
+
audio denormalization if needed.
|
| 243 |
+
|
| 244 |
+
Args:
|
| 245 |
+
codes (torch.Tensor): Int tensor of shape [B, K, T]
|
| 246 |
+
scale (torch.Tensor, optional): Float tensor containing the scale value.
|
| 247 |
+
|
| 248 |
+
Returns:
|
| 249 |
+
out (torch.Tensor): Float tensor of shape [B, C, T], the reconstructed audio.
|
| 250 |
+
"""
|
| 251 |
+
emb = self.decode_latent(codes)
|
| 252 |
+
out = self.decoder(emb)
|
| 253 |
+
out = self.postprocess(out, scale)
|
| 254 |
+
# out contains extra padding added by the encoder and decoder
|
| 255 |
+
return out
|
| 256 |
+
|
| 257 |
+
def decode_latent(self, codes: torch.Tensor):
|
| 258 |
+
"""Decode from the discrete codes to continuous latent space."""
|
| 259 |
+
return self.quantizer.decode(codes)
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
class DAC(CompressionModel):
|
| 263 |
+
def __init__(self, model_type: str = "44khz"):
|
| 264 |
+
super().__init__()
|
| 265 |
+
try:
|
| 266 |
+
import dac.utils
|
| 267 |
+
except ImportError:
|
| 268 |
+
raise RuntimeError("Could not import dac, make sure it is installed, "
|
| 269 |
+
"please run `pip install descript-audio-codec`")
|
| 270 |
+
self.model = dac.utils.load_model(model_type=model_type)
|
| 271 |
+
self.n_quantizers = self.total_codebooks
|
| 272 |
+
self.model.eval()
|
| 273 |
+
|
| 274 |
+
def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
|
| 275 |
+
# We don't support training with this.
|
| 276 |
+
raise NotImplementedError("Forward and training with DAC not supported.")
|
| 277 |
+
|
| 278 |
+
def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 279 |
+
codes = self.model.encode(x, self.n_quantizers)[1]
|
| 280 |
+
return codes[:, :self.n_quantizers], None
|
| 281 |
+
|
| 282 |
+
def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
|
| 283 |
+
assert scale is None
|
| 284 |
+
z_q = self.decode_latent(codes)
|
| 285 |
+
return self.model.decode(z_q)
|
| 286 |
+
|
| 287 |
+
def decode_latent(self, codes: torch.Tensor):
|
| 288 |
+
"""Decode from the discrete codes to continuous latent space."""
|
| 289 |
+
return self.model.quantizer.from_codes(codes)[0]
|
| 290 |
+
|
| 291 |
+
@property
|
| 292 |
+
def channels(self) -> int:
|
| 293 |
+
return 1
|
| 294 |
+
|
| 295 |
+
@property
|
| 296 |
+
def frame_rate(self) -> float:
|
| 297 |
+
return self.model.sample_rate / self.model.hop_length
|
| 298 |
+
|
| 299 |
+
@property
|
| 300 |
+
def sample_rate(self) -> int:
|
| 301 |
+
return self.model.sample_rate
|
| 302 |
+
|
| 303 |
+
@property
|
| 304 |
+
def cardinality(self) -> int:
|
| 305 |
+
return self.model.codebook_size
|
| 306 |
+
|
| 307 |
+
@property
|
| 308 |
+
def num_codebooks(self) -> int:
|
| 309 |
+
return self.n_quantizers
|
| 310 |
+
|
| 311 |
+
@property
|
| 312 |
+
def total_codebooks(self) -> int:
|
| 313 |
+
return self.model.n_codebooks
|
| 314 |
+
|
| 315 |
+
def set_num_codebooks(self, n: int):
|
| 316 |
+
"""Set the active number of codebooks used by the quantizer.
|
| 317 |
+
"""
|
| 318 |
+
assert n >= 1
|
| 319 |
+
assert n <= self.total_codebooks
|
| 320 |
+
self.n_quantizers = n
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
class HFEncodecCompressionModel(CompressionModel):
|
| 324 |
+
"""Wrapper around HuggingFace Encodec.
|
| 325 |
+
"""
|
| 326 |
+
def __init__(self, model: HFEncodecModel):
|
| 327 |
+
super().__init__()
|
| 328 |
+
self.model = model
|
| 329 |
+
bws = self.model.config.target_bandwidths
|
| 330 |
+
num_codebooks = [
|
| 331 |
+
bw * 1000 / (self.frame_rate * math.log2(self.cardinality))
|
| 332 |
+
for bw in bws
|
| 333 |
+
]
|
| 334 |
+
deltas = [nc - int(nc) for nc in num_codebooks]
|
| 335 |
+
# Checking we didn't do some bad maths and we indeed have integers!
|
| 336 |
+
assert all(deltas) <= 1e-3, deltas
|
| 337 |
+
self.possible_num_codebooks = [int(nc) for nc in num_codebooks]
|
| 338 |
+
self.set_num_codebooks(max(self.possible_num_codebooks))
|
| 339 |
+
|
| 340 |
+
def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
|
| 341 |
+
# We don't support training with this.
|
| 342 |
+
raise NotImplementedError("Forward and training with HF EncodecModel not supported.")
|
| 343 |
+
|
| 344 |
+
def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 345 |
+
bandwidth_index = self.possible_num_codebooks.index(self.num_codebooks)
|
| 346 |
+
bandwidth = self.model.config.target_bandwidths[bandwidth_index]
|
| 347 |
+
res = self.model.encode(x, None, bandwidth)
|
| 348 |
+
assert len(res[0]) == 1
|
| 349 |
+
assert len(res[1]) == 1
|
| 350 |
+
return res[0][0], res[1][0]
|
| 351 |
+
|
| 352 |
+
def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
|
| 353 |
+
if scale is None:
|
| 354 |
+
scales = [None] # type: ignore
|
| 355 |
+
else:
|
| 356 |
+
scales = scale # type: ignore
|
| 357 |
+
res = self.model.decode(codes[None], scales)
|
| 358 |
+
return res[0]
|
| 359 |
+
|
| 360 |
+
def decode_latent(self, codes: torch.Tensor):
|
| 361 |
+
"""Decode from the discrete codes to continuous latent space."""
|
| 362 |
+
return self.model.quantizer.decode(codes.transpose(0, 1))
|
| 363 |
+
|
| 364 |
+
@property
|
| 365 |
+
def channels(self) -> int:
|
| 366 |
+
return self.model.config.audio_channels
|
| 367 |
+
|
| 368 |
+
@property
|
| 369 |
+
def frame_rate(self) -> float:
|
| 370 |
+
hop_length = int(np.prod(self.model.config.upsampling_ratios))
|
| 371 |
+
return self.sample_rate / hop_length
|
| 372 |
+
|
| 373 |
+
@property
|
| 374 |
+
def sample_rate(self) -> int:
|
| 375 |
+
return self.model.config.sampling_rate
|
| 376 |
+
|
| 377 |
+
@property
|
| 378 |
+
def cardinality(self) -> int:
|
| 379 |
+
return self.model.config.codebook_size
|
| 380 |
+
|
| 381 |
+
@property
|
| 382 |
+
def num_codebooks(self) -> int:
|
| 383 |
+
return self._num_codebooks
|
| 384 |
+
|
| 385 |
+
@property
|
| 386 |
+
def total_codebooks(self) -> int:
|
| 387 |
+
return max(self.possible_num_codebooks)
|
| 388 |
+
|
| 389 |
+
def set_num_codebooks(self, n: int):
|
| 390 |
+
"""Set the active number of codebooks used by the quantizer.
|
| 391 |
+
"""
|
| 392 |
+
if n not in self.possible_num_codebooks:
|
| 393 |
+
raise ValueError(f"Allowed values for num codebooks: {self.possible_num_codebooks}")
|
| 394 |
+
self._num_codebooks = n
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
class InterleaveStereoCompressionModel(CompressionModel):
|
| 398 |
+
"""Wraps a CompressionModel to support stereo inputs. The wrapped model
|
| 399 |
+
will be applied independently to the left and right channels, and both codebooks
|
| 400 |
+
will be interleaved. If the wrapped model returns a representation `[B, K ,T]` per
|
| 401 |
+
channel, then the output will be `[B, K * 2, T]` or `[B, K, T * 2]` depending on
|
| 402 |
+
`per_timestep`.
|
| 403 |
+
|
| 404 |
+
Args:
|
| 405 |
+
model (CompressionModel): Compression model to wrap.
|
| 406 |
+
per_timestep (bool): Whether to interleave on the timestep dimension
|
| 407 |
+
or on the codebooks dimension.
|
| 408 |
+
"""
|
| 409 |
+
def __init__(self, model: CompressionModel, per_timestep: bool = False):
|
| 410 |
+
super().__init__()
|
| 411 |
+
self.model = model
|
| 412 |
+
self.per_timestep = per_timestep
|
| 413 |
+
assert self.model.channels == 1, "Wrapped model is expected to be for monophonic audio"
|
| 414 |
+
|
| 415 |
+
@property
|
| 416 |
+
def total_codebooks(self):
|
| 417 |
+
return self.model.total_codebooks
|
| 418 |
+
|
| 419 |
+
@property
|
| 420 |
+
def num_codebooks(self):
|
| 421 |
+
"""Active number of codebooks used by the quantizer.
|
| 422 |
+
|
| 423 |
+
..Warning:: this reports the number of codebooks after the interleaving
|
| 424 |
+
of the codebooks!
|
| 425 |
+
"""
|
| 426 |
+
return self.model.num_codebooks if self.per_timestep else self.model.num_codebooks * 2
|
| 427 |
+
|
| 428 |
+
def set_num_codebooks(self, n: int):
|
| 429 |
+
"""Set the active number of codebooks used by the quantizer.
|
| 430 |
+
|
| 431 |
+
..Warning:: this sets the number of codebooks before the interleaving!
|
| 432 |
+
"""
|
| 433 |
+
self.model.set_num_codebooks(n)
|
| 434 |
+
|
| 435 |
+
@property
|
| 436 |
+
def num_virtual_steps(self) -> float:
|
| 437 |
+
"""Return the number of virtual steps, e.g. one real step
|
| 438 |
+
will be split into that many steps.
|
| 439 |
+
"""
|
| 440 |
+
return 2 if self.per_timestep else 1
|
| 441 |
+
|
| 442 |
+
@property
|
| 443 |
+
def frame_rate(self) -> float:
|
| 444 |
+
return self.model.frame_rate * self.num_virtual_steps
|
| 445 |
+
|
| 446 |
+
@property
|
| 447 |
+
def sample_rate(self) -> int:
|
| 448 |
+
return self.model.sample_rate
|
| 449 |
+
|
| 450 |
+
@property
|
| 451 |
+
def channels(self) -> int:
|
| 452 |
+
return 2
|
| 453 |
+
|
| 454 |
+
@property
|
| 455 |
+
def cardinality(self):
|
| 456 |
+
"""Cardinality of each codebook.
|
| 457 |
+
"""
|
| 458 |
+
return self.model.cardinality
|
| 459 |
+
|
| 460 |
+
def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
|
| 461 |
+
raise NotImplementedError("Not supported, use encode and decode.")
|
| 462 |
+
|
| 463 |
+
def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 464 |
+
B, C, T = x.shape
|
| 465 |
+
assert C == self.channels, f"Expecting stereo audio but audio num channels is {C}"
|
| 466 |
+
|
| 467 |
+
indices_c0, scales_c0 = self.model.encode(x[:, 0, ...].unsqueeze(1))
|
| 468 |
+
indices_c1, scales_c1 = self.model.encode(x[:, 1, ...].unsqueeze(1))
|
| 469 |
+
indices = torch.stack([indices_c0, indices_c1], dim=0)
|
| 470 |
+
scales: tp.Optional[torch.Tensor] = None
|
| 471 |
+
if scales_c0 is not None and scales_c1 is not None:
|
| 472 |
+
scales = torch.stack([scales_c0, scales_c1], dim=1)
|
| 473 |
+
|
| 474 |
+
if self.per_timestep:
|
| 475 |
+
indices = rearrange(indices, 'c b k t -> b k (t c)', c=2)
|
| 476 |
+
else:
|
| 477 |
+
indices = rearrange(indices, 'c b k t -> b (k c) t', c=2)
|
| 478 |
+
|
| 479 |
+
return (indices, scales)
|
| 480 |
+
|
| 481 |
+
def get_left_right_codes(self, codes: torch.Tensor) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 482 |
+
if self.per_timestep:
|
| 483 |
+
codes = rearrange(codes, 'b k (t c) -> c b k t', c=2)
|
| 484 |
+
else:
|
| 485 |
+
codes = rearrange(codes, 'b (k c) t -> c b k t', c=2)
|
| 486 |
+
return codes[0], codes[1]
|
| 487 |
+
|
| 488 |
+
def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
|
| 489 |
+
B, K, T = codes.shape
|
| 490 |
+
assert T % self.num_virtual_steps == 0, "Provided codes' number of timesteps does not match"
|
| 491 |
+
assert K == self.num_codebooks, "Provided codes' number of codebooks does not match"
|
| 492 |
+
|
| 493 |
+
scale_c0, scale_c1 = None, None
|
| 494 |
+
if scale is not None:
|
| 495 |
+
assert scale.size(0) == B and scale.size(1) == 2, f"Scale has unexpected shape: {scale.shape}"
|
| 496 |
+
scale_c0 = scale[0, ...]
|
| 497 |
+
scale_c1 = scale[1, ...]
|
| 498 |
+
|
| 499 |
+
codes_c0, codes_c1 = self.get_left_right_codes(codes)
|
| 500 |
+
audio_c0 = self.model.decode(codes_c0, scale_c0)
|
| 501 |
+
audio_c1 = self.model.decode(codes_c1, scale_c1)
|
| 502 |
+
return torch.cat([audio_c0, audio_c1], dim=1)
|
| 503 |
+
|
| 504 |
+
def decode_latent(self, codes: torch.Tensor):
|
| 505 |
+
"""Decode from the discrete codes to continuous latent space."""
|
| 506 |
+
raise NotImplementedError("Not supported by interleaved stereo wrapped models.")
|
src/audiocraft/models/genmodel.py
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Base implementation for audio generative models. This base implementation
|
| 9 |
+
combines all the required components to run inference with pretrained audio
|
| 10 |
+
generative models. It can be easily inherited by downstream model classes to
|
| 11 |
+
provide easy access to the generation API.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from abc import ABC, abstractmethod
|
| 15 |
+
import typing as tp
|
| 16 |
+
|
| 17 |
+
import omegaconf
|
| 18 |
+
import torch
|
| 19 |
+
|
| 20 |
+
from .encodec import CompressionModel
|
| 21 |
+
from .lm import LMModel
|
| 22 |
+
from .builders import get_wrapped_compression_model
|
| 23 |
+
from ..data.audio_utils import convert_audio
|
| 24 |
+
from ..modules.conditioners import ConditioningAttributes
|
| 25 |
+
from ..utils.autocast import TorchAutocast
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
class BaseGenModel(ABC):
|
| 29 |
+
"""Base generative model with convenient generation API.
|
| 30 |
+
|
| 31 |
+
Args:
|
| 32 |
+
name (str): name of the model.
|
| 33 |
+
compression_model (CompressionModel): Compression model
|
| 34 |
+
used to map audio to invertible discrete representations.
|
| 35 |
+
lm (LMModel): Language model over discrete representations.
|
| 36 |
+
max_duration (float, optional): maximum duration the model can produce,
|
| 37 |
+
otherwise, inferred from the training params.
|
| 38 |
+
"""
|
| 39 |
+
def __init__(self, name: str, compression_model: CompressionModel, lm: LMModel,
|
| 40 |
+
max_duration: tp.Optional[float] = None):
|
| 41 |
+
self.name = name
|
| 42 |
+
self.compression_model = compression_model
|
| 43 |
+
self.lm = lm
|
| 44 |
+
self.cfg: tp.Optional[omegaconf.DictConfig] = None
|
| 45 |
+
# Just to be safe, let's put everything in eval mode.
|
| 46 |
+
self.compression_model.eval()
|
| 47 |
+
self.lm.eval()
|
| 48 |
+
|
| 49 |
+
if hasattr(lm, 'cfg'):
|
| 50 |
+
cfg = lm.cfg
|
| 51 |
+
assert isinstance(cfg, omegaconf.DictConfig)
|
| 52 |
+
self.cfg = cfg
|
| 53 |
+
|
| 54 |
+
if self.cfg is not None:
|
| 55 |
+
self.compression_model = get_wrapped_compression_model(self.compression_model, self.cfg)
|
| 56 |
+
|
| 57 |
+
if max_duration is None:
|
| 58 |
+
if self.cfg is not None:
|
| 59 |
+
max_duration = lm.cfg.dataset.segment_duration # type: ignore
|
| 60 |
+
else:
|
| 61 |
+
raise ValueError("You must provide max_duration when building directly your GenModel")
|
| 62 |
+
assert max_duration is not None
|
| 63 |
+
|
| 64 |
+
self.max_duration: float = max_duration
|
| 65 |
+
self.duration = self.max_duration
|
| 66 |
+
|
| 67 |
+
# self.extend_stride is the length of audio extension when generating samples longer
|
| 68 |
+
# than self.max_duration. NOTE: the derived class must set self.extend_stride to a
|
| 69 |
+
# positive float value when generating with self.duration > self.max_duration.
|
| 70 |
+
self.extend_stride: tp.Optional[float] = None
|
| 71 |
+
self.device = next(iter(lm.parameters())).device
|
| 72 |
+
self.generation_params: dict = {}
|
| 73 |
+
self._progress_callback: tp.Optional[tp.Callable[[int, int], None]] = None
|
| 74 |
+
if self.device.type == 'cpu':
|
| 75 |
+
self.autocast = TorchAutocast(enabled=False)
|
| 76 |
+
else:
|
| 77 |
+
self.autocast = TorchAutocast(
|
| 78 |
+
enabled=True, device_type=self.device.type, dtype=torch.float16)
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def frame_rate(self) -> float:
|
| 82 |
+
"""Roughly the number of AR steps per seconds."""
|
| 83 |
+
return self.compression_model.frame_rate
|
| 84 |
+
|
| 85 |
+
@property
|
| 86 |
+
def sample_rate(self) -> int:
|
| 87 |
+
"""Sample rate of the generated audio."""
|
| 88 |
+
return self.compression_model.sample_rate
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def audio_channels(self) -> int:
|
| 92 |
+
"""Audio channels of the generated audio."""
|
| 93 |
+
return self.compression_model.channels
|
| 94 |
+
|
| 95 |
+
def set_custom_progress_callback(self, progress_callback: tp.Optional[tp.Callable[[int, int], None]] = None):
|
| 96 |
+
"""Override the default progress callback."""
|
| 97 |
+
self._progress_callback = progress_callback
|
| 98 |
+
|
| 99 |
+
@abstractmethod
|
| 100 |
+
def set_generation_params(self, *args, **kwargs):
|
| 101 |
+
"""Set the generation parameters."""
|
| 102 |
+
raise NotImplementedError("No base implementation for setting generation params.")
|
| 103 |
+
|
| 104 |
+
@staticmethod
|
| 105 |
+
@abstractmethod
|
| 106 |
+
def get_pretrained(name: str, device=None):
|
| 107 |
+
raise NotImplementedError("No base implementation for getting pretrained model")
|
| 108 |
+
|
| 109 |
+
@torch.no_grad()
|
| 110 |
+
def _prepare_tokens_and_attributes(
|
| 111 |
+
self,
|
| 112 |
+
descriptions: tp.Sequence[tp.Optional[str]],
|
| 113 |
+
prompt: tp.Optional[torch.Tensor],
|
| 114 |
+
) -> tp.Tuple[tp.List[ConditioningAttributes], tp.Optional[torch.Tensor]]:
|
| 115 |
+
"""Prepare model inputs.
|
| 116 |
+
|
| 117 |
+
Args:
|
| 118 |
+
descriptions (list of str): A list of strings used as text conditioning.
|
| 119 |
+
prompt (torch.Tensor): A batch of waveforms used for continuation.
|
| 120 |
+
"""
|
| 121 |
+
attributes = [
|
| 122 |
+
ConditioningAttributes(text={'description': description})
|
| 123 |
+
for description in descriptions]
|
| 124 |
+
|
| 125 |
+
if prompt is not None:
|
| 126 |
+
if descriptions is not None:
|
| 127 |
+
assert len(descriptions) == len(prompt), "Prompt and nb. descriptions doesn't match"
|
| 128 |
+
prompt = prompt.to(self.device)
|
| 129 |
+
prompt_tokens, scale = self.compression_model.encode(prompt)
|
| 130 |
+
assert scale is None
|
| 131 |
+
else:
|
| 132 |
+
prompt_tokens = None
|
| 133 |
+
return attributes, prompt_tokens
|
| 134 |
+
|
| 135 |
+
def generate_unconditional(self, num_samples: int, progress: bool = False,
|
| 136 |
+
return_tokens: bool = False) -> tp.Union[torch.Tensor,
|
| 137 |
+
tp.Tuple[torch.Tensor, torch.Tensor]]:
|
| 138 |
+
"""Generate samples in an unconditional manner.
|
| 139 |
+
|
| 140 |
+
Args:
|
| 141 |
+
num_samples (int): Number of samples to be generated.
|
| 142 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 143 |
+
"""
|
| 144 |
+
descriptions: tp.List[tp.Optional[str]] = [None] * num_samples
|
| 145 |
+
attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, None)
|
| 146 |
+
tokens = self._generate_tokens(attributes, prompt_tokens, progress)
|
| 147 |
+
if return_tokens:
|
| 148 |
+
return self.generate_audio(tokens), tokens
|
| 149 |
+
return self.generate_audio(tokens)
|
| 150 |
+
|
| 151 |
+
def generate(self, descriptions: tp.List[str], progress: bool = False, return_tokens: bool = False) \
|
| 152 |
+
-> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, torch.Tensor]]:
|
| 153 |
+
"""Generate samples conditioned on text.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
descriptions (list of str): A list of strings used as text conditioning.
|
| 157 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 158 |
+
"""
|
| 159 |
+
attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, None)
|
| 160 |
+
assert prompt_tokens is None
|
| 161 |
+
tokens = self._generate_tokens(attributes, prompt_tokens, progress)
|
| 162 |
+
if return_tokens:
|
| 163 |
+
return self.generate_audio(tokens), tokens
|
| 164 |
+
return self.generate_audio(tokens)
|
| 165 |
+
|
| 166 |
+
def generate_continuation(self, prompt: torch.Tensor, prompt_sample_rate: int,
|
| 167 |
+
descriptions: tp.Optional[tp.List[tp.Optional[str]]] = None,
|
| 168 |
+
progress: bool = False, return_tokens: bool = False) \
|
| 169 |
+
-> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, torch.Tensor]]:
|
| 170 |
+
"""Generate samples conditioned on audio prompts and an optional text description.
|
| 171 |
+
|
| 172 |
+
Args:
|
| 173 |
+
prompt (torch.Tensor): A batch of waveforms used for continuation.
|
| 174 |
+
Prompt should be [B, C, T], or [C, T] if only one sample is generated.
|
| 175 |
+
prompt_sample_rate (int): Sampling rate of the given audio waveforms.
|
| 176 |
+
descriptions (list of str, optional): A list of strings used as text conditioning. Defaults to None.
|
| 177 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 178 |
+
"""
|
| 179 |
+
if prompt.dim() == 2:
|
| 180 |
+
prompt = prompt[None]
|
| 181 |
+
if prompt.dim() != 3:
|
| 182 |
+
raise ValueError("prompt should have 3 dimensions: [B, C, T] (C = 1).")
|
| 183 |
+
prompt = convert_audio(prompt, prompt_sample_rate, self.sample_rate, self.audio_channels)
|
| 184 |
+
if descriptions is None:
|
| 185 |
+
descriptions = [None] * len(prompt)
|
| 186 |
+
attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, prompt)
|
| 187 |
+
assert prompt_tokens is not None
|
| 188 |
+
tokens = self._generate_tokens(attributes, prompt_tokens, progress)
|
| 189 |
+
if return_tokens:
|
| 190 |
+
return self.generate_audio(tokens), tokens
|
| 191 |
+
return self.generate_audio(tokens)
|
| 192 |
+
|
| 193 |
+
def _generate_tokens(self, attributes: tp.List[ConditioningAttributes],
|
| 194 |
+
prompt_tokens: tp.Optional[torch.Tensor], progress: bool = False) -> torch.Tensor:
|
| 195 |
+
"""Generate discrete audio tokens given audio prompt and/or conditions.
|
| 196 |
+
|
| 197 |
+
Args:
|
| 198 |
+
attributes (list of ConditioningAttributes): Conditions used for generation (here text).
|
| 199 |
+
prompt_tokens (torch.Tensor, optional): Audio prompt used for continuation.
|
| 200 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 201 |
+
Returns:
|
| 202 |
+
torch.Tensor: Generated audio, of shape [B, C, T], T is defined by the generation params.
|
| 203 |
+
"""
|
| 204 |
+
total_gen_len = int(self.duration * self.frame_rate)
|
| 205 |
+
max_prompt_len = int(min(self.duration, self.max_duration) * self.frame_rate)
|
| 206 |
+
current_gen_offset: int = 0
|
| 207 |
+
|
| 208 |
+
def _progress_callback(generated_tokens: int, tokens_to_generate: int):
|
| 209 |
+
generated_tokens += current_gen_offset
|
| 210 |
+
if self._progress_callback is not None:
|
| 211 |
+
# Note that total_gen_len might be quite wrong depending on the
|
| 212 |
+
# codebook pattern used, but with delay it is almost accurate.
|
| 213 |
+
self._progress_callback(generated_tokens, tokens_to_generate)
|
| 214 |
+
else:
|
| 215 |
+
print(f'{generated_tokens: 6d} / {tokens_to_generate: 6d}', end='\r')
|
| 216 |
+
|
| 217 |
+
if prompt_tokens is not None:
|
| 218 |
+
assert max_prompt_len >= prompt_tokens.shape[-1], \
|
| 219 |
+
"Prompt is longer than audio to generate"
|
| 220 |
+
|
| 221 |
+
callback = None
|
| 222 |
+
if progress:
|
| 223 |
+
callback = _progress_callback
|
| 224 |
+
|
| 225 |
+
if self.duration <= self.max_duration:
|
| 226 |
+
# generate by sampling from LM, simple case.
|
| 227 |
+
with self.autocast:
|
| 228 |
+
gen_tokens = self.lm.generate(
|
| 229 |
+
prompt_tokens, attributes,
|
| 230 |
+
callback=callback, max_gen_len=total_gen_len, **self.generation_params)
|
| 231 |
+
|
| 232 |
+
else:
|
| 233 |
+
assert self.extend_stride is not None, "Stride should be defined to generate beyond max_duration"
|
| 234 |
+
assert self.extend_stride < self.max_duration, "Cannot stride by more than max generation duration."
|
| 235 |
+
all_tokens = []
|
| 236 |
+
if prompt_tokens is None:
|
| 237 |
+
prompt_length = 0
|
| 238 |
+
else:
|
| 239 |
+
all_tokens.append(prompt_tokens)
|
| 240 |
+
prompt_length = prompt_tokens.shape[-1]
|
| 241 |
+
|
| 242 |
+
stride_tokens = int(self.frame_rate * self.extend_stride)
|
| 243 |
+
while current_gen_offset + prompt_length < total_gen_len:
|
| 244 |
+
time_offset = current_gen_offset / self.frame_rate
|
| 245 |
+
chunk_duration = min(self.duration - time_offset, self.max_duration)
|
| 246 |
+
max_gen_len = int(chunk_duration * self.frame_rate)
|
| 247 |
+
with self.autocast:
|
| 248 |
+
gen_tokens = self.lm.generate(
|
| 249 |
+
prompt_tokens, attributes,
|
| 250 |
+
callback=callback, max_gen_len=max_gen_len, **self.generation_params)
|
| 251 |
+
if prompt_tokens is None:
|
| 252 |
+
all_tokens.append(gen_tokens)
|
| 253 |
+
else:
|
| 254 |
+
all_tokens.append(gen_tokens[:, :, prompt_tokens.shape[-1]:])
|
| 255 |
+
prompt_tokens = gen_tokens[:, :, stride_tokens:]
|
| 256 |
+
prompt_length = prompt_tokens.shape[-1]
|
| 257 |
+
current_gen_offset += stride_tokens
|
| 258 |
+
|
| 259 |
+
gen_tokens = torch.cat(all_tokens, dim=-1)
|
| 260 |
+
return gen_tokens
|
| 261 |
+
|
| 262 |
+
def generate_audio(self, gen_tokens: torch.Tensor) -> torch.Tensor:
|
| 263 |
+
"""Generate Audio from tokens."""
|
| 264 |
+
assert gen_tokens.dim() == 3
|
| 265 |
+
with torch.no_grad():
|
| 266 |
+
gen_audio = self.compression_model.decode(gen_tokens, None)
|
| 267 |
+
return gen_audio
|
src/audiocraft/models/lm.py
ADDED
|
@@ -0,0 +1,547 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
from dataclasses import dataclass
|
| 8 |
+
from functools import partial
|
| 9 |
+
import logging
|
| 10 |
+
import math
|
| 11 |
+
import typing as tp
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
from torch import nn
|
| 15 |
+
|
| 16 |
+
from ..utils import utils
|
| 17 |
+
from ..modules.streaming import StreamingModule, State
|
| 18 |
+
from ..modules.transformer import StreamingTransformer, create_norm_fn
|
| 19 |
+
from ..modules.conditioners import (
|
| 20 |
+
ConditionFuser,
|
| 21 |
+
ClassifierFreeGuidanceDropout,
|
| 22 |
+
AttributeDropout,
|
| 23 |
+
ConditioningProvider,
|
| 24 |
+
ConditioningAttributes,
|
| 25 |
+
ConditionType,
|
| 26 |
+
)
|
| 27 |
+
from ..modules.codebooks_patterns import CodebooksPatternProvider
|
| 28 |
+
from ..modules.activations import get_activation_fn
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
logger = logging.getLogger(__name__)
|
| 32 |
+
ConditionTensors = tp.Dict[str, ConditionType]
|
| 33 |
+
CFGConditions = tp.Union[ConditionTensors, tp.Tuple[ConditionTensors, ConditionTensors]]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def get_init_fn(method: str, input_dim: int, init_depth: tp.Optional[int] = None):
|
| 37 |
+
"""LM layer initialization.
|
| 38 |
+
Inspired from xlformers: https://github.com/fairinternal/xlformers
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
method (str): Method name for init function. Valid options are:
|
| 42 |
+
'gaussian', 'uniform'.
|
| 43 |
+
input_dim (int): Input dimension of the initialized module.
|
| 44 |
+
init_depth (int, optional): Optional init depth value used to rescale
|
| 45 |
+
the standard deviation if defined.
|
| 46 |
+
"""
|
| 47 |
+
# Compute std
|
| 48 |
+
std = 1 / math.sqrt(input_dim)
|
| 49 |
+
# Rescale with depth
|
| 50 |
+
if init_depth is not None:
|
| 51 |
+
std = std / math.sqrt(2 * init_depth)
|
| 52 |
+
|
| 53 |
+
if method == 'gaussian':
|
| 54 |
+
return partial(
|
| 55 |
+
torch.nn.init.trunc_normal_, mean=0.0, std=std, a=-3 * std, b=3 * std
|
| 56 |
+
)
|
| 57 |
+
elif method == 'uniform':
|
| 58 |
+
bound = math.sqrt(3) * std # ensure the standard deviation is `std`
|
| 59 |
+
return partial(torch.nn.init.uniform_, a=-bound, b=bound)
|
| 60 |
+
else:
|
| 61 |
+
raise ValueError("Unsupported layer initialization method")
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def init_layer(m: nn.Module,
|
| 65 |
+
method: str,
|
| 66 |
+
init_depth: tp.Optional[int] = None,
|
| 67 |
+
zero_bias_init: bool = False):
|
| 68 |
+
"""Wrapper around ``get_init_fn`` for proper initialization of LM modules.
|
| 69 |
+
|
| 70 |
+
Args:
|
| 71 |
+
m (nn.Module): Module to initialize.
|
| 72 |
+
method (str): Method name for the init function.
|
| 73 |
+
init_depth (int, optional): Optional init depth value used to rescale
|
| 74 |
+
the standard deviation if defined.
|
| 75 |
+
zero_bias_init (bool): Whether to initialize the bias to 0 or not.
|
| 76 |
+
"""
|
| 77 |
+
if isinstance(m, nn.Linear):
|
| 78 |
+
init_fn = get_init_fn(method, m.in_features, init_depth=init_depth)
|
| 79 |
+
if m.weight.device.type == 'cpu' and m.weight.dtype == torch.float16:
|
| 80 |
+
weight = m.weight.float()
|
| 81 |
+
init_fn(weight)
|
| 82 |
+
m.weight.data[:] = weight.half()
|
| 83 |
+
else:
|
| 84 |
+
init_fn(m.weight)
|
| 85 |
+
if zero_bias_init and m.bias is not None:
|
| 86 |
+
nn.init.constant_(m.bias, 0)
|
| 87 |
+
elif isinstance(m, nn.Embedding):
|
| 88 |
+
init_fn = get_init_fn(method, m.embedding_dim, init_depth=None)
|
| 89 |
+
if m.weight.device.type == 'cpu' and m.weight.dtype == torch.float16:
|
| 90 |
+
weight = m.weight.float()
|
| 91 |
+
init_fn(weight)
|
| 92 |
+
m.weight.data[:] = weight.half()
|
| 93 |
+
else:
|
| 94 |
+
init_fn(m.weight)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
class ScaledEmbedding(nn.Embedding):
|
| 98 |
+
"""Boost learning rate for embeddings (with `scale`).
|
| 99 |
+
"""
|
| 100 |
+
def __init__(self, *args, lr=None, **kwargs):
|
| 101 |
+
super().__init__(*args, **kwargs)
|
| 102 |
+
self.lr = lr
|
| 103 |
+
|
| 104 |
+
def make_optim_group(self):
|
| 105 |
+
group = {"params": list(self.parameters())}
|
| 106 |
+
if self.lr is not None:
|
| 107 |
+
group["lr"] = self.lr
|
| 108 |
+
return group
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
@dataclass
|
| 112 |
+
class LMOutput:
|
| 113 |
+
# The logits are already re-aligned with the input codes
|
| 114 |
+
# hence no extra shift is required, e.g. when computing CE
|
| 115 |
+
logits: torch.Tensor # [B, K, T, card]
|
| 116 |
+
mask: torch.Tensor # [B, K, T]
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class LMModel(StreamingModule):
|
| 120 |
+
"""Transformer-based language model on multiple streams of codes.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
pattern_provider (CodebooksPatternProvider): Pattern provider for codebook interleaving.
|
| 124 |
+
condition_provider (MusicConditioningProvider): Conditioning provider from metadata.
|
| 125 |
+
fuser (ConditionFuser): Fuser handling the fusing of conditions with language model input.
|
| 126 |
+
n_q (int): Number of parallel streams to model.
|
| 127 |
+
card (int): Cardinality, vocabulary size.
|
| 128 |
+
dim (int): Dimension of the transformer encoder.
|
| 129 |
+
num_heads (int): Number of heads for the transformer encoder.
|
| 130 |
+
hidden_scale (int): Scale for hidden feed forward dimension of the transformer encoder.
|
| 131 |
+
norm (str): Normalization method.
|
| 132 |
+
norm_first (bool): Use pre-norm instead of post-norm.
|
| 133 |
+
emb_lr (float, optional): Embedding-specific learning rate.
|
| 134 |
+
bias_proj (bool): Use bias for output projections.
|
| 135 |
+
weight_init (str, optional): Method for weight initialization.
|
| 136 |
+
depthwise_init (str, optional): Method for depthwise weight initialization.
|
| 137 |
+
zero_bias_init (bool): If true and bias in Linears, initialize bias to zeros.
|
| 138 |
+
cfg_dropout (float): Classifier-free guidance dropout.
|
| 139 |
+
cfg_coef (float): Classifier-free guidance coefficient.
|
| 140 |
+
attribute_dropout (dict): Attribute dropout probabilities.
|
| 141 |
+
two_step_cfg (bool): Whether to run classifier free-guidance with 2 distinct steps.
|
| 142 |
+
**kwargs: Additional parameters for the transformer encoder.
|
| 143 |
+
"""
|
| 144 |
+
def __init__(self, pattern_provider: CodebooksPatternProvider, condition_provider: ConditioningProvider,
|
| 145 |
+
fuser: ConditionFuser, n_q: int = 8, card: int = 1024, dim: int = 128, num_heads: int = 8,
|
| 146 |
+
hidden_scale: int = 4, norm: str = 'layer_norm', norm_first: bool = False,
|
| 147 |
+
emb_lr: tp.Optional[float] = None, bias_proj: bool = True,
|
| 148 |
+
weight_init: tp.Optional[str] = None, depthwise_init: tp.Optional[str] = None,
|
| 149 |
+
zero_bias_init: bool = False, cfg_dropout: float = 0, cfg_coef: float = 1.0,
|
| 150 |
+
attribute_dropout: tp.Dict[str, tp.Dict[str, float]] = {}, two_step_cfg: bool = False,
|
| 151 |
+
**kwargs):
|
| 152 |
+
super().__init__()
|
| 153 |
+
self.cfg_coef = cfg_coef
|
| 154 |
+
self.cfg_dropout = ClassifierFreeGuidanceDropout(p=cfg_dropout)
|
| 155 |
+
self.att_dropout = AttributeDropout(p=attribute_dropout)
|
| 156 |
+
self.condition_provider = condition_provider
|
| 157 |
+
self.fuser = fuser
|
| 158 |
+
self.card = card
|
| 159 |
+
embed_dim = self.card + 1
|
| 160 |
+
self.n_q = n_q
|
| 161 |
+
self.dim = dim
|
| 162 |
+
self.pattern_provider = pattern_provider
|
| 163 |
+
self.two_step_cfg = two_step_cfg
|
| 164 |
+
self.emb = nn.ModuleList([ScaledEmbedding(embed_dim, dim, lr=emb_lr) for _ in range(n_q)])
|
| 165 |
+
if 'activation' in kwargs:
|
| 166 |
+
kwargs['activation'] = get_activation_fn(kwargs['activation'])
|
| 167 |
+
self.transformer = StreamingTransformer(
|
| 168 |
+
d_model=dim, num_heads=num_heads, dim_feedforward=int(hidden_scale * dim),
|
| 169 |
+
norm=norm, norm_first=norm_first, **kwargs)
|
| 170 |
+
self.out_norm: tp.Optional[nn.Module] = None
|
| 171 |
+
if norm_first:
|
| 172 |
+
self.out_norm = create_norm_fn(norm, dim)
|
| 173 |
+
self.linears = nn.ModuleList([nn.Linear(dim, self.card, bias=bias_proj) for _ in range(n_q)])
|
| 174 |
+
self._init_weights(weight_init, depthwise_init, zero_bias_init)
|
| 175 |
+
self._fsdp: tp.Optional[nn.Module]
|
| 176 |
+
self.__dict__['_fsdp'] = None
|
| 177 |
+
|
| 178 |
+
def _init_weights(self, weight_init: tp.Optional[str], depthwise_init: tp.Optional[str], zero_bias_init: bool):
|
| 179 |
+
"""Initialization of the transformer module weights.
|
| 180 |
+
|
| 181 |
+
Args:
|
| 182 |
+
weight_init (str, optional): Weight initialization strategy. See ``get_init_fn`` for valid options.
|
| 183 |
+
depthwise_init (str, optional): Depthwise initialization strategy. The following options are valid:
|
| 184 |
+
'current' where the depth corresponds to the current layer index or 'global' where the total number
|
| 185 |
+
of layer is used as depth. If not set, no depthwise initialization strategy is used.
|
| 186 |
+
zero_bias_init (bool): Whether to initialize bias to zero or not.
|
| 187 |
+
"""
|
| 188 |
+
assert depthwise_init is None or depthwise_init in ['current', 'global']
|
| 189 |
+
assert depthwise_init is None or weight_init is not None, \
|
| 190 |
+
"If 'depthwise_init' is defined, a 'weight_init' method should be provided."
|
| 191 |
+
assert not zero_bias_init or weight_init is not None, \
|
| 192 |
+
"If 'zero_bias_init', a 'weight_init' method should be provided"
|
| 193 |
+
|
| 194 |
+
if weight_init is None:
|
| 195 |
+
return
|
| 196 |
+
|
| 197 |
+
for emb_layer in self.emb:
|
| 198 |
+
init_layer(emb_layer, method=weight_init, init_depth=None, zero_bias_init=zero_bias_init)
|
| 199 |
+
|
| 200 |
+
for layer_idx, tr_layer in enumerate(self.transformer.layers):
|
| 201 |
+
depth = None
|
| 202 |
+
if depthwise_init == 'current':
|
| 203 |
+
depth = layer_idx + 1
|
| 204 |
+
elif depthwise_init == 'global':
|
| 205 |
+
depth = len(self.transformer.layers)
|
| 206 |
+
init_fn = partial(init_layer, method=weight_init, init_depth=depth, zero_bias_init=zero_bias_init)
|
| 207 |
+
tr_layer.apply(init_fn)
|
| 208 |
+
|
| 209 |
+
for linear in self.linears:
|
| 210 |
+
init_layer(linear, method=weight_init, init_depth=None, zero_bias_init=zero_bias_init)
|
| 211 |
+
|
| 212 |
+
@property
|
| 213 |
+
def special_token_id(self) -> int:
|
| 214 |
+
return self.card
|
| 215 |
+
|
| 216 |
+
@property
|
| 217 |
+
def num_codebooks(self) -> int:
|
| 218 |
+
return self.n_q
|
| 219 |
+
|
| 220 |
+
def forward(self, sequence: torch.Tensor,
|
| 221 |
+
conditions: tp.List[ConditioningAttributes],
|
| 222 |
+
condition_tensors: tp.Optional[ConditionTensors] = None,
|
| 223 |
+
stage: int = -1) -> torch.Tensor:
|
| 224 |
+
"""Apply language model on sequence and conditions.
|
| 225 |
+
Given a tensor of sequence of shape [B, K, S] with K the number of codebooks and
|
| 226 |
+
S the sequence steps, return the logits with shape [B, card, K, S].
|
| 227 |
+
|
| 228 |
+
Args:
|
| 229 |
+
indices (torch.Tensor): Indices of the codes to model.
|
| 230 |
+
conditions (list of ConditioningAttributes): Conditions to use when modeling
|
| 231 |
+
the given codes. Note that when evaluating multiple time with the same conditioning
|
| 232 |
+
you should pre-compute those and pass them as `condition_tensors`.
|
| 233 |
+
condition_tensors (dict[str, ConditionType], optional): Pre-computed conditioning
|
| 234 |
+
tensors, see `conditions`.
|
| 235 |
+
stage (int): The codebook level that is being predicted. Relevant for MAGNeT
|
| 236 |
+
in which prediction is done in a codebook-by-codebook manner.
|
| 237 |
+
Takes values in range(n_q), and ignored by default.
|
| 238 |
+
Returns:
|
| 239 |
+
torch.Tensor: Logits.
|
| 240 |
+
"""
|
| 241 |
+
B, K, S = sequence.shape
|
| 242 |
+
assert K == self.num_codebooks, "Sequence shape must match the specified number of codebooks"
|
| 243 |
+
input_ = sum([self.emb[k](sequence[:, k]) for k in range(K)])
|
| 244 |
+
if condition_tensors is None:
|
| 245 |
+
assert not self._is_streaming, "Conditions tensors should be precomputed when streaming."
|
| 246 |
+
# apply dropout modules
|
| 247 |
+
conditions = self.cfg_dropout(conditions)
|
| 248 |
+
conditions = self.att_dropout(conditions)
|
| 249 |
+
tokenized = self.condition_provider.tokenize(conditions)
|
| 250 |
+
# encode conditions and fuse, both have a streaming cache to not recompute when generating.
|
| 251 |
+
condition_tensors = self.condition_provider(tokenized)
|
| 252 |
+
else:
|
| 253 |
+
assert not conditions, "Shouldn't pass both conditions and condition_tensors."
|
| 254 |
+
|
| 255 |
+
input_, cross_attention_input = self.fuser(input_, condition_tensors)
|
| 256 |
+
|
| 257 |
+
out = self.transformer(input_, cross_attention_src=cross_attention_input,
|
| 258 |
+
src_mask=(self.attn_mask_per_stage[stage] if stage >= 0 else None))
|
| 259 |
+
if self.out_norm:
|
| 260 |
+
out = self.out_norm(out)
|
| 261 |
+
logits = torch.stack([self.linears[k](out) for k in range(K)], dim=1) # [B, K, S, card]
|
| 262 |
+
|
| 263 |
+
# remove the prefix from the model outputs
|
| 264 |
+
if len(self.fuser.fuse2cond['prepend']) > 0:
|
| 265 |
+
logits = logits[:, :, -S:]
|
| 266 |
+
|
| 267 |
+
return logits # [B, K, S, card]
|
| 268 |
+
|
| 269 |
+
def compute_predictions(
|
| 270 |
+
self, codes: torch.Tensor,
|
| 271 |
+
conditions: tp.List[ConditioningAttributes],
|
| 272 |
+
condition_tensors: tp.Optional[ConditionTensors] = None,
|
| 273 |
+
stage: int = -1,
|
| 274 |
+
keep_only_valid_steps: bool = True) -> LMOutput:
|
| 275 |
+
"""Given an input tensor of codes [B, K, T] and list of conditions, runs the model
|
| 276 |
+
forward using the specified codes interleaving pattern.
|
| 277 |
+
|
| 278 |
+
Args:
|
| 279 |
+
codes (torch.Tensor): Input codes of shape [B, K, T] with B the batch size,
|
| 280 |
+
K the number of codebooks and T the number of timesteps.
|
| 281 |
+
conditions (list of ConditioningAttributes): conditionings to use when modeling
|
| 282 |
+
the given codes. Note that when evaluating multiple time with the same conditioning
|
| 283 |
+
you should pre-compute those and pass them as `condition_tensors`.
|
| 284 |
+
condition_tensors (dict[str, ConditionType], optional): pre-computed conditioning
|
| 285 |
+
tensors, see `conditions`.
|
| 286 |
+
stage (int): The codebook level that is being predicted. Relevant for MAGNeT
|
| 287 |
+
in which prediction is done in a codebook-by-codebook manner.
|
| 288 |
+
Takes values in range(n_q), and ignored by default.
|
| 289 |
+
keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
|
| 290 |
+
Steps that are beyond valid steps will be replaced by the special_token in that case.
|
| 291 |
+
Returns:
|
| 292 |
+
LMOutput: Language model outputs
|
| 293 |
+
logits (torch.Tensor) of shape [B, K, T, card] corresponding to the provided codes,
|
| 294 |
+
i.e. the first item corresponds to logits to predict the first code, meaning that
|
| 295 |
+
no additional shifting of codes and logits is required.
|
| 296 |
+
mask (torch.Tensor) of shape [B, K, T], mask over valid and invalid positions.
|
| 297 |
+
Given the specified interleaving strategies, parts of the logits and codes should
|
| 298 |
+
not be considered as valid predictions because of invalid context.
|
| 299 |
+
"""
|
| 300 |
+
B, K, T = codes.shape
|
| 301 |
+
codes = codes.contiguous()
|
| 302 |
+
# map codes [B, K, T] into pattern sequence [B, K, S] using special_token_id for masked tokens
|
| 303 |
+
pattern = self.pattern_provider.get_pattern(T)
|
| 304 |
+
sequence_codes, sequence_indexes, sequence_mask = pattern.build_pattern_sequence(
|
| 305 |
+
codes, self.special_token_id, keep_only_valid_steps=keep_only_valid_steps,
|
| 306 |
+
)
|
| 307 |
+
|
| 308 |
+
# apply model on pattern sequence
|
| 309 |
+
model = self if self._fsdp is None else self._fsdp
|
| 310 |
+
logits = model(sequence_codes, conditions, condition_tensors, stage=stage) # [B, K, S, card]
|
| 311 |
+
# map back the logits on pattern sequence to logits on original codes: [B, K, S, card] -> [B, K, T, card]
|
| 312 |
+
# and provide the corresponding mask over invalid positions of tokens
|
| 313 |
+
logits = logits.permute(0, 3, 1, 2) # [B, card, K, S]
|
| 314 |
+
# note: we use nans as special token to make it obvious if we feed unexpected logits
|
| 315 |
+
logits, logits_indexes, logits_mask = pattern.revert_pattern_logits(
|
| 316 |
+
logits, float('nan'), keep_only_valid_steps=keep_only_valid_steps
|
| 317 |
+
)
|
| 318 |
+
logits = logits.permute(0, 2, 3, 1) # [B, K, T, card]
|
| 319 |
+
logits_mask = logits_mask[None, :, :].expand(B, -1, -1) # [K, T] -> [B, K, T]
|
| 320 |
+
return LMOutput(logits, logits_mask)
|
| 321 |
+
|
| 322 |
+
def _sample_next_token(self,
|
| 323 |
+
sequence: torch.Tensor,
|
| 324 |
+
cfg_conditions: CFGConditions,
|
| 325 |
+
unconditional_state: State,
|
| 326 |
+
use_sampling: bool = False,
|
| 327 |
+
temp: float = 1.0,
|
| 328 |
+
top_k: int = 0,
|
| 329 |
+
top_p: float = 0.0,
|
| 330 |
+
cfg_coef: tp.Optional[float] = None,
|
| 331 |
+
two_step_cfg: tp.Optional[bool] = None) -> torch.Tensor:
|
| 332 |
+
"""Sample next token from the model given a sequence and a set of conditions. The model supports
|
| 333 |
+
multiple sampling strategies (greedy sampling, softmax, top-k, top-p...).
|
| 334 |
+
|
| 335 |
+
Args:
|
| 336 |
+
sequence (torch.Tensor): Current sequence of shape [B, K, S]
|
| 337 |
+
with K corresponding to the number of codebooks and S the number of sequence steps.
|
| 338 |
+
S = 1 in streaming mode, except for the first step that contains a bigger prompt.
|
| 339 |
+
condition_tensors (dict[str, ConditionType): Set of conditions. If CFG is used,
|
| 340 |
+
should be twice the batch size, being the concatenation of the conditions + null conditions.
|
| 341 |
+
use_sampling (bool): Whether to use a sampling strategy or not.
|
| 342 |
+
temp (float): Sampling temperature.
|
| 343 |
+
top_k (int): K for "top-k" sampling.
|
| 344 |
+
top_p (float): P for "top-p" sampling.
|
| 345 |
+
cfg_coef (float, optional): classifier free guidance coefficient
|
| 346 |
+
Returns:
|
| 347 |
+
next_token (torch.Tensor): Next token tensor of shape [B, K, 1].
|
| 348 |
+
"""
|
| 349 |
+
B = sequence.shape[0]
|
| 350 |
+
cfg_coef = self.cfg_coef if cfg_coef is None else cfg_coef
|
| 351 |
+
model = self if self._fsdp is None else self._fsdp
|
| 352 |
+
two_step_cfg = self.two_step_cfg if two_step_cfg is None else two_step_cfg
|
| 353 |
+
if two_step_cfg and cfg_conditions != {}:
|
| 354 |
+
assert isinstance(cfg_conditions, tuple), type(cfg_conditions)
|
| 355 |
+
condition_tensors, null_condition_tensors = cfg_conditions
|
| 356 |
+
cond_logits = model(sequence, conditions=[], condition_tensors=condition_tensors)
|
| 357 |
+
state = self.get_streaming_state()
|
| 358 |
+
self.set_streaming_state(unconditional_state)
|
| 359 |
+
uncond_logits = model(sequence, conditions=[], condition_tensors=null_condition_tensors)
|
| 360 |
+
unconditional_state.update(self.get_streaming_state())
|
| 361 |
+
self.set_streaming_state(state)
|
| 362 |
+
logits = uncond_logits + (cond_logits - uncond_logits) * self.cfg_coef
|
| 363 |
+
else:
|
| 364 |
+
assert isinstance(cfg_conditions, dict)
|
| 365 |
+
condition_tensors = cfg_conditions
|
| 366 |
+
if condition_tensors:
|
| 367 |
+
# Preparing for CFG, predicting both conditional and unconditional logits.
|
| 368 |
+
sequence = torch.cat([sequence, sequence], dim=0)
|
| 369 |
+
all_logits = model(
|
| 370 |
+
sequence,
|
| 371 |
+
conditions=[], condition_tensors=condition_tensors)
|
| 372 |
+
if condition_tensors:
|
| 373 |
+
cond_logits, uncond_logits = all_logits.split(B, dim=0) # [B, K, T, card]
|
| 374 |
+
logits = uncond_logits + (cond_logits - uncond_logits) * cfg_coef
|
| 375 |
+
else:
|
| 376 |
+
logits = all_logits
|
| 377 |
+
|
| 378 |
+
logits = logits.permute(0, 1, 3, 2) # [B, K, card, T]
|
| 379 |
+
logits = logits[..., -1] # [B x K x card]
|
| 380 |
+
|
| 381 |
+
# Apply softmax for sampling if temp > 0. Else, do greedy sampling to avoid zero division error.
|
| 382 |
+
if use_sampling and temp > 0.0:
|
| 383 |
+
probs = torch.softmax(logits / temp, dim=-1)
|
| 384 |
+
if top_p > 0.0:
|
| 385 |
+
next_token = utils.sample_top_p(probs, p=top_p)
|
| 386 |
+
elif top_k > 0:
|
| 387 |
+
next_token = utils.sample_top_k(probs, k=top_k)
|
| 388 |
+
else:
|
| 389 |
+
next_token = utils.multinomial(probs, num_samples=1)
|
| 390 |
+
else:
|
| 391 |
+
next_token = torch.argmax(logits, dim=-1, keepdim=True)
|
| 392 |
+
|
| 393 |
+
return next_token
|
| 394 |
+
|
| 395 |
+
@torch.no_grad()
|
| 396 |
+
def generate(self,
|
| 397 |
+
prompt: tp.Optional[torch.Tensor] = None,
|
| 398 |
+
conditions: tp.List[ConditioningAttributes] = [],
|
| 399 |
+
num_samples: tp.Optional[int] = None,
|
| 400 |
+
max_gen_len: int = 256,
|
| 401 |
+
use_sampling: bool = True,
|
| 402 |
+
temp: float = 1.0,
|
| 403 |
+
top_k: int = 250,
|
| 404 |
+
top_p: float = 0.0,
|
| 405 |
+
cfg_coef: tp.Optional[float] = None,
|
| 406 |
+
two_step_cfg: tp.Optional[bool] = None,
|
| 407 |
+
remove_prompts: bool = False,
|
| 408 |
+
check: bool = False,
|
| 409 |
+
callback: tp.Optional[tp.Callable[[int, int], None]] = None,
|
| 410 |
+
**kwargs) -> torch.Tensor:
|
| 411 |
+
"""Generate tokens sampling from the model given a prompt or unconditionally. Generation can
|
| 412 |
+
be performed in a greedy fashion or using sampling with top K and top P strategies.
|
| 413 |
+
|
| 414 |
+
Args:
|
| 415 |
+
prompt (torch.Tensor, optional): Prompt tokens of shape [B, K, T].
|
| 416 |
+
conditions_tensors (list of ConditioningAttributes, optional): List of conditions.
|
| 417 |
+
num_samples (int, optional): Number of samples to generate when no prompt and no conditions are given.
|
| 418 |
+
max_gen_len (int): Maximum generation length.
|
| 419 |
+
use_sampling (bool): Whether to use a sampling strategy or not.
|
| 420 |
+
temp (float): Sampling temperature.
|
| 421 |
+
top_k (int): K for "top-k" sampling.
|
| 422 |
+
top_p (float): P for "top-p" sampling.
|
| 423 |
+
cfg_coeff (float, optional): Classifier-free guidance coefficient.
|
| 424 |
+
two_step_cfg (bool, optional): Whether to perform classifier-free guidance with two steps generation.
|
| 425 |
+
remove_prompts (bool): Whether to remove prompts from generation or not.
|
| 426 |
+
check (bool): Whether to apply further checks on generated sequence.
|
| 427 |
+
callback (Callback, optional): Callback function to report generation progress.
|
| 428 |
+
Returns:
|
| 429 |
+
torch.Tensor: Generated tokens.
|
| 430 |
+
"""
|
| 431 |
+
assert not self.training, "generation shouldn't be used in training mode."
|
| 432 |
+
first_param = next(iter(self.parameters()))
|
| 433 |
+
device = first_param.device
|
| 434 |
+
|
| 435 |
+
# Checking all input shapes are consistent.
|
| 436 |
+
possible_num_samples = []
|
| 437 |
+
if num_samples is not None:
|
| 438 |
+
possible_num_samples.append(num_samples)
|
| 439 |
+
elif prompt is not None:
|
| 440 |
+
possible_num_samples.append(prompt.shape[0])
|
| 441 |
+
elif conditions:
|
| 442 |
+
possible_num_samples.append(len(conditions))
|
| 443 |
+
else:
|
| 444 |
+
possible_num_samples.append(1)
|
| 445 |
+
assert [x == possible_num_samples[0] for x in possible_num_samples], "Inconsistent inputs shapes"
|
| 446 |
+
num_samples = possible_num_samples[0]
|
| 447 |
+
|
| 448 |
+
# below we create set of conditions: one conditional and one unconditional
|
| 449 |
+
# to do that we merge the regular condition together with the null condition
|
| 450 |
+
# we then do 1 forward pass instead of 2.
|
| 451 |
+
# the reason for that is two-fold:
|
| 452 |
+
# 1. it is about x2 faster than doing 2 forward passes
|
| 453 |
+
# 2. avoid the streaming API treating the 2 passes as part of different time steps
|
| 454 |
+
# We also support doing two different passes, in particular to ensure that
|
| 455 |
+
# the padding structure is exactly the same between train and test.
|
| 456 |
+
# With a batch size of 1, this can be slower though.
|
| 457 |
+
cfg_conditions: CFGConditions
|
| 458 |
+
two_step_cfg = self.two_step_cfg if two_step_cfg is None else two_step_cfg
|
| 459 |
+
if conditions:
|
| 460 |
+
null_conditions = ClassifierFreeGuidanceDropout(p=1.0)(conditions)
|
| 461 |
+
if two_step_cfg:
|
| 462 |
+
cfg_conditions = (
|
| 463 |
+
self.condition_provider(self.condition_provider.tokenize(conditions)),
|
| 464 |
+
self.condition_provider(self.condition_provider.tokenize(null_conditions)),
|
| 465 |
+
)
|
| 466 |
+
else:
|
| 467 |
+
conditions = conditions + null_conditions
|
| 468 |
+
tokenized = self.condition_provider.tokenize(conditions)
|
| 469 |
+
cfg_conditions = self.condition_provider(tokenized)
|
| 470 |
+
else:
|
| 471 |
+
cfg_conditions = {}
|
| 472 |
+
|
| 473 |
+
if prompt is None:
|
| 474 |
+
assert num_samples > 0
|
| 475 |
+
prompt = torch.zeros((num_samples, self.num_codebooks, 0), dtype=torch.long, device=device)
|
| 476 |
+
|
| 477 |
+
B, K, T = prompt.shape
|
| 478 |
+
start_offset = T
|
| 479 |
+
assert start_offset < max_gen_len
|
| 480 |
+
|
| 481 |
+
pattern = self.pattern_provider.get_pattern(max_gen_len)
|
| 482 |
+
# this token is used as default value for codes that are not generated yet
|
| 483 |
+
unknown_token = -1
|
| 484 |
+
|
| 485 |
+
# we generate codes up to the max_gen_len that will be mapped to the pattern sequence
|
| 486 |
+
gen_codes = torch.full((B, K, max_gen_len), unknown_token, dtype=torch.long, device=device)
|
| 487 |
+
# filling the gen_codes with the prompt if needed
|
| 488 |
+
gen_codes[..., :start_offset] = prompt
|
| 489 |
+
# create the gen_sequence with proper interleaving from the pattern: [B, K, S]
|
| 490 |
+
gen_sequence, indexes, mask = pattern.build_pattern_sequence(gen_codes, self.special_token_id)
|
| 491 |
+
# retrieve the start_offset in the sequence:
|
| 492 |
+
# it is the first sequence step that contains the `start_offset` timestep
|
| 493 |
+
start_offset_sequence = pattern.get_first_step_with_timesteps(start_offset)
|
| 494 |
+
assert start_offset_sequence is not None
|
| 495 |
+
|
| 496 |
+
with self.streaming():
|
| 497 |
+
unconditional_state = self.get_streaming_state()
|
| 498 |
+
prev_offset = 0
|
| 499 |
+
gen_sequence_len = gen_sequence.shape[-1] # gen_sequence shape is [B, K, S]
|
| 500 |
+
for offset in range(start_offset_sequence, gen_sequence_len):
|
| 501 |
+
# get current sequence (note that the streaming API is providing the caching over previous offsets)
|
| 502 |
+
curr_sequence = gen_sequence[..., prev_offset:offset]
|
| 503 |
+
curr_mask = mask[None, ..., prev_offset:offset].expand(B, -1, -1)
|
| 504 |
+
if check:
|
| 505 |
+
# check coherence between mask and sequence
|
| 506 |
+
assert (curr_sequence == torch.where(curr_mask, curr_sequence, self.special_token_id)).all()
|
| 507 |
+
# should never happen as gen_sequence is filled progressively
|
| 508 |
+
assert not (curr_sequence == unknown_token).any()
|
| 509 |
+
# sample next token from the model, next token shape is [B, K, 1]
|
| 510 |
+
next_token = self._sample_next_token(
|
| 511 |
+
curr_sequence, cfg_conditions, unconditional_state, use_sampling, temp, top_k, top_p,
|
| 512 |
+
cfg_coef=cfg_coef, two_step_cfg=two_step_cfg)
|
| 513 |
+
# ensure the tokens that should be masked are properly set to special_token_id
|
| 514 |
+
# as the model never output special_token_id
|
| 515 |
+
valid_mask = mask[..., offset:offset+1].expand(B, -1, -1)
|
| 516 |
+
next_token[~valid_mask] = self.special_token_id
|
| 517 |
+
# ensure we don't overwrite prompt tokens, we only write over unknown tokens
|
| 518 |
+
# (then mask tokens should be left as is as well, which is correct)
|
| 519 |
+
gen_sequence[..., offset:offset+1] = torch.where(
|
| 520 |
+
gen_sequence[..., offset:offset+1] == unknown_token,
|
| 521 |
+
next_token, gen_sequence[..., offset:offset+1]
|
| 522 |
+
)
|
| 523 |
+
prev_offset = offset
|
| 524 |
+
if callback is not None:
|
| 525 |
+
callback(1 + offset - start_offset_sequence, gen_sequence_len - start_offset_sequence)
|
| 526 |
+
unconditional_state.clear()
|
| 527 |
+
|
| 528 |
+
# ensure sequence has been entirely filled
|
| 529 |
+
assert not (gen_sequence == unknown_token).any()
|
| 530 |
+
# ensure gen_sequence pattern and mask are matching
|
| 531 |
+
# which means the gen_sequence is valid according to the pattern
|
| 532 |
+
assert (
|
| 533 |
+
gen_sequence == torch.where(mask[None, ...].expand(B, -1, -1), gen_sequence, self.special_token_id)
|
| 534 |
+
).all()
|
| 535 |
+
# get back the codes, trimming the prompt if needed and cutting potentially incomplete timesteps
|
| 536 |
+
out_codes, out_indexes, out_mask = pattern.revert_pattern_sequence(gen_sequence, special_token=unknown_token)
|
| 537 |
+
|
| 538 |
+
# sanity checks over the returned codes and corresponding masks
|
| 539 |
+
assert (out_codes[..., :max_gen_len] != unknown_token).all()
|
| 540 |
+
assert (out_mask[..., :max_gen_len] == 1).all()
|
| 541 |
+
|
| 542 |
+
out_start_offset = start_offset if remove_prompts else 0
|
| 543 |
+
out_codes = out_codes[..., out_start_offset:max_gen_len]
|
| 544 |
+
|
| 545 |
+
# ensure the returned codes are all valid
|
| 546 |
+
assert (out_codes >= 0).all() and (out_codes <= self.card).all()
|
| 547 |
+
return out_codes
|
src/audiocraft/models/lm_magnet.py
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import logging
|
| 8 |
+
import math
|
| 9 |
+
import typing as tp
|
| 10 |
+
import torch
|
| 11 |
+
import numpy as np
|
| 12 |
+
|
| 13 |
+
from ..utils import utils
|
| 14 |
+
from ..modules.conditioners import (
|
| 15 |
+
ClassifierFreeGuidanceDropout,
|
| 16 |
+
ConditioningAttributes,
|
| 17 |
+
ConditionType,
|
| 18 |
+
)
|
| 19 |
+
from .lm import LMModel
|
| 20 |
+
|
| 21 |
+
logger = logging.getLogger(__name__)
|
| 22 |
+
ConditionTensors = tp.Dict[str, ConditionType]
|
| 23 |
+
CFGConditions = tp.Union[ConditionTensors, tp.Tuple[ConditionTensors, ConditionTensors]]
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class MagnetLMModel(LMModel):
|
| 27 |
+
"""Transformer-based, non-autoregressive model, operates on multiple streams of audio tokens (MAGNeT).
|
| 28 |
+
Args:
|
| 29 |
+
subcodes_context (int): The number of timesteps attended in the self-attention blocks of codebooks > 0.
|
| 30 |
+
When set to -1, attention is unrestricted and all timesteps are attended. Defaults to 5.
|
| 31 |
+
compression_model_framerate (int): frame rate of the audio tokenizer.
|
| 32 |
+
segment_duration (int): Sample length in seconds.
|
| 33 |
+
span_len (int): Determines the length of masking spans. This is the minimal length of consecutive masked tokens,
|
| 34 |
+
for both training and inference. Defaults to 3.
|
| 35 |
+
**kwargs: Additional parameters for the LMModel.
|
| 36 |
+
"""
|
| 37 |
+
def __init__(self, subcodes_context: int = 5, compression_model_framerate: int = 50,
|
| 38 |
+
segment_duration: int = 10, span_len: int = 3, **kwargs):
|
| 39 |
+
super().__init__(**kwargs)
|
| 40 |
+
self.causal = kwargs['causal']
|
| 41 |
+
self.subcodes_context = subcodes_context
|
| 42 |
+
self.span_len = span_len
|
| 43 |
+
self._build_attn_masks(compression_model_framerate=compression_model_framerate,
|
| 44 |
+
segment_duration=segment_duration,
|
| 45 |
+
num_heads=kwargs['num_heads'],
|
| 46 |
+
device=kwargs['device'], dtype=kwargs['dtype'])
|
| 47 |
+
|
| 48 |
+
def restricted_context_attn_mask(self, seq_len: int, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
|
| 49 |
+
"""Creates a restricted attention mask (local attention map) where the context
|
| 50 |
+
is determined by self.subcodes_context.
|
| 51 |
+
Args:
|
| 52 |
+
seq_len (int): token sequence length.
|
| 53 |
+
device (torch.device): device of the output tensor.
|
| 54 |
+
dtype (torch.dtype): data type of the output tensor.
|
| 55 |
+
Returns:
|
| 56 |
+
torch.Tensor: The restricted attention mask.
|
| 57 |
+
"""
|
| 58 |
+
# Return a context restricted non-causal att mask
|
| 59 |
+
queries_pos = torch.arange(seq_len, device=device).view(-1, 1)
|
| 60 |
+
keys_pos = torch.arange(seq_len, device=device).view(1, -1)
|
| 61 |
+
|
| 62 |
+
delta = queries_pos - keys_pos
|
| 63 |
+
valid = torch.abs(delta) <= self.subcodes_context
|
| 64 |
+
return torch.where(
|
| 65 |
+
valid,
|
| 66 |
+
torch.zeros([], device=device, dtype=dtype),
|
| 67 |
+
torch.full([], float('-inf'), device=device, dtype=dtype))
|
| 68 |
+
|
| 69 |
+
def _stage_attn_mask(self, stage: int, seq_len: int, num_heads: int,
|
| 70 |
+
device: torch.device, dtype: torch.dtype) -> tp.Optional[torch.Tensor]:
|
| 71 |
+
"""Creates a restricted attention mask given the stage (codebook index).
|
| 72 |
+
Args:
|
| 73 |
+
stage (int): The codebook index. Takes values in [0, n_q].
|
| 74 |
+
seq_len (int): Token sequence length.
|
| 75 |
+
num_heads (int): Num transformer attention heads.
|
| 76 |
+
device (torch.device): device of the output tensor.
|
| 77 |
+
dtype (torch.dtype): data type of the output tensor.
|
| 78 |
+
Returns:
|
| 79 |
+
torch.Tensor: Either a restricted attention mask or None if stage attention is unrestricted.
|
| 80 |
+
"""
|
| 81 |
+
sa_mask = None
|
| 82 |
+
|
| 83 |
+
if stage > 0 and self.subcodes_context > -1:
|
| 84 |
+
# parallel - non-causal - with restricted subcodes context
|
| 85 |
+
sa_mask = self.restricted_context_attn_mask(seq_len, device=device, dtype=dtype)
|
| 86 |
+
|
| 87 |
+
if sa_mask is not None:
|
| 88 |
+
# Repeat for each attention head
|
| 89 |
+
sa_mask = sa_mask.repeat((1, num_heads, 1, 1))
|
| 90 |
+
|
| 91 |
+
# align8 to enable memory efficient attention
|
| 92 |
+
MEMORY_EFFICIENT_ATTN_ALIGN_FACTOR = 8
|
| 93 |
+
seq_len_aligned = \
|
| 94 |
+
int(np.ceil(seq_len / MEMORY_EFFICIENT_ATTN_ALIGN_FACTOR)) * MEMORY_EFFICIENT_ATTN_ALIGN_FACTOR
|
| 95 |
+
|
| 96 |
+
sa_mask_aligned = torch.zeros((1, num_heads, seq_len_aligned, seq_len_aligned), device=device, dtype=dtype)
|
| 97 |
+
sa_mask_aligned[..., :seq_len, :seq_len] = sa_mask
|
| 98 |
+
sa_mask = sa_mask_aligned
|
| 99 |
+
|
| 100 |
+
return sa_mask
|
| 101 |
+
|
| 102 |
+
def _build_attn_masks(self, compression_model_framerate: int, segment_duration: int, num_heads: int,
|
| 103 |
+
device: torch.device, dtype: torch.dtype):
|
| 104 |
+
"""Construct attention mask per stage. For each of the RVQ codebook levels in the [0, n_q] range,
|
| 105 |
+
either a local attention map or None would be stored as an entry in the self.attn_mask_per_stage list.
|
| 106 |
+
Args:
|
| 107 |
+
compression_model_framerate (int): The frame rate of the tokenizer.
|
| 108 |
+
segment_duration (int): Sample length in seconds.
|
| 109 |
+
num_heads (int): Num transformer attention heads.
|
| 110 |
+
device (torch.device): device of the output tensor.
|
| 111 |
+
dtype (torch.dtype): data type of the output tensor.
|
| 112 |
+
"""
|
| 113 |
+
seq_len = compression_model_framerate * segment_duration
|
| 114 |
+
self.attn_mask_per_stage = [self._stage_attn_mask(stage, seq_len, num_heads,
|
| 115 |
+
device, dtype) for stage in range(self.n_q)]
|
| 116 |
+
|
| 117 |
+
@torch.no_grad()
|
| 118 |
+
def generate(self,
|
| 119 |
+
prompt: tp.Optional[torch.Tensor] = None,
|
| 120 |
+
conditions: tp.List[ConditioningAttributes] = [],
|
| 121 |
+
num_samples: tp.Optional[int] = None,
|
| 122 |
+
max_gen_len: int = 256,
|
| 123 |
+
use_sampling: bool = True,
|
| 124 |
+
temp: float = 1.0,
|
| 125 |
+
top_k: int = 250,
|
| 126 |
+
top_p: float = 0.0,
|
| 127 |
+
cfg_coef: tp.Optional[float] = None,
|
| 128 |
+
two_step_cfg: tp.Optional[bool] = None,
|
| 129 |
+
remove_prompts: bool = False,
|
| 130 |
+
check: bool = False,
|
| 131 |
+
callback: tp.Optional[tp.Callable[[int, int], None]] = None,
|
| 132 |
+
**kwargs) -> torch.Tensor:
|
| 133 |
+
|
| 134 |
+
assert cfg_coef is None, "Unsupported in MAGNeT. Use max_cfg_coef,min_cfg_coef instead."
|
| 135 |
+
assert two_step_cfg is None, "MAGNeT currently doesn't support two step classifier-free-guidance."
|
| 136 |
+
assert remove_prompts is False, "MAGNeT currently doesn't support the remove_prompts arg."
|
| 137 |
+
assert check is False, "MAGNeT currently doesn't support the check arg."
|
| 138 |
+
# Call the MAGNeT-specific generation method
|
| 139 |
+
return self._generate_magnet(prompt=prompt,
|
| 140 |
+
conditions=conditions,
|
| 141 |
+
num_samples=num_samples,
|
| 142 |
+
max_gen_len=max_gen_len,
|
| 143 |
+
use_sampling=use_sampling,
|
| 144 |
+
temp=temp,
|
| 145 |
+
top_k=top_k,
|
| 146 |
+
top_p=top_p,
|
| 147 |
+
callback=callback, **kwargs)
|
| 148 |
+
|
| 149 |
+
@torch.no_grad()
|
| 150 |
+
def _generate_magnet(self,
|
| 151 |
+
prompt: tp.Optional[torch.Tensor] = None,
|
| 152 |
+
conditions: tp.List[ConditioningAttributes] = [],
|
| 153 |
+
num_samples: tp.Optional[int] = None,
|
| 154 |
+
max_gen_len: int = 256,
|
| 155 |
+
use_sampling: bool = True,
|
| 156 |
+
temp: float = 3.0,
|
| 157 |
+
top_k: int = 0,
|
| 158 |
+
top_p: float = 0.9,
|
| 159 |
+
callback: tp.Optional[tp.Callable[[int, int], None]] = None,
|
| 160 |
+
max_cfg_coef: float = 10.0,
|
| 161 |
+
min_cfg_coef: float = 1.0,
|
| 162 |
+
decoding_steps: tp.List[int] = [20, 10, 10, 10],
|
| 163 |
+
anneal_temp: bool = True,
|
| 164 |
+
span_scoring='max',
|
| 165 |
+
span_arrangement='nonoverlap') -> torch.Tensor:
|
| 166 |
+
"""Generate audio tokens given textual conditions, and optionally given audio prompts,
|
| 167 |
+
by running MAGNeT's iterative decoding algorithm for each of the n_q RVQ levels.
|
| 168 |
+
Args:
|
| 169 |
+
prompt (torch.Tensor): Prompt tokens of shape [B, K, T].
|
| 170 |
+
conditions (list of ConditioningAttributes): List of conditions.
|
| 171 |
+
num_samples (int): Number of samples to generate when no prompt and no conditions are given.
|
| 172 |
+
max_gen_len (int): Maximum generation length.
|
| 173 |
+
use_sampling (bool): Whether to use a sampling strategy or not.
|
| 174 |
+
temp (float): Initial sampling temperature.
|
| 175 |
+
top_k (int): k for "top-k" sampling.
|
| 176 |
+
top_p (float): p for "top-p" sampling.
|
| 177 |
+
callback (Callback): Callback function to report generation progress.
|
| 178 |
+
max_clsfg_coef (float): Initial coefficient used for classifier free guidance.
|
| 179 |
+
min_clsfg_coef (float): Final coefficient used for classifier free guidance.
|
| 180 |
+
decoding_steps (list of n_q ints): The number of iterative decoding steps,
|
| 181 |
+
for each of the n_q RVQ codebooks.
|
| 182 |
+
anneal_temp (bool): When set to True, softmax temperature will be linearly decayed to zero, at each stage.
|
| 183 |
+
span_scoring (str): Use the maximum probability of each span ('max')
|
| 184 |
+
or the product of probabilities ('prod').
|
| 185 |
+
span_arrangement (str): Use either non-overlapping spans ('nonoverlap') or overlapping spans ('stride1').
|
| 186 |
+
in the masking scheme.
|
| 187 |
+
Returns:
|
| 188 |
+
torch.Tensor: Generated tokens.
|
| 189 |
+
"""
|
| 190 |
+
assert not self.training, "generation shouldn't be used in training mode."
|
| 191 |
+
first_param = next(iter(self.parameters()))
|
| 192 |
+
device = first_param.device
|
| 193 |
+
|
| 194 |
+
# Checking all input shapes are consistent.
|
| 195 |
+
possible_num_samples = []
|
| 196 |
+
if num_samples is not None:
|
| 197 |
+
possible_num_samples.append(num_samples)
|
| 198 |
+
elif prompt is not None:
|
| 199 |
+
possible_num_samples.append(prompt.shape[0])
|
| 200 |
+
elif conditions:
|
| 201 |
+
possible_num_samples.append(len(conditions))
|
| 202 |
+
else:
|
| 203 |
+
possible_num_samples.append(1)
|
| 204 |
+
assert [x == possible_num_samples[0] for x in possible_num_samples], "Inconsistent inputs shapes"
|
| 205 |
+
num_samples = possible_num_samples[0]
|
| 206 |
+
|
| 207 |
+
# below we create set of conditions: one conditional and one unconditional
|
| 208 |
+
# to do that we merge the regular condition together with the null condition
|
| 209 |
+
# we then do 1 forward pass instead of 2.
|
| 210 |
+
cfg_conditions: tp.Optional[ConditionTensors]
|
| 211 |
+
if conditions:
|
| 212 |
+
null_conditions = ClassifierFreeGuidanceDropout(p=1.0)(conditions)
|
| 213 |
+
conditions = conditions + null_conditions
|
| 214 |
+
tokenized = self.condition_provider.tokenize(conditions)
|
| 215 |
+
cfg_conditions = self.condition_provider(tokenized)
|
| 216 |
+
else:
|
| 217 |
+
cfg_conditions = {}
|
| 218 |
+
|
| 219 |
+
if prompt is None:
|
| 220 |
+
assert num_samples > 0
|
| 221 |
+
prompt = torch.zeros((num_samples, self.num_codebooks, 0), dtype=torch.long, device=device)
|
| 222 |
+
|
| 223 |
+
B, K, prompt_length = prompt.shape
|
| 224 |
+
start_offset = prompt_length
|
| 225 |
+
assert start_offset < max_gen_len
|
| 226 |
+
|
| 227 |
+
mask_id = self.special_token_id
|
| 228 |
+
|
| 229 |
+
# we generate codes with a fixed sequence length
|
| 230 |
+
shape = (B, K, max_gen_len)
|
| 231 |
+
|
| 232 |
+
gen_codes = torch.full(shape, mask_id, dtype=torch.long, device=device)
|
| 233 |
+
# filling the gen_codes with the prompt if needed
|
| 234 |
+
gen_codes[..., :start_offset] = prompt
|
| 235 |
+
# create the gen_sequence with proper interleaving from the pattern: [B, K, S]
|
| 236 |
+
gen_sequence = gen_codes
|
| 237 |
+
|
| 238 |
+
curr_step = 0
|
| 239 |
+
for stage, n_steps in zip(range(self.n_q), decoding_steps):
|
| 240 |
+
gen_sequence, curr_step = self._generate_stage(gen_sequence,
|
| 241 |
+
cfg_conditions,
|
| 242 |
+
stage=stage,
|
| 243 |
+
device=device,
|
| 244 |
+
prompt_length=prompt_length,
|
| 245 |
+
prompt=prompt,
|
| 246 |
+
temp=temp,
|
| 247 |
+
max_cfg_coef=max_cfg_coef,
|
| 248 |
+
min_cfg_coef=min_cfg_coef,
|
| 249 |
+
top_k=top_k,
|
| 250 |
+
top_p=top_p,
|
| 251 |
+
timesteps=n_steps,
|
| 252 |
+
anneal_temp=anneal_temp,
|
| 253 |
+
span_scoring=span_scoring,
|
| 254 |
+
use_sampling=use_sampling,
|
| 255 |
+
span_arrangement=span_arrangement,
|
| 256 |
+
curr_step=curr_step,
|
| 257 |
+
total_steps=sum(decoding_steps),
|
| 258 |
+
callback=callback)
|
| 259 |
+
|
| 260 |
+
return gen_sequence
|
| 261 |
+
|
| 262 |
+
@torch.no_grad()
|
| 263 |
+
def _generate_stage(self,
|
| 264 |
+
gen_sequence: torch.Tensor,
|
| 265 |
+
condition_tensors: tp.Optional[ConditionTensors],
|
| 266 |
+
stage: int,
|
| 267 |
+
device: torch.device,
|
| 268 |
+
prompt_length: int = 0,
|
| 269 |
+
prompt: tp.Optional[torch.Tensor] = None,
|
| 270 |
+
use_sampling: bool = True,
|
| 271 |
+
temp: float = 3.0,
|
| 272 |
+
max_cfg_coef: float = 10.0,
|
| 273 |
+
min_cfg_coef: float = 1.0,
|
| 274 |
+
top_k: int = 0,
|
| 275 |
+
top_p: float = 0.0,
|
| 276 |
+
timesteps: int = 10,
|
| 277 |
+
anneal_temp: bool = True,
|
| 278 |
+
span_scoring: str = 'max',
|
| 279 |
+
span_arrangement: str = 'nonoverlap',
|
| 280 |
+
curr_step: int = 0,
|
| 281 |
+
total_steps: int = 0,
|
| 282 |
+
callback: tp.Optional[tp.Callable[[int, int], None]] = None) -> tp.Tuple[torch.Tensor, int]:
|
| 283 |
+
"""Generate audio tokens of a single RVQ level (stage), given the previously generated stages,
|
| 284 |
+
and the textual conditions.
|
| 285 |
+
Args:
|
| 286 |
+
gen_sequence (torch.Tensor): Previously generated tokens.
|
| 287 |
+
condition_tensors (tp.Optional[ConditionTensors]): pre-computed conditioning tensors.
|
| 288 |
+
stage (int): RVQ level to generate.
|
| 289 |
+
device (torch.device): device of the output tensor.
|
| 290 |
+
prompt_length (int): Temporal length of the audio prompt.
|
| 291 |
+
prompt (torch.Tensor): Prompt tokens of shape [B, K, T].
|
| 292 |
+
use_sampling (bool): Whether to use a sampling strategy or not.
|
| 293 |
+
temp (float): Initial sampling temperature.
|
| 294 |
+
max_clsfg_coef (float): Initial coefficient used for classifier free guidance.
|
| 295 |
+
min_clsfg_coef (float): Final coefficient used for classifier free guidance.
|
| 296 |
+
top_k (int): k for "top-k" sampling.
|
| 297 |
+
top_p (float): p for "top-p" sampling.
|
| 298 |
+
timesteps (int): Number of iterative decoding steps.
|
| 299 |
+
anneal_temp (bool): When set to True, softmax temperature will be linearly decayed to zero, at each stage.
|
| 300 |
+
span_scoring (str): Use the maximum probability of each span ('max')
|
| 301 |
+
or the product of probabilities ('prod').
|
| 302 |
+
span_arrangement (str): Use either non-overlapping spans ('nonoverlap') or overlapping spans ('stride1').
|
| 303 |
+
in the masking scheme.
|
| 304 |
+
curr_step (int): Global iterative decoding step counter.
|
| 305 |
+
total_steps (int): Total decoding steps.
|
| 306 |
+
callback (Callback): Callback function to report generation progress.
|
| 307 |
+
Returns:
|
| 308 |
+
tuple(torch.Tensor, int): Generated tokens and the current decoding step counter.
|
| 309 |
+
"""
|
| 310 |
+
B, K, T = gen_sequence.shape
|
| 311 |
+
shape = (B, 1, T) # generating a single codebook per stage
|
| 312 |
+
|
| 313 |
+
mask_id = self.special_token_id
|
| 314 |
+
stage_gen_seq = torch.full(shape, mask_id, dtype=torch.long, device=device)
|
| 315 |
+
|
| 316 |
+
assert span_arrangement == 'nonoverlap' or span_arrangement == 'stride1'
|
| 317 |
+
chunk_masking = self.span_len > 1 and span_arrangement == 'nonoverlap'
|
| 318 |
+
|
| 319 |
+
DONT_REMASK_ME_SCORE = -1e4
|
| 320 |
+
|
| 321 |
+
model = self if self._fsdp is None else self._fsdp
|
| 322 |
+
|
| 323 |
+
if chunk_masking:
|
| 324 |
+
# span-wise scores
|
| 325 |
+
n_chunks = T // self.span_len
|
| 326 |
+
if T % self.span_len != 0:
|
| 327 |
+
# trim sequence ending to achieve a multiple of span_len
|
| 328 |
+
T = self.span_len * n_chunks
|
| 329 |
+
gen_sequence = gen_sequence[..., :T]
|
| 330 |
+
stage_gen_seq = stage_gen_seq[..., :T]
|
| 331 |
+
|
| 332 |
+
chunked_shape = (B, 1, n_chunks)
|
| 333 |
+
n_prompt_chunks = prompt_length // self.span_len
|
| 334 |
+
scores = torch.zeros(chunked_shape, dtype=torch.float32, device=device)
|
| 335 |
+
scores[..., :n_prompt_chunks] = DONT_REMASK_ME_SCORE
|
| 336 |
+
num_chunks_to_gen = n_chunks - n_prompt_chunks
|
| 337 |
+
else:
|
| 338 |
+
# token-wise scores
|
| 339 |
+
scores = torch.zeros(shape, dtype=torch.float32, device=device)
|
| 340 |
+
scores[..., :prompt_length] = DONT_REMASK_ME_SCORE
|
| 341 |
+
gen_T = T - prompt_length
|
| 342 |
+
|
| 343 |
+
# run MAGNeT iterative decoding for "timesteps" iterations
|
| 344 |
+
for timestep, steps_left in zip(torch.linspace(0, 1, timesteps, device=device), reversed(range(timesteps))):
|
| 345 |
+
|
| 346 |
+
mask_p = torch.cos(timestep * math.pi * 0.5)
|
| 347 |
+
|
| 348 |
+
if chunk_masking:
|
| 349 |
+
num_masked = max(int((mask_p * num_chunks_to_gen).item()), 1)
|
| 350 |
+
else:
|
| 351 |
+
num_masked = max(int((mask_p * gen_T).item()), 1)
|
| 352 |
+
|
| 353 |
+
# masking
|
| 354 |
+
run_lps_masking = (span_arrangement == 'stride1') and self.span_len > 1
|
| 355 |
+
if run_lps_masking:
|
| 356 |
+
# masking of the k least probable overlapping (stride 1) spans
|
| 357 |
+
mask = torch.concat((
|
| 358 |
+
[self._least_probable_span_masking(scores[[i], :, :], num_masked).to(device)
|
| 359 |
+
for i in range(B)]), dim=0)
|
| 360 |
+
stage_gen_seq[mask] = mask_id
|
| 361 |
+
else:
|
| 362 |
+
# masking of the k least probable non-overlapping spans
|
| 363 |
+
masked = scores.topk(num_masked, dim=-1).indices
|
| 364 |
+
if chunk_masking:
|
| 365 |
+
chunks_mask = torch.full(chunked_shape, False, dtype=torch.bool, device=device)
|
| 366 |
+
chunks_mask = chunks_mask.scatter(2, masked, True)
|
| 367 |
+
mask = torch.repeat_interleave(chunks_mask, self.span_len, dim=-1)
|
| 368 |
+
stage_gen_seq[mask] = mask_id
|
| 369 |
+
else:
|
| 370 |
+
stage_gen_seq = stage_gen_seq.scatter(2, masked, mask_id)
|
| 371 |
+
|
| 372 |
+
if prompt is not None:
|
| 373 |
+
stage_gen_seq[..., :prompt_length] = prompt[:, stage, :].unsqueeze(1)
|
| 374 |
+
|
| 375 |
+
gen_sequence[:, [stage], :] = stage_gen_seq
|
| 376 |
+
if condition_tensors:
|
| 377 |
+
# duplicate input for classifier free guidance
|
| 378 |
+
sequence = torch.cat([gen_sequence, gen_sequence], dim=0)
|
| 379 |
+
|
| 380 |
+
all_logits = model(sequence, [], condition_tensors, stage=stage)
|
| 381 |
+
|
| 382 |
+
if condition_tensors:
|
| 383 |
+
# classifier free guidance with annealing
|
| 384 |
+
cond_logits, uncond_logits = all_logits.split(B, dim=0) # [B, K, T, card]
|
| 385 |
+
clsfg_coef = float(mask_p) * max_cfg_coef + (1 - float(mask_p)) * min_cfg_coef
|
| 386 |
+
logits = uncond_logits + (cond_logits - uncond_logits) * clsfg_coef
|
| 387 |
+
else:
|
| 388 |
+
logits = all_logits
|
| 389 |
+
|
| 390 |
+
# temperature annealing - linear
|
| 391 |
+
t = temp * (steps_left / timesteps) if anneal_temp else temp
|
| 392 |
+
|
| 393 |
+
# sampling
|
| 394 |
+
logits = logits[:, stage, :, :].unsqueeze(1)
|
| 395 |
+
probs = torch.softmax(logits / max(t, 1e-2), dim=-1)
|
| 396 |
+
if use_sampling:
|
| 397 |
+
if top_p > 0.0:
|
| 398 |
+
sampled_tokens = utils.sample_top_p(probs, p=top_p)
|
| 399 |
+
elif top_k > 0:
|
| 400 |
+
sampled_tokens = utils.sample_top_k(probs, k=top_k)
|
| 401 |
+
else:
|
| 402 |
+
sampled_tokens = utils.multinomial(probs, num_samples=1)
|
| 403 |
+
else:
|
| 404 |
+
sampled_tokens = torch.argmax(logits, dim=-1, keepdim=True)
|
| 405 |
+
|
| 406 |
+
# place mask_id token in each of the masked positions
|
| 407 |
+
mask = stage_gen_seq == mask_id
|
| 408 |
+
stage_gen_seq = torch.where(mask, sampled_tokens[..., 0], stage_gen_seq)
|
| 409 |
+
gen_sequence[:, [stage], :] = stage_gen_seq
|
| 410 |
+
|
| 411 |
+
# get probs of sampled tokens
|
| 412 |
+
sampled_probs = torch.gather(probs, 3, sampled_tokens)[..., 0]
|
| 413 |
+
|
| 414 |
+
# span scoring
|
| 415 |
+
if chunk_masking:
|
| 416 |
+
if span_scoring == 'max':
|
| 417 |
+
# max in linear space
|
| 418 |
+
scores = 1 - torch.max(sampled_probs.reshape((B, 1, n_chunks, -1)), dim=-1)[0]
|
| 419 |
+
elif span_scoring == 'prod':
|
| 420 |
+
# prod in log space
|
| 421 |
+
scores = torch.sum(-torch.log(sampled_probs).reshape((B, 1, n_chunks, -1)), dim=-1)
|
| 422 |
+
else:
|
| 423 |
+
raise NotImplementedError
|
| 424 |
+
else:
|
| 425 |
+
# prod in log space for lps masking (stride1)
|
| 426 |
+
scores = -torch.log(sampled_probs)
|
| 427 |
+
|
| 428 |
+
# Fix unmasked tokens by placing inf probs (-inf scores)
|
| 429 |
+
if chunk_masking:
|
| 430 |
+
scores = scores.masked_fill(~chunks_mask, DONT_REMASK_ME_SCORE)
|
| 431 |
+
else:
|
| 432 |
+
scores = scores.masked_fill(~mask, DONT_REMASK_ME_SCORE)
|
| 433 |
+
|
| 434 |
+
if callback is not None:
|
| 435 |
+
curr_step += 1
|
| 436 |
+
callback(curr_step, total_steps)
|
| 437 |
+
|
| 438 |
+
return gen_sequence, curr_step
|
| 439 |
+
|
| 440 |
+
def _construct_spans_mask(self, span_starts: torch.Tensor, T: int, device: torch.device) -> torch.Tensor:
|
| 441 |
+
"""Build a [1x1xT] boolean mask consists of overlapping spans of True values, where
|
| 442 |
+
span_starts defines the initial index of each span, and the span length is
|
| 443 |
+
defined by self.span_len.
|
| 444 |
+
Args:
|
| 445 |
+
span_starts (torch.Tensor): Boolean mask determines the temporal location of each span start.
|
| 446 |
+
T (int): Sequence length.
|
| 447 |
+
device (torch.device): device of the output tensor.
|
| 448 |
+
Returns:
|
| 449 |
+
torch.Tensor: Spans mask of shape [1x1xT]
|
| 450 |
+
"""
|
| 451 |
+
mask = torch.full((1, 1, T), False, device=device)
|
| 452 |
+
mask[:, :, span_starts] = True
|
| 453 |
+
shifted_mask = mask.clone()
|
| 454 |
+
for _ in range(self.span_len - 1):
|
| 455 |
+
shifted_mask = torch.concat((torch.full((1, 1, 1), False, device=device), shifted_mask[:, :, :-1]), dim=-1)
|
| 456 |
+
mask = torch.logical_or(mask, shifted_mask)
|
| 457 |
+
return mask
|
| 458 |
+
|
| 459 |
+
def _least_probable_span_masking(self, scores: torch.Tensor, num_masked_trg: int) -> torch.Tensor:
|
| 460 |
+
"""Construct a [1x1xT] boolean mask, consists of the u least probable spans,
|
| 461 |
+
where the token probability is determined by -scores, and the total
|
| 462 |
+
number of masked tokens is as closest as possible to num_masked_trg.
|
| 463 |
+
Find u using binary search.
|
| 464 |
+
Args:
|
| 465 |
+
scores (torch.Tensor): Per token score [-log(prob)]
|
| 466 |
+
num_masked_trg: int: The desired amount of tokens to be masked.
|
| 467 |
+
Returns:
|
| 468 |
+
torch.Tensor: Spans mask of shape [1x1xT]
|
| 469 |
+
"""
|
| 470 |
+
T = scores.shape[-1]
|
| 471 |
+
device = scores.device
|
| 472 |
+
scores_unfolded = scores.unfold(2, self.span_len, 1)
|
| 473 |
+
# Span score is the product of probs (sum in log space)
|
| 474 |
+
span_scores = scores_unfolded.sum(dim=-1)
|
| 475 |
+
spans_by_scores = torch.argsort(span_scores[0, 0], descending=True)
|
| 476 |
+
|
| 477 |
+
num_masked_trg = max(num_masked_trg, self.span_len)
|
| 478 |
+
|
| 479 |
+
# Binary search for u - the number least probable overlapping masked spans s.t.
|
| 480 |
+
# the total masking rate is the closest to num_masked_trg / T.
|
| 481 |
+
min_u = num_masked_trg // self.span_len
|
| 482 |
+
max_u = num_masked_trg - self.span_len + 1
|
| 483 |
+
mid = round(0.5 * (min_u + max_u))
|
| 484 |
+
|
| 485 |
+
if mid == min_u or mid == max_u:
|
| 486 |
+
return self._construct_spans_mask(spans_by_scores[:mid], T, device)
|
| 487 |
+
|
| 488 |
+
while mid > min_u and mid < max_u:
|
| 489 |
+
mask = self._construct_spans_mask(spans_by_scores[:mid], T, device)
|
| 490 |
+
n_masked = mask.sum()
|
| 491 |
+
if n_masked > num_masked_trg:
|
| 492 |
+
max_u = mid
|
| 493 |
+
mid = round(0.5 * (min_u + max_u))
|
| 494 |
+
else:
|
| 495 |
+
min_u = mid
|
| 496 |
+
mid = round(0.5 * (min_u + max_u))
|
| 497 |
+
|
| 498 |
+
return mask
|
src/audiocraft/models/loaders.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Utility functions to load from the checkpoints.
|
| 9 |
+
Each checkpoint is a torch.saved dict with the following keys:
|
| 10 |
+
- 'xp.cfg': the hydra config as dumped during training. This should be used
|
| 11 |
+
to rebuild the object using the audiocraft.models.builders functions,
|
| 12 |
+
- 'model_best_state': a readily loadable best state for the model, including
|
| 13 |
+
the conditioner. The model obtained from `xp.cfg` should be compatible
|
| 14 |
+
with this state dict. In the case of a LM, the encodec model would not be
|
| 15 |
+
bundled along but instead provided separately.
|
| 16 |
+
|
| 17 |
+
Those functions also support loading from a remote location with the Torch Hub API.
|
| 18 |
+
They also support overriding some parameters, in particular the device and dtype
|
| 19 |
+
of the returned model.
|
| 20 |
+
"""
|
| 21 |
+
|
| 22 |
+
from pathlib import Path
|
| 23 |
+
from huggingface_hub import hf_hub_download
|
| 24 |
+
import typing as tp
|
| 25 |
+
import os
|
| 26 |
+
|
| 27 |
+
from omegaconf import OmegaConf, DictConfig
|
| 28 |
+
import torch
|
| 29 |
+
|
| 30 |
+
import src.audiocraft as audiocraft
|
| 31 |
+
from . import builders
|
| 32 |
+
from .encodec import CompressionModel
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def get_audiocraft_cache_dir() -> tp.Optional[str]:
|
| 36 |
+
return os.environ.get('AUDIOCRAFT_CACHE_DIR', None)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _get_state_dict(
|
| 40 |
+
file_or_url_or_id: tp.Union[Path, str],
|
| 41 |
+
filename: tp.Optional[str] = None,
|
| 42 |
+
device='cpu',
|
| 43 |
+
cache_dir: tp.Optional[str] = None,
|
| 44 |
+
):
|
| 45 |
+
if cache_dir is None:
|
| 46 |
+
cache_dir = get_audiocraft_cache_dir()
|
| 47 |
+
# Return the state dict either from a file or url
|
| 48 |
+
file_or_url_or_id = str(file_or_url_or_id)
|
| 49 |
+
assert isinstance(file_or_url_or_id, str)
|
| 50 |
+
|
| 51 |
+
# weights_only=False: these checkpoints bundle an OmegaConf config, not just tensors
|
| 52 |
+
if os.path.isfile(file_or_url_or_id):
|
| 53 |
+
return torch.load(file_or_url_or_id, map_location=device, weights_only=False)
|
| 54 |
+
|
| 55 |
+
if os.path.isdir(file_or_url_or_id):
|
| 56 |
+
file = f"{file_or_url_or_id}/{filename}"
|
| 57 |
+
return torch.load(file, map_location=device, weights_only=False)
|
| 58 |
+
|
| 59 |
+
elif file_or_url_or_id.startswith('https://'):
|
| 60 |
+
return torch.hub.load_state_dict_from_url(file_or_url_or_id, map_location=device, check_hash=True, weights_only=False)
|
| 61 |
+
|
| 62 |
+
else:
|
| 63 |
+
assert filename is not None, "filename needs to be defined if using HF checkpoints"
|
| 64 |
+
|
| 65 |
+
file = hf_hub_download(
|
| 66 |
+
repo_id=file_or_url_or_id, filename=filename, cache_dir=cache_dir,
|
| 67 |
+
library_name="audiocraft", library_version=audiocraft.__version__)
|
| 68 |
+
return torch.load(file, map_location=device, weights_only=False)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def load_compression_model_ckpt(file_or_url_or_id: tp.Union[Path, str], cache_dir: tp.Optional[str] = None):
|
| 72 |
+
return _get_state_dict(file_or_url_or_id, filename="compression_state_dict.bin", cache_dir=cache_dir)
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def load_compression_model(file_or_url_or_id: tp.Union[Path, str], device='cpu', cache_dir: tp.Optional[str] = None):
|
| 76 |
+
pkg = load_compression_model_ckpt(file_or_url_or_id, cache_dir=cache_dir)
|
| 77 |
+
if 'pretrained' in pkg:
|
| 78 |
+
return CompressionModel.get_pretrained(pkg['pretrained'], device=device)
|
| 79 |
+
cfg = OmegaConf.create(pkg['xp.cfg'])
|
| 80 |
+
cfg.device = str(device)
|
| 81 |
+
model = builders.get_compression_model(cfg)
|
| 82 |
+
model.load_state_dict(pkg['best_state'])
|
| 83 |
+
model.eval()
|
| 84 |
+
return model
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def load_lm_model_ckpt(file_or_url_or_id: tp.Union[Path, str], cache_dir: tp.Optional[str] = None):
|
| 88 |
+
return _get_state_dict(file_or_url_or_id, filename="state_dict.bin", cache_dir=cache_dir)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _delete_param(cfg: DictConfig, full_name: str):
|
| 92 |
+
parts = full_name.split('.')
|
| 93 |
+
for part in parts[:-1]:
|
| 94 |
+
if part in cfg:
|
| 95 |
+
cfg = cfg[part]
|
| 96 |
+
else:
|
| 97 |
+
return
|
| 98 |
+
OmegaConf.set_struct(cfg, False)
|
| 99 |
+
if parts[-1] in cfg:
|
| 100 |
+
del cfg[parts[-1]]
|
| 101 |
+
OmegaConf.set_struct(cfg, True)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def load_lm_model(file_or_url_or_id: tp.Union[Path, str], device='cpu', cache_dir: tp.Optional[str] = None):
|
| 105 |
+
pkg = load_lm_model_ckpt(file_or_url_or_id, cache_dir=cache_dir)
|
| 106 |
+
cfg = OmegaConf.create(pkg['xp.cfg'])
|
| 107 |
+
cfg.device = str(device)
|
| 108 |
+
if cfg.device == 'cpu':
|
| 109 |
+
cfg.dtype = 'float32'
|
| 110 |
+
else:
|
| 111 |
+
cfg.dtype = 'float16'
|
| 112 |
+
_delete_param(cfg, 'conditioners.self_wav.chroma_stem.cache_path')
|
| 113 |
+
_delete_param(cfg, 'conditioners.args.merge_text_conditions_p')
|
| 114 |
+
_delete_param(cfg, 'conditioners.args.drop_desc_p')
|
| 115 |
+
model = builders.get_lm_model(cfg)
|
| 116 |
+
model.load_state_dict(pkg['best_state'])
|
| 117 |
+
model.eval()
|
| 118 |
+
model.cfg = cfg
|
| 119 |
+
return model
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def load_lm_model_magnet(file_or_url_or_id: tp.Union[Path, str], compression_model_frame_rate: int,
|
| 123 |
+
device='cpu', cache_dir: tp.Optional[str] = None):
|
| 124 |
+
pkg = load_lm_model_ckpt(file_or_url_or_id, cache_dir=cache_dir)
|
| 125 |
+
cfg = OmegaConf.create(pkg['xp.cfg'])
|
| 126 |
+
cfg.device = str(device)
|
| 127 |
+
if cfg.device == 'cpu':
|
| 128 |
+
cfg.dtype = 'float32'
|
| 129 |
+
else:
|
| 130 |
+
cfg.dtype = 'float16'
|
| 131 |
+
_delete_param(cfg, 'conditioners.args.merge_text_conditions_p')
|
| 132 |
+
_delete_param(cfg, 'conditioners.args.drop_desc_p')
|
| 133 |
+
|
| 134 |
+
cfg.transformer_lm.compression_model_framerate = compression_model_frame_rate
|
| 135 |
+
cfg.transformer_lm.segment_duration = cfg.dataset.segment_duration
|
| 136 |
+
cfg.transformer_lm.span_len = cfg.masking.span_len
|
| 137 |
+
|
| 138 |
+
# MAGNeT models v1 support only xformers backend.
|
| 139 |
+
from src.audiocraft.modules.transformer import set_efficient_attention_backend
|
| 140 |
+
if cfg.transformer_lm.memory_efficient:
|
| 141 |
+
set_efficient_attention_backend("xformers")
|
| 142 |
+
|
| 143 |
+
model = builders.get_lm_model(cfg)
|
| 144 |
+
model.load_state_dict(pkg['best_state'])
|
| 145 |
+
model.eval()
|
| 146 |
+
model.cfg = cfg
|
| 147 |
+
return model
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def load_mbd_ckpt(file_or_url_or_id: tp.Union[Path, str],
|
| 151 |
+
filename: tp.Optional[str] = None,
|
| 152 |
+
cache_dir: tp.Optional[str] = None):
|
| 153 |
+
return _get_state_dict(file_or_url_or_id, filename=filename, cache_dir=cache_dir)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def load_diffusion_models(file_or_url_or_id: tp.Union[Path, str],
|
| 157 |
+
device='cpu',
|
| 158 |
+
filename: tp.Optional[str] = None,
|
| 159 |
+
cache_dir: tp.Optional[str] = None):
|
| 160 |
+
pkg = load_mbd_ckpt(file_or_url_or_id, filename=filename, cache_dir=cache_dir)
|
| 161 |
+
models = []
|
| 162 |
+
processors = []
|
| 163 |
+
cfgs = []
|
| 164 |
+
sample_rate = pkg['sample_rate']
|
| 165 |
+
for i in range(pkg['n_bands']):
|
| 166 |
+
cfg = pkg[i]['cfg']
|
| 167 |
+
model = builders.get_diffusion_model(cfg)
|
| 168 |
+
model_dict = pkg[i]['model_state']
|
| 169 |
+
model.load_state_dict(model_dict)
|
| 170 |
+
model.to(device)
|
| 171 |
+
processor = builders.get_processor(cfg=cfg.processor, sample_rate=sample_rate)
|
| 172 |
+
processor_dict = pkg[i]['processor_state']
|
| 173 |
+
processor.load_state_dict(processor_dict)
|
| 174 |
+
processor.to(device)
|
| 175 |
+
models.append(model)
|
| 176 |
+
processors.append(processor)
|
| 177 |
+
cfgs.append(cfg)
|
| 178 |
+
return models, processors, cfgs
|
src/audiocraft/models/magnet.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Main model for using MAGNeT. This will combine all the required components
|
| 9 |
+
and provide easy access to the generation API.
|
| 10 |
+
"""
|
| 11 |
+
import typing as tp
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
from .genmodel import BaseGenModel
|
| 15 |
+
from .loaders import load_compression_model, load_lm_model_magnet
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class MAGNeT(BaseGenModel):
|
| 19 |
+
"""MAGNeT main model with convenient generation API.
|
| 20 |
+
Args:
|
| 21 |
+
See MusicGen class.
|
| 22 |
+
"""
|
| 23 |
+
def __init__(self, **kwargs):
|
| 24 |
+
super().__init__(**kwargs)
|
| 25 |
+
# MAGNeT operates over a fixed sequence length defined in it's config.
|
| 26 |
+
self.duration = self.lm.cfg.dataset.segment_duration
|
| 27 |
+
self.set_generation_params()
|
| 28 |
+
|
| 29 |
+
@staticmethod
|
| 30 |
+
def get_pretrained(name: str = 'facebook/magnet-small-10secs', device=None):
|
| 31 |
+
"""Return pretrained model, we provide six models:
|
| 32 |
+
- facebook/magnet-small-10secs (300M), text to music, 10-second audio samples.
|
| 33 |
+
# see: https://huggingface.co/facebook/magnet-small-10secs
|
| 34 |
+
- facebook/magnet-medium-10secs (1.5B), text to music, 10-second audio samples.
|
| 35 |
+
# see: https://huggingface.co/facebook/magnet-medium-10secs
|
| 36 |
+
- facebook/magnet-small-30secs (300M), text to music, 30-second audio samples.
|
| 37 |
+
# see: https://huggingface.co/facebook/magnet-small-30secs
|
| 38 |
+
- facebook/magnet-medium-30secs (1.5B), text to music, 30-second audio samples.
|
| 39 |
+
# see: https://huggingface.co/facebook/magnet-medium-30secs
|
| 40 |
+
- facebook/audio-magnet-small (300M), text to sound-effect (10-second samples).
|
| 41 |
+
# see: https://huggingface.co/facebook/audio-magnet-small
|
| 42 |
+
- facebook/audio-magnet-medium (1.5B), text to sound-effect (10-second samples).
|
| 43 |
+
# see: https://huggingface.co/facebook/audio-magnet-medium
|
| 44 |
+
"""
|
| 45 |
+
if device is None:
|
| 46 |
+
if torch.cuda.device_count():
|
| 47 |
+
device = 'cuda'
|
| 48 |
+
else:
|
| 49 |
+
device = 'cpu'
|
| 50 |
+
|
| 51 |
+
compression_model = load_compression_model(name, device=device)
|
| 52 |
+
lm = load_lm_model_magnet(name, compression_model_frame_rate=int(compression_model.frame_rate), device=device)
|
| 53 |
+
|
| 54 |
+
if 'self_wav' in lm.condition_provider.conditioners:
|
| 55 |
+
lm.condition_provider.conditioners['self_wav'].match_len_on_eval = True
|
| 56 |
+
|
| 57 |
+
kwargs = {'name': name, 'compression_model': compression_model, 'lm': lm}
|
| 58 |
+
return MAGNeT(**kwargs)
|
| 59 |
+
|
| 60 |
+
def set_generation_params(self, use_sampling: bool = True, top_k: int = 0,
|
| 61 |
+
top_p: float = 0.9, temperature: float = 3.0,
|
| 62 |
+
max_cfg_coef: float = 10.0, min_cfg_coef: float = 1.0,
|
| 63 |
+
decoding_steps: tp.List[int] = [20, 10, 10, 10],
|
| 64 |
+
span_arrangement: str = 'nonoverlap'):
|
| 65 |
+
"""Set the generation parameters for MAGNeT.
|
| 66 |
+
|
| 67 |
+
Args:
|
| 68 |
+
use_sampling (bool, optional): Use sampling if True, else do argmax decoding. Defaults to True.
|
| 69 |
+
top_k (int, optional): top_k used for sampling. Defaults to 0.
|
| 70 |
+
top_p (float, optional): top_p used for sampling, when set to 0 top_k is used. Defaults to 0.9.
|
| 71 |
+
temperature (float, optional): Initial softmax temperature parameter. Defaults to 3.0.
|
| 72 |
+
max_cfg_coef (float, optional): Coefficient used for classifier free guidance. Defaults to 10.0.
|
| 73 |
+
min_cfg_coef (float, optional): End coefficient of classifier free guidance annealing. Defaults to 1.0.
|
| 74 |
+
decoding_steps (list of n_q ints, optional): The number of iterative decoding steps,
|
| 75 |
+
for each of the n_q RVQ codebooks.
|
| 76 |
+
span_arrangement (str, optional): Use either non-overlapping spans ('nonoverlap')
|
| 77 |
+
or overlapping spans ('stride1') in the masking scheme.
|
| 78 |
+
"""
|
| 79 |
+
self.generation_params = {
|
| 80 |
+
'use_sampling': use_sampling,
|
| 81 |
+
'temp': temperature,
|
| 82 |
+
'top_k': top_k,
|
| 83 |
+
'top_p': top_p,
|
| 84 |
+
'max_cfg_coef': max_cfg_coef,
|
| 85 |
+
'min_cfg_coef': min_cfg_coef,
|
| 86 |
+
'decoding_steps': [int(s) for s in decoding_steps],
|
| 87 |
+
'span_arrangement': span_arrangement
|
| 88 |
+
}
|
src/audiocraft/models/musicgen.py
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Main model for using MusicGen. This will combine all the required components
|
| 9 |
+
and provide easy access to the generation API.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import typing as tp
|
| 13 |
+
import warnings
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
from .encodec import CompressionModel
|
| 18 |
+
from .genmodel import BaseGenModel
|
| 19 |
+
from .lm import LMModel
|
| 20 |
+
from .builders import get_debug_compression_model, get_debug_lm_model
|
| 21 |
+
from .loaders import load_compression_model, load_lm_model
|
| 22 |
+
from ..data.audio_utils import convert_audio
|
| 23 |
+
from ..modules.conditioners import ConditioningAttributes, WavCondition
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
MelodyList = tp.List[tp.Optional[torch.Tensor]]
|
| 27 |
+
MelodyType = tp.Union[torch.Tensor, MelodyList]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
# backward compatible names mapping
|
| 31 |
+
_HF_MODEL_CHECKPOINTS_MAP = {
|
| 32 |
+
"small": "facebook/musicgen-small",
|
| 33 |
+
"medium": "facebook/musicgen-medium",
|
| 34 |
+
"large": "facebook/musicgen-large",
|
| 35 |
+
"melody": "facebook/musicgen-melody",
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class MusicGen(BaseGenModel):
|
| 40 |
+
"""MusicGen main model with convenient generation API.
|
| 41 |
+
|
| 42 |
+
Args:
|
| 43 |
+
name (str): name of the model.
|
| 44 |
+
compression_model (CompressionModel): Compression model
|
| 45 |
+
used to map audio to invertible discrete representations.
|
| 46 |
+
lm (LMModel): Language model over discrete representations.
|
| 47 |
+
max_duration (float, optional): maximum duration the model can produce,
|
| 48 |
+
otherwise, inferred from the training params.
|
| 49 |
+
"""
|
| 50 |
+
def __init__(self, name: str, compression_model: CompressionModel, lm: LMModel,
|
| 51 |
+
max_duration: tp.Optional[float] = None):
|
| 52 |
+
super().__init__(name, compression_model, lm, max_duration)
|
| 53 |
+
self.set_generation_params(duration=15) # default duration
|
| 54 |
+
|
| 55 |
+
@staticmethod
|
| 56 |
+
def get_pretrained(name: str = 'facebook/musicgen-melody', device=None):
|
| 57 |
+
"""Return pretrained model, we provide four models:
|
| 58 |
+
- facebook/musicgen-small (300M), text to music,
|
| 59 |
+
# see: https://huggingface.co/facebook/musicgen-small
|
| 60 |
+
- facebook/musicgen-medium (1.5B), text to music,
|
| 61 |
+
# see: https://huggingface.co/facebook/musicgen-medium
|
| 62 |
+
- facebook/musicgen-melody (1.5B) text to music and text+melody to music,
|
| 63 |
+
# see: https://huggingface.co/facebook/musicgen-melody
|
| 64 |
+
- facebook/musicgen-large (3.3B), text to music,
|
| 65 |
+
# see: https://huggingface.co/facebook/musicgen-large
|
| 66 |
+
"""
|
| 67 |
+
if device is None:
|
| 68 |
+
if torch.cuda.device_count():
|
| 69 |
+
device = 'cuda'
|
| 70 |
+
else:
|
| 71 |
+
device = 'cpu'
|
| 72 |
+
|
| 73 |
+
if name == 'debug':
|
| 74 |
+
# used only for unit tests
|
| 75 |
+
compression_model = get_debug_compression_model(device)
|
| 76 |
+
lm = get_debug_lm_model(device)
|
| 77 |
+
return MusicGen(name, compression_model, lm, max_duration=30)
|
| 78 |
+
|
| 79 |
+
if name in _HF_MODEL_CHECKPOINTS_MAP:
|
| 80 |
+
warnings.warn(
|
| 81 |
+
"MusicGen pretrained model relying on deprecated checkpoint mapping. " +
|
| 82 |
+
f"Please use full pre-trained id instead: facebook/musicgen-{name}")
|
| 83 |
+
name = _HF_MODEL_CHECKPOINTS_MAP[name]
|
| 84 |
+
|
| 85 |
+
lm = load_lm_model(name, device=device)
|
| 86 |
+
compression_model = load_compression_model(name, device=device)
|
| 87 |
+
if 'self_wav' in lm.condition_provider.conditioners:
|
| 88 |
+
lm.condition_provider.conditioners['self_wav'].match_len_on_eval = True
|
| 89 |
+
lm.condition_provider.conditioners['self_wav']._use_masking = False
|
| 90 |
+
|
| 91 |
+
return MusicGen(name, compression_model, lm)
|
| 92 |
+
|
| 93 |
+
def set_generation_params(self, use_sampling: bool = True, top_k: int = 250,
|
| 94 |
+
top_p: float = 0.0, temperature: float = 1.0,
|
| 95 |
+
duration: float = 30.0, cfg_coef: float = 3.0,
|
| 96 |
+
two_step_cfg: bool = False, extend_stride: float = 18):
|
| 97 |
+
"""Set the generation parameters for MusicGen.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
use_sampling (bool, optional): Use sampling if True, else do argmax decoding. Defaults to True.
|
| 101 |
+
top_k (int, optional): top_k used for sampling. Defaults to 250.
|
| 102 |
+
top_p (float, optional): top_p used for sampling, when set to 0 top_k is used. Defaults to 0.0.
|
| 103 |
+
temperature (float, optional): Softmax temperature parameter. Defaults to 1.0.
|
| 104 |
+
duration (float, optional): Duration of the generated waveform. Defaults to 30.0.
|
| 105 |
+
cfg_coef (float, optional): Coefficient used for classifier free guidance. Defaults to 3.0.
|
| 106 |
+
two_step_cfg (bool, optional): If True, performs 2 forward for Classifier Free Guidance,
|
| 107 |
+
instead of batching together the two. This has some impact on how things
|
| 108 |
+
are padded but seems to have little impact in practice.
|
| 109 |
+
extend_stride: when doing extended generation (i.e. more than 30 seconds), by how much
|
| 110 |
+
should we extend the audio each time. Larger values will mean less context is
|
| 111 |
+
preserved, and shorter value will require extra computations.
|
| 112 |
+
"""
|
| 113 |
+
assert extend_stride < self.max_duration, "Cannot stride by more than max generation duration."
|
| 114 |
+
self.extend_stride = extend_stride
|
| 115 |
+
self.duration = duration
|
| 116 |
+
self.generation_params = {
|
| 117 |
+
'use_sampling': use_sampling,
|
| 118 |
+
'temp': temperature,
|
| 119 |
+
'top_k': top_k,
|
| 120 |
+
'top_p': top_p,
|
| 121 |
+
'cfg_coef': cfg_coef,
|
| 122 |
+
'two_step_cfg': two_step_cfg,
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
def generate_with_chroma(self, descriptions: tp.List[str], melody_wavs: MelodyType,
|
| 126 |
+
melody_sample_rate: int, progress: bool = False,
|
| 127 |
+
return_tokens: bool = False) -> tp.Union[torch.Tensor,
|
| 128 |
+
tp.Tuple[torch.Tensor, torch.Tensor]]:
|
| 129 |
+
"""Generate samples conditioned on text and melody.
|
| 130 |
+
|
| 131 |
+
Args:
|
| 132 |
+
descriptions (list of str): A list of strings used as text conditioning.
|
| 133 |
+
melody_wavs: (torch.Tensor or list of Tensor): A batch of waveforms used as
|
| 134 |
+
melody conditioning. Should have shape [B, C, T] with B matching the description length,
|
| 135 |
+
C=1 or 2. It can be [C, T] if there is a single description. It can also be
|
| 136 |
+
a list of [C, T] tensors.
|
| 137 |
+
melody_sample_rate: (int): Sample rate of the melody waveforms.
|
| 138 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 139 |
+
"""
|
| 140 |
+
if isinstance(melody_wavs, torch.Tensor):
|
| 141 |
+
if melody_wavs.dim() == 2:
|
| 142 |
+
melody_wavs = melody_wavs[None]
|
| 143 |
+
if melody_wavs.dim() != 3:
|
| 144 |
+
raise ValueError("Melody wavs should have a shape [B, C, T].")
|
| 145 |
+
melody_wavs = list(melody_wavs)
|
| 146 |
+
else:
|
| 147 |
+
for melody in melody_wavs:
|
| 148 |
+
if melody is not None:
|
| 149 |
+
assert melody.dim() == 2, "One melody in the list has the wrong number of dims."
|
| 150 |
+
|
| 151 |
+
melody_wavs = [
|
| 152 |
+
convert_audio(wav, melody_sample_rate, self.sample_rate, self.audio_channels)
|
| 153 |
+
if wav is not None else None
|
| 154 |
+
for wav in melody_wavs]
|
| 155 |
+
attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions=descriptions, prompt=None,
|
| 156 |
+
melody_wavs=melody_wavs)
|
| 157 |
+
assert prompt_tokens is None
|
| 158 |
+
tokens = self._generate_tokens(attributes, prompt_tokens, progress)
|
| 159 |
+
if return_tokens:
|
| 160 |
+
return self.generate_audio(tokens), tokens
|
| 161 |
+
return self.generate_audio(tokens)
|
| 162 |
+
|
| 163 |
+
@torch.no_grad()
|
| 164 |
+
def _prepare_tokens_and_attributes(
|
| 165 |
+
self,
|
| 166 |
+
descriptions: tp.Sequence[tp.Optional[str]],
|
| 167 |
+
prompt: tp.Optional[torch.Tensor],
|
| 168 |
+
melody_wavs: tp.Optional[MelodyList] = None,
|
| 169 |
+
) -> tp.Tuple[tp.List[ConditioningAttributes], tp.Optional[torch.Tensor]]:
|
| 170 |
+
"""Prepare model inputs.
|
| 171 |
+
|
| 172 |
+
Args:
|
| 173 |
+
descriptions (list of str): A list of strings used as text conditioning.
|
| 174 |
+
prompt (torch.Tensor): A batch of waveforms used for continuation.
|
| 175 |
+
melody_wavs (torch.Tensor, optional): A batch of waveforms
|
| 176 |
+
used as melody conditioning. Defaults to None.
|
| 177 |
+
"""
|
| 178 |
+
attributes = [
|
| 179 |
+
ConditioningAttributes(text={'description': description})
|
| 180 |
+
for description in descriptions]
|
| 181 |
+
|
| 182 |
+
if melody_wavs is None:
|
| 183 |
+
for attr in attributes:
|
| 184 |
+
attr.wav['self_wav'] = WavCondition(
|
| 185 |
+
torch.zeros((1, 1, 1), device=self.device),
|
| 186 |
+
torch.tensor([0], device=self.device),
|
| 187 |
+
sample_rate=[self.sample_rate],
|
| 188 |
+
path=[None])
|
| 189 |
+
else:
|
| 190 |
+
if 'self_wav' not in self.lm.condition_provider.conditioners:
|
| 191 |
+
raise RuntimeError("This model doesn't support melody conditioning. "
|
| 192 |
+
"Use the `melody` model.")
|
| 193 |
+
assert len(melody_wavs) == len(descriptions), \
|
| 194 |
+
f"number of melody wavs must match number of descriptions! " \
|
| 195 |
+
f"got melody len={len(melody_wavs)}, and descriptions len={len(descriptions)}"
|
| 196 |
+
for attr, melody in zip(attributes, melody_wavs):
|
| 197 |
+
if melody is None:
|
| 198 |
+
attr.wav['self_wav'] = WavCondition(
|
| 199 |
+
torch.zeros((1, 1, 1), device=self.device),
|
| 200 |
+
torch.tensor([0], device=self.device),
|
| 201 |
+
sample_rate=[self.sample_rate],
|
| 202 |
+
path=[None])
|
| 203 |
+
else:
|
| 204 |
+
attr.wav['self_wav'] = WavCondition(
|
| 205 |
+
melody[None].to(device=self.device),
|
| 206 |
+
torch.tensor([melody.shape[-1]], device=self.device),
|
| 207 |
+
sample_rate=[self.sample_rate],
|
| 208 |
+
path=[None],
|
| 209 |
+
)
|
| 210 |
+
|
| 211 |
+
if prompt is not None:
|
| 212 |
+
if descriptions is not None:
|
| 213 |
+
assert len(descriptions) == len(prompt), "Prompt and nb. descriptions doesn't match"
|
| 214 |
+
prompt = prompt.to(self.device)
|
| 215 |
+
prompt_tokens, scale = self.compression_model.encode(prompt)
|
| 216 |
+
assert scale is None
|
| 217 |
+
else:
|
| 218 |
+
prompt_tokens = None
|
| 219 |
+
return attributes, prompt_tokens
|
| 220 |
+
|
| 221 |
+
def _generate_tokens(self, attributes: tp.List[ConditioningAttributes],
|
| 222 |
+
prompt_tokens: tp.Optional[torch.Tensor], progress: bool = False) -> torch.Tensor:
|
| 223 |
+
"""Generate discrete audio tokens given audio prompt and/or conditions.
|
| 224 |
+
|
| 225 |
+
Args:
|
| 226 |
+
attributes (list of ConditioningAttributes): Conditions used for generation (text/melody).
|
| 227 |
+
prompt_tokens (torch.Tensor, optional): Audio prompt used for continuation.
|
| 228 |
+
progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
|
| 229 |
+
Returns:
|
| 230 |
+
torch.Tensor: Generated audio, of shape [B, C, T], T is defined by the generation params.
|
| 231 |
+
"""
|
| 232 |
+
total_gen_len = int(self.duration * self.frame_rate)
|
| 233 |
+
max_prompt_len = int(min(self.duration, self.max_duration) * self.frame_rate)
|
| 234 |
+
current_gen_offset: int = 0
|
| 235 |
+
|
| 236 |
+
def _progress_callback(generated_tokens: int, tokens_to_generate: int):
|
| 237 |
+
generated_tokens += current_gen_offset
|
| 238 |
+
if self._progress_callback is not None:
|
| 239 |
+
# Note that total_gen_len might be quite wrong depending on the
|
| 240 |
+
# codebook pattern used, but with delay it is almost accurate.
|
| 241 |
+
self._progress_callback(generated_tokens, tokens_to_generate)
|
| 242 |
+
else:
|
| 243 |
+
print(f'{generated_tokens: 6d} / {tokens_to_generate: 6d}', end='\r')
|
| 244 |
+
|
| 245 |
+
if prompt_tokens is not None:
|
| 246 |
+
assert max_prompt_len >= prompt_tokens.shape[-1], \
|
| 247 |
+
"Prompt is longer than audio to generate"
|
| 248 |
+
|
| 249 |
+
callback = None
|
| 250 |
+
if progress:
|
| 251 |
+
callback = _progress_callback
|
| 252 |
+
|
| 253 |
+
if self.duration <= self.max_duration:
|
| 254 |
+
# generate by sampling from LM, simple case.
|
| 255 |
+
with self.autocast:
|
| 256 |
+
gen_tokens = self.lm.generate(
|
| 257 |
+
prompt_tokens, attributes,
|
| 258 |
+
callback=callback, max_gen_len=total_gen_len, **self.generation_params)
|
| 259 |
+
|
| 260 |
+
else:
|
| 261 |
+
# now this gets a bit messier, we need to handle prompts,
|
| 262 |
+
# melody conditioning etc.
|
| 263 |
+
ref_wavs = [attr.wav['self_wav'] for attr in attributes]
|
| 264 |
+
all_tokens = []
|
| 265 |
+
if prompt_tokens is None:
|
| 266 |
+
prompt_length = 0
|
| 267 |
+
else:
|
| 268 |
+
all_tokens.append(prompt_tokens)
|
| 269 |
+
prompt_length = prompt_tokens.shape[-1]
|
| 270 |
+
|
| 271 |
+
assert self.extend_stride is not None, "Stride should be defined to generate beyond max_duration"
|
| 272 |
+
assert self.extend_stride < self.max_duration, "Cannot stride by more than max generation duration."
|
| 273 |
+
stride_tokens = int(self.frame_rate * self.extend_stride)
|
| 274 |
+
|
| 275 |
+
while current_gen_offset + prompt_length < total_gen_len:
|
| 276 |
+
time_offset = current_gen_offset / self.frame_rate
|
| 277 |
+
chunk_duration = min(self.duration - time_offset, self.max_duration)
|
| 278 |
+
max_gen_len = int(chunk_duration * self.frame_rate)
|
| 279 |
+
for attr, ref_wav in zip(attributes, ref_wavs):
|
| 280 |
+
wav_length = ref_wav.length.item()
|
| 281 |
+
if wav_length == 0:
|
| 282 |
+
continue
|
| 283 |
+
# We will extend the wav periodically if it not long enough.
|
| 284 |
+
# we have to do it here rather than in conditioners.py as otherwise
|
| 285 |
+
# we wouldn't have the full wav.
|
| 286 |
+
initial_position = int(time_offset * self.sample_rate)
|
| 287 |
+
wav_target_length = int(self.max_duration * self.sample_rate)
|
| 288 |
+
positions = torch.arange(initial_position,
|
| 289 |
+
initial_position + wav_target_length, device=self.device)
|
| 290 |
+
attr.wav['self_wav'] = WavCondition(
|
| 291 |
+
ref_wav[0][..., positions % wav_length],
|
| 292 |
+
torch.full_like(ref_wav[1], wav_target_length),
|
| 293 |
+
[self.sample_rate] * ref_wav[0].size(0),
|
| 294 |
+
[None], [0.])
|
| 295 |
+
with self.autocast:
|
| 296 |
+
gen_tokens = self.lm.generate(
|
| 297 |
+
prompt_tokens, attributes,
|
| 298 |
+
callback=callback, max_gen_len=max_gen_len, **self.generation_params)
|
| 299 |
+
if prompt_tokens is None:
|
| 300 |
+
all_tokens.append(gen_tokens)
|
| 301 |
+
else:
|
| 302 |
+
all_tokens.append(gen_tokens[:, :, prompt_tokens.shape[-1]:])
|
| 303 |
+
prompt_tokens = gen_tokens[:, :, stride_tokens:]
|
| 304 |
+
prompt_length = prompt_tokens.shape[-1]
|
| 305 |
+
current_gen_offset += stride_tokens
|
| 306 |
+
|
| 307 |
+
gen_tokens = torch.cat(all_tokens, dim=-1)
|
| 308 |
+
return gen_tokens
|
src/audiocraft/models/unet.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Pytorch Unet Module used for diffusion.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from dataclasses import dataclass
|
| 12 |
+
import typing as tp
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
from torch import nn
|
| 16 |
+
from torch.nn import functional as F
|
| 17 |
+
from src.audiocraft.modules.transformer import StreamingTransformer, create_sin_embedding
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class Output:
|
| 22 |
+
sample: torch.Tensor
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def get_model(cfg, channels: int, side: int, num_steps: int):
|
| 26 |
+
if cfg.model == 'unet':
|
| 27 |
+
return DiffusionUnet(
|
| 28 |
+
chin=channels, num_steps=num_steps, **cfg.diffusion_unet)
|
| 29 |
+
else:
|
| 30 |
+
raise RuntimeError('Not Implemented')
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class ResBlock(nn.Module):
|
| 34 |
+
def __init__(self, channels: int, kernel: int = 3, norm_groups: int = 4,
|
| 35 |
+
dilation: int = 1, activation: tp.Type[nn.Module] = nn.ReLU,
|
| 36 |
+
dropout: float = 0.):
|
| 37 |
+
super().__init__()
|
| 38 |
+
stride = 1
|
| 39 |
+
padding = dilation * (kernel - stride) // 2
|
| 40 |
+
Conv = nn.Conv1d
|
| 41 |
+
Drop = nn.Dropout1d
|
| 42 |
+
self.norm1 = nn.GroupNorm(norm_groups, channels)
|
| 43 |
+
self.conv1 = Conv(channels, channels, kernel, 1, padding, dilation=dilation)
|
| 44 |
+
self.activation1 = activation()
|
| 45 |
+
self.dropout1 = Drop(dropout)
|
| 46 |
+
|
| 47 |
+
self.norm2 = nn.GroupNorm(norm_groups, channels)
|
| 48 |
+
self.conv2 = Conv(channels, channels, kernel, 1, padding, dilation=dilation)
|
| 49 |
+
self.activation2 = activation()
|
| 50 |
+
self.dropout2 = Drop(dropout)
|
| 51 |
+
|
| 52 |
+
def forward(self, x):
|
| 53 |
+
h = self.dropout1(self.conv1(self.activation1(self.norm1(x))))
|
| 54 |
+
h = self.dropout2(self.conv2(self.activation2(self.norm2(h))))
|
| 55 |
+
return x + h
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
class DecoderLayer(nn.Module):
|
| 59 |
+
def __init__(self, chin: int, chout: int, kernel: int = 4, stride: int = 2,
|
| 60 |
+
norm_groups: int = 4, res_blocks: int = 1, activation: tp.Type[nn.Module] = nn.ReLU,
|
| 61 |
+
dropout: float = 0.):
|
| 62 |
+
super().__init__()
|
| 63 |
+
padding = (kernel - stride) // 2
|
| 64 |
+
self.res_blocks = nn.Sequential(
|
| 65 |
+
*[ResBlock(chin, norm_groups=norm_groups, dilation=2**idx, dropout=dropout)
|
| 66 |
+
for idx in range(res_blocks)])
|
| 67 |
+
self.norm = nn.GroupNorm(norm_groups, chin)
|
| 68 |
+
ConvTr = nn.ConvTranspose1d
|
| 69 |
+
self.convtr = ConvTr(chin, chout, kernel, stride, padding, bias=False)
|
| 70 |
+
self.activation = activation()
|
| 71 |
+
|
| 72 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 73 |
+
x = self.res_blocks(x)
|
| 74 |
+
x = self.norm(x)
|
| 75 |
+
x = self.activation(x)
|
| 76 |
+
x = self.convtr(x)
|
| 77 |
+
return x
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class EncoderLayer(nn.Module):
|
| 81 |
+
def __init__(self, chin: int, chout: int, kernel: int = 4, stride: int = 2,
|
| 82 |
+
norm_groups: int = 4, res_blocks: int = 1, activation: tp.Type[nn.Module] = nn.ReLU,
|
| 83 |
+
dropout: float = 0.):
|
| 84 |
+
super().__init__()
|
| 85 |
+
padding = (kernel - stride) // 2
|
| 86 |
+
Conv = nn.Conv1d
|
| 87 |
+
self.conv = Conv(chin, chout, kernel, stride, padding, bias=False)
|
| 88 |
+
self.norm = nn.GroupNorm(norm_groups, chout)
|
| 89 |
+
self.activation = activation()
|
| 90 |
+
self.res_blocks = nn.Sequential(
|
| 91 |
+
*[ResBlock(chout, norm_groups=norm_groups, dilation=2**idx, dropout=dropout)
|
| 92 |
+
for idx in range(res_blocks)])
|
| 93 |
+
|
| 94 |
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
| 95 |
+
B, C, T = x.shape
|
| 96 |
+
stride, = self.conv.stride
|
| 97 |
+
pad = (stride - (T % stride)) % stride
|
| 98 |
+
x = F.pad(x, (0, pad))
|
| 99 |
+
|
| 100 |
+
x = self.conv(x)
|
| 101 |
+
x = self.norm(x)
|
| 102 |
+
x = self.activation(x)
|
| 103 |
+
x = self.res_blocks(x)
|
| 104 |
+
return x
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class BLSTM(nn.Module):
|
| 108 |
+
"""BiLSTM with same hidden units as input dim.
|
| 109 |
+
"""
|
| 110 |
+
def __init__(self, dim, layers=2):
|
| 111 |
+
super().__init__()
|
| 112 |
+
self.lstm = nn.LSTM(bidirectional=True, num_layers=layers, hidden_size=dim, input_size=dim)
|
| 113 |
+
self.linear = nn.Linear(2 * dim, dim)
|
| 114 |
+
|
| 115 |
+
def forward(self, x):
|
| 116 |
+
x = x.permute(2, 0, 1)
|
| 117 |
+
x = self.lstm(x)[0]
|
| 118 |
+
x = self.linear(x)
|
| 119 |
+
x = x.permute(1, 2, 0)
|
| 120 |
+
return x
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
class DiffusionUnet(nn.Module):
|
| 124 |
+
def __init__(self, chin: int = 3, hidden: int = 24, depth: int = 3, growth: float = 2.,
|
| 125 |
+
max_channels: int = 10_000, num_steps: int = 1000, emb_all_layers=False, cross_attention: bool = False,
|
| 126 |
+
bilstm: bool = False, transformer: bool = False,
|
| 127 |
+
codec_dim: tp.Optional[int] = None, **kwargs):
|
| 128 |
+
super().__init__()
|
| 129 |
+
self.encoders = nn.ModuleList()
|
| 130 |
+
self.decoders = nn.ModuleList()
|
| 131 |
+
self.embeddings: tp.Optional[nn.ModuleList] = None
|
| 132 |
+
self.embedding = nn.Embedding(num_steps, hidden)
|
| 133 |
+
if emb_all_layers:
|
| 134 |
+
self.embeddings = nn.ModuleList()
|
| 135 |
+
self.condition_embedding: tp.Optional[nn.Module] = None
|
| 136 |
+
for d in range(depth):
|
| 137 |
+
encoder = EncoderLayer(chin, hidden, **kwargs)
|
| 138 |
+
decoder = DecoderLayer(hidden, chin, **kwargs)
|
| 139 |
+
self.encoders.append(encoder)
|
| 140 |
+
self.decoders.insert(0, decoder)
|
| 141 |
+
if emb_all_layers and d > 0:
|
| 142 |
+
assert self.embeddings is not None
|
| 143 |
+
self.embeddings.append(nn.Embedding(num_steps, hidden))
|
| 144 |
+
chin = hidden
|
| 145 |
+
hidden = min(int(chin * growth), max_channels)
|
| 146 |
+
self.bilstm: tp.Optional[nn.Module]
|
| 147 |
+
if bilstm:
|
| 148 |
+
self.bilstm = BLSTM(chin)
|
| 149 |
+
else:
|
| 150 |
+
self.bilstm = None
|
| 151 |
+
self.use_transformer = transformer
|
| 152 |
+
self.cross_attention = False
|
| 153 |
+
if transformer:
|
| 154 |
+
self.cross_attention = cross_attention
|
| 155 |
+
self.transformer = StreamingTransformer(chin, 8, 6, bias_ff=False, bias_attn=False,
|
| 156 |
+
cross_attention=cross_attention)
|
| 157 |
+
|
| 158 |
+
self.use_codec = False
|
| 159 |
+
if codec_dim is not None:
|
| 160 |
+
self.conv_codec = nn.Conv1d(codec_dim, chin, 1)
|
| 161 |
+
self.use_codec = True
|
| 162 |
+
|
| 163 |
+
def forward(self, x: torch.Tensor, step: tp.Union[int, torch.Tensor], condition: tp.Optional[torch.Tensor] = None):
|
| 164 |
+
skips = []
|
| 165 |
+
bs = x.size(0)
|
| 166 |
+
z = x
|
| 167 |
+
view_args = [1]
|
| 168 |
+
if type(step) is torch.Tensor:
|
| 169 |
+
step_tensor = step
|
| 170 |
+
else:
|
| 171 |
+
step_tensor = torch.tensor([step], device=x.device, dtype=torch.long).expand(bs)
|
| 172 |
+
|
| 173 |
+
for idx, encoder in enumerate(self.encoders):
|
| 174 |
+
z = encoder(z)
|
| 175 |
+
if idx == 0:
|
| 176 |
+
z = z + self.embedding(step_tensor).view(bs, -1, *view_args).expand_as(z)
|
| 177 |
+
elif self.embeddings is not None:
|
| 178 |
+
z = z + self.embeddings[idx - 1](step_tensor).view(bs, -1, *view_args).expand_as(z)
|
| 179 |
+
|
| 180 |
+
skips.append(z)
|
| 181 |
+
|
| 182 |
+
if self.use_codec: # insert condition in the bottleneck
|
| 183 |
+
assert condition is not None, "Model defined for conditionnal generation"
|
| 184 |
+
condition_emb = self.conv_codec(condition) # reshape to the bottleneck dim
|
| 185 |
+
assert condition_emb.size(-1) <= 2 * z.size(-1), \
|
| 186 |
+
f"You are downsampling the conditionning with factor >=2 : {condition_emb.size(-1)=} and {z.size(-1)=}"
|
| 187 |
+
if not self.cross_attention:
|
| 188 |
+
|
| 189 |
+
condition_emb = torch.nn.functional.interpolate(condition_emb, z.size(-1))
|
| 190 |
+
assert z.size() == condition_emb.size()
|
| 191 |
+
z += condition_emb
|
| 192 |
+
cross_attention_src = None
|
| 193 |
+
else:
|
| 194 |
+
cross_attention_src = condition_emb.permute(0, 2, 1) # B, T, C
|
| 195 |
+
B, T, C = cross_attention_src.shape
|
| 196 |
+
positions = torch.arange(T, device=x.device).view(1, -1, 1)
|
| 197 |
+
pos_emb = create_sin_embedding(positions, C, max_period=10_000, dtype=cross_attention_src.dtype)
|
| 198 |
+
cross_attention_src = cross_attention_src + pos_emb
|
| 199 |
+
if self.use_transformer:
|
| 200 |
+
z = self.transformer(z.permute(0, 2, 1), cross_attention_src=cross_attention_src).permute(0, 2, 1)
|
| 201 |
+
else:
|
| 202 |
+
if self.bilstm is None:
|
| 203 |
+
z = torch.zeros_like(z)
|
| 204 |
+
else:
|
| 205 |
+
z = self.bilstm(z)
|
| 206 |
+
|
| 207 |
+
for decoder in self.decoders:
|
| 208 |
+
s = skips.pop(-1)
|
| 209 |
+
z = z[:, :, :s.shape[2]]
|
| 210 |
+
z = z + s
|
| 211 |
+
z = decoder(z)
|
| 212 |
+
|
| 213 |
+
z = z[:, :, :x.shape[2]]
|
| 214 |
+
return Output(z)
|
src/audiocraft/modules/__init__.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Modules used for building the models."""
|
| 7 |
+
|
| 8 |
+
# flake8: noqa
|
| 9 |
+
from .conv import (
|
| 10 |
+
NormConv1d,
|
| 11 |
+
NormConv2d,
|
| 12 |
+
NormConvTranspose1d,
|
| 13 |
+
NormConvTranspose2d,
|
| 14 |
+
StreamableConv1d,
|
| 15 |
+
StreamableConvTranspose1d,
|
| 16 |
+
pad_for_conv1d,
|
| 17 |
+
pad1d,
|
| 18 |
+
unpad1d,
|
| 19 |
+
)
|
| 20 |
+
from .lstm import StreamableLSTM
|
| 21 |
+
from .seanet import SEANetEncoder, SEANetDecoder
|
| 22 |
+
from .transformer import StreamingTransformer
|
src/audiocraft/modules/activations.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn as nn
|
| 9 |
+
from torch import Tensor
|
| 10 |
+
from typing import Union, Callable
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class CustomGLU(nn.Module):
|
| 14 |
+
"""Custom Gated Linear Unit activation.
|
| 15 |
+
Applies a modified gated linear unit :math:`a * f(b)` where :math:`a` is the first half
|
| 16 |
+
of the input matrices, :math:`b` is the second half, and :math:`f` is a provided activation
|
| 17 |
+
function (i.e. sigmoid, swish, etc.).
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
activation (nn.Module): The custom activation to apply in the Gated Linear Unit
|
| 21 |
+
dim (int): the dimension on which to split the input. Default: -1
|
| 22 |
+
|
| 23 |
+
Shape:
|
| 24 |
+
- Input: :math:`(\ast_1, N, \ast_2)` where `*` means, any number of additional
|
| 25 |
+
dimensions
|
| 26 |
+
- Output: :math:`(\ast_1, M, \ast_2)` where :math:`M=N/2`
|
| 27 |
+
|
| 28 |
+
Examples::
|
| 29 |
+
>>> m = CustomGLU(nn.Sigmoid())
|
| 30 |
+
>>> input = torch.randn(4, 2)
|
| 31 |
+
>>> output = m(input)
|
| 32 |
+
"""
|
| 33 |
+
def __init__(self, activation: nn.Module, dim: int = -1):
|
| 34 |
+
super(CustomGLU, self).__init__()
|
| 35 |
+
self.dim = dim
|
| 36 |
+
self.activation = activation
|
| 37 |
+
|
| 38 |
+
def forward(self, x: Tensor):
|
| 39 |
+
assert x.shape[self.dim] % 2 == 0 # M = N / 2
|
| 40 |
+
a, b = torch.chunk(x, 2, dim=self.dim)
|
| 41 |
+
return a * self.activation(b)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class SwiGLU(CustomGLU):
|
| 45 |
+
"""SiLU Gated Linear Unit activation.
|
| 46 |
+
Applies SiLU Gated Linear Unit :math:`a * SiLU(b)` where :math:`a` is
|
| 47 |
+
the first half of the input matrices, :math:`b` is the second half.
|
| 48 |
+
|
| 49 |
+
Args:
|
| 50 |
+
dim (int): the dimension on which to split the input. Default: -1
|
| 51 |
+
"""
|
| 52 |
+
def __init__(self, dim: int = -1):
|
| 53 |
+
super(SwiGLU, self).__init__(nn.SiLU(), dim)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class GeGLU(CustomGLU):
|
| 57 |
+
"""GeLU Gated Linear Unit activation.
|
| 58 |
+
Applies GeLU Gated Linear Unit :math:`a * GELU(b)` where :math:`a` is
|
| 59 |
+
the first half of the input matrices, :math:`b` is the second half.
|
| 60 |
+
|
| 61 |
+
Args:
|
| 62 |
+
dim (int): the dimension on which to split the input. Default: -1
|
| 63 |
+
"""
|
| 64 |
+
def __init__(self, dim: int = -1):
|
| 65 |
+
super(GeGLU, self).__init__(nn.GELU(), dim)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class ReGLU(CustomGLU):
|
| 69 |
+
"""ReLU Gated Linear Unit activation.
|
| 70 |
+
Applies ReLU Gated Linear Unit :math:`a * ReLU(b)` where :math:`a` is
|
| 71 |
+
the first half of the input matrices, :math:`b` is the second half.
|
| 72 |
+
|
| 73 |
+
Args:
|
| 74 |
+
dim (int): the dimension on which to split the input. Default: -1
|
| 75 |
+
"""
|
| 76 |
+
def __init__(self, dim: int = -1):
|
| 77 |
+
super(ReGLU, self).__init__(nn.ReLU(), dim)
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def get_activation_fn(
|
| 81 |
+
activation: Union[str, Callable[[Tensor], Tensor]]
|
| 82 |
+
) -> Union[str, Callable[[Tensor], Tensor]]:
|
| 83 |
+
"""Helper function to map an activation string to the activation class.
|
| 84 |
+
If the supplied activation is not a string that is recognized, the activation is passed back.
|
| 85 |
+
|
| 86 |
+
Args:
|
| 87 |
+
activation (str, or Callable[[Tensor], Tensor]): Activation to check
|
| 88 |
+
"""
|
| 89 |
+
if isinstance(activation, str):
|
| 90 |
+
if activation == "reglu":
|
| 91 |
+
return ReGLU()
|
| 92 |
+
elif activation == "geglu":
|
| 93 |
+
return GeGLU()
|
| 94 |
+
elif activation == "swiglu":
|
| 95 |
+
return SwiGLU()
|
| 96 |
+
return activation
|
src/audiocraft/modules/chroma.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
import typing as tp
|
| 7 |
+
|
| 8 |
+
from einops import rearrange
|
| 9 |
+
from librosa import filters
|
| 10 |
+
import torch
|
| 11 |
+
from torch import nn
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
import torchaudio
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class ChromaExtractor(nn.Module):
|
| 17 |
+
"""Chroma extraction and quantization.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
sample_rate (int): Sample rate for the chroma extraction.
|
| 21 |
+
n_chroma (int): Number of chroma bins for the chroma extraction.
|
| 22 |
+
radix2_exp (int): Size of stft window for the chroma extraction (power of 2, e.g. 12 -> 2^12).
|
| 23 |
+
nfft (int, optional): Number of FFT.
|
| 24 |
+
winlen (int, optional): Window length.
|
| 25 |
+
winhop (int, optional): Window hop size.
|
| 26 |
+
argmax (bool, optional): Whether to use argmax. Defaults to False.
|
| 27 |
+
norm (float, optional): Norm for chroma normalization. Defaults to inf.
|
| 28 |
+
"""
|
| 29 |
+
def __init__(self, sample_rate: int, n_chroma: int = 12, radix2_exp: int = 12, nfft: tp.Optional[int] = None,
|
| 30 |
+
winlen: tp.Optional[int] = None, winhop: tp.Optional[int] = None, argmax: bool = False,
|
| 31 |
+
norm: float = torch.inf):
|
| 32 |
+
super().__init__()
|
| 33 |
+
self.winlen = winlen or 2 ** radix2_exp
|
| 34 |
+
self.nfft = nfft or self.winlen
|
| 35 |
+
self.winhop = winhop or (self.winlen // 4)
|
| 36 |
+
self.sample_rate = sample_rate
|
| 37 |
+
self.n_chroma = n_chroma
|
| 38 |
+
self.norm = norm
|
| 39 |
+
self.argmax = argmax
|
| 40 |
+
self.register_buffer('fbanks', torch.from_numpy(filters.chroma(sr=sample_rate, n_fft=self.nfft, tuning=0,
|
| 41 |
+
n_chroma=self.n_chroma)), persistent=False)
|
| 42 |
+
self.spec = torchaudio.transforms.Spectrogram(n_fft=self.nfft, win_length=self.winlen,
|
| 43 |
+
hop_length=self.winhop, power=2, center=True,
|
| 44 |
+
pad=0, normalized=True)
|
| 45 |
+
|
| 46 |
+
def forward(self, wav: torch.Tensor) -> torch.Tensor:
|
| 47 |
+
T = wav.shape[-1]
|
| 48 |
+
# in case we are getting a wav that was dropped out (nullified)
|
| 49 |
+
# from the conditioner, make sure wav length is no less that nfft
|
| 50 |
+
if T < self.nfft:
|
| 51 |
+
pad = self.nfft - T
|
| 52 |
+
r = 0 if pad % 2 == 0 else 1
|
| 53 |
+
wav = F.pad(wav, (pad // 2, pad // 2 + r), 'constant', 0)
|
| 54 |
+
assert wav.shape[-1] == self.nfft, f"expected len {self.nfft} but got {wav.shape[-1]}"
|
| 55 |
+
|
| 56 |
+
spec = self.spec(wav).squeeze(1)
|
| 57 |
+
raw_chroma = torch.einsum('cf,...ft->...ct', self.fbanks, spec)
|
| 58 |
+
norm_chroma = torch.nn.functional.normalize(raw_chroma, p=self.norm, dim=-2, eps=1e-6)
|
| 59 |
+
norm_chroma = rearrange(norm_chroma, 'b d t -> b t d')
|
| 60 |
+
|
| 61 |
+
if self.argmax:
|
| 62 |
+
idx = norm_chroma.argmax(-1, keepdim=True)
|
| 63 |
+
norm_chroma[:] = 0
|
| 64 |
+
norm_chroma.scatter_(dim=-1, index=idx, value=1)
|
| 65 |
+
|
| 66 |
+
return norm_chroma
|
src/audiocraft/modules/codebooks_patterns.py
ADDED
|
@@ -0,0 +1,548 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
from collections import namedtuple
|
| 8 |
+
from dataclasses import dataclass
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
import logging
|
| 11 |
+
import typing as tp
|
| 12 |
+
|
| 13 |
+
from abc import ABC, abstractmethod
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
LayoutCoord = namedtuple('LayoutCoord', ['t', 'q']) # (timestep, codebook index)
|
| 17 |
+
PatternLayout = tp.List[tp.List[LayoutCoord]] # Sequence of coordinates
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@dataclass
|
| 22 |
+
class Pattern:
|
| 23 |
+
"""Base implementation of a pattern over a sequence with multiple codebooks.
|
| 24 |
+
|
| 25 |
+
The codebook pattern consists in a layout, defining for each sequence step
|
| 26 |
+
the list of coordinates of each codebook timestep in the resulting interleaved sequence.
|
| 27 |
+
The first item of the pattern is always an empty list in order to properly insert a special token
|
| 28 |
+
to start with. For convenience, we also keep track of ``n_q`` the number of codebooks used for the pattern
|
| 29 |
+
and ``timesteps`` the number of timesteps corresponding to the original sequence.
|
| 30 |
+
|
| 31 |
+
The pattern provides convenient methods to build and revert interleaved sequences from it:
|
| 32 |
+
``build_pattern_sequence`` maps a given a dense input tensor of multi-codebook sequence from [B, K, T]
|
| 33 |
+
to the interleaved sequence of shape [B, K, S] applying the pattern, with B being the batch size,
|
| 34 |
+
K being the number of codebooks, T the number of original timesteps and S the number of sequence steps
|
| 35 |
+
for the output sequence. The unfilled positions are replaced with a special token and the built sequence
|
| 36 |
+
is returned along with a mask indicating valid tokens.
|
| 37 |
+
``revert_pattern_sequence`` maps back an interleaved sequence of shape [B, K, S] to the original alignment
|
| 38 |
+
of codebooks across timesteps to an output tensor of shape [B, K, T], using again a special token and a mask
|
| 39 |
+
to fill and specify invalid positions if needed.
|
| 40 |
+
See the dedicated methods for more details.
|
| 41 |
+
"""
|
| 42 |
+
# Pattern layout, for each sequence step, we have a list of coordinates
|
| 43 |
+
# corresponding to the original codebook timestep and position.
|
| 44 |
+
# The first list is always an empty list in order to properly insert
|
| 45 |
+
# a special token to start with.
|
| 46 |
+
layout: PatternLayout
|
| 47 |
+
timesteps: int
|
| 48 |
+
n_q: int
|
| 49 |
+
|
| 50 |
+
def __post_init__(self):
|
| 51 |
+
assert len(self.layout) > 0
|
| 52 |
+
self._validate_layout()
|
| 53 |
+
self._build_reverted_sequence_scatter_indexes = lru_cache(100)(self._build_reverted_sequence_scatter_indexes)
|
| 54 |
+
self._build_pattern_sequence_scatter_indexes = lru_cache(100)(self._build_pattern_sequence_scatter_indexes)
|
| 55 |
+
logger.info("New pattern, time steps: %d, sequence steps: %d", self.timesteps, len(self.layout))
|
| 56 |
+
|
| 57 |
+
def _validate_layout(self):
|
| 58 |
+
"""Runs checks on the layout to ensure a valid pattern is defined.
|
| 59 |
+
A pattern is considered invalid if:
|
| 60 |
+
- Multiple timesteps for a same codebook are defined in the same sequence step
|
| 61 |
+
- The timesteps for a given codebook are not in ascending order as we advance in the sequence
|
| 62 |
+
(this would mean that we have future timesteps before past timesteps).
|
| 63 |
+
"""
|
| 64 |
+
q_timesteps = {q: 0 for q in range(self.n_q)}
|
| 65 |
+
for s, seq_coords in enumerate(self.layout):
|
| 66 |
+
if len(seq_coords) > 0:
|
| 67 |
+
qs = set()
|
| 68 |
+
for coord in seq_coords:
|
| 69 |
+
qs.add(coord.q)
|
| 70 |
+
last_q_timestep = q_timesteps[coord.q]
|
| 71 |
+
assert coord.t >= last_q_timestep, \
|
| 72 |
+
f"Past timesteps are found in the sequence for codebook = {coord.q} at step {s}"
|
| 73 |
+
q_timesteps[coord.q] = coord.t
|
| 74 |
+
# each sequence step contains at max 1 coordinate per codebook
|
| 75 |
+
assert len(qs) == len(seq_coords), \
|
| 76 |
+
f"Multiple entries for a same codebook are found at step {s}"
|
| 77 |
+
|
| 78 |
+
@property
|
| 79 |
+
def num_sequence_steps(self):
|
| 80 |
+
return len(self.layout) - 1
|
| 81 |
+
|
| 82 |
+
@property
|
| 83 |
+
def max_delay(self):
|
| 84 |
+
max_t_in_seq_coords = 0
|
| 85 |
+
for seq_coords in self.layout[1:]:
|
| 86 |
+
for coords in seq_coords:
|
| 87 |
+
max_t_in_seq_coords = max(max_t_in_seq_coords, coords.t + 1)
|
| 88 |
+
return max_t_in_seq_coords - self.timesteps
|
| 89 |
+
|
| 90 |
+
@property
|
| 91 |
+
def valid_layout(self):
|
| 92 |
+
valid_step = len(self.layout) - self.max_delay
|
| 93 |
+
return self.layout[:valid_step]
|
| 94 |
+
|
| 95 |
+
def starts_with_special_token(self):
|
| 96 |
+
return self.layout[0] == []
|
| 97 |
+
|
| 98 |
+
def get_sequence_coords_with_timestep(self, t: int, q: tp.Optional[int] = None):
|
| 99 |
+
"""Get codebook coordinates in the layout that corresponds to the specified timestep t
|
| 100 |
+
and optionally to the codebook q. Coordinates are returned as a tuple with the sequence step
|
| 101 |
+
and the actual codebook coordinates.
|
| 102 |
+
"""
|
| 103 |
+
assert t <= self.timesteps, "provided timesteps is greater than the pattern's number of timesteps"
|
| 104 |
+
if q is not None:
|
| 105 |
+
assert q <= self.n_q, "provided number of codebooks is greater than the pattern's number of codebooks"
|
| 106 |
+
coords = []
|
| 107 |
+
for s, seq_codes in enumerate(self.layout):
|
| 108 |
+
for code in seq_codes:
|
| 109 |
+
if code.t == t and (q is None or code.q == q):
|
| 110 |
+
coords.append((s, code))
|
| 111 |
+
return coords
|
| 112 |
+
|
| 113 |
+
def get_steps_with_timestep(self, t: int, q: tp.Optional[int] = None) -> tp.List[int]:
|
| 114 |
+
return [step for step, coords in self.get_sequence_coords_with_timestep(t, q)]
|
| 115 |
+
|
| 116 |
+
def get_first_step_with_timesteps(self, t: int, q: tp.Optional[int] = None) -> tp.Optional[int]:
|
| 117 |
+
steps_with_timesteps = self.get_steps_with_timestep(t, q)
|
| 118 |
+
return steps_with_timesteps[0] if len(steps_with_timesteps) > 0 else None
|
| 119 |
+
|
| 120 |
+
def _build_pattern_sequence_scatter_indexes(self, timesteps: int, n_q: int, keep_only_valid_steps: bool,
|
| 121 |
+
device: tp.Union[torch.device, str] = 'cpu'):
|
| 122 |
+
"""Build scatter indexes corresponding to the pattern, up to the provided sequence_steps.
|
| 123 |
+
|
| 124 |
+
Args:
|
| 125 |
+
timesteps (int): Maximum number of timesteps steps to consider.
|
| 126 |
+
keep_only_valid_steps (bool): Restrict the pattern layout to match only valid steps.
|
| 127 |
+
device (torch.device or str): Device for created tensors.
|
| 128 |
+
Returns:
|
| 129 |
+
indexes (torch.Tensor): Indexes corresponding to the sequence, of shape [K, S].
|
| 130 |
+
mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes, of shape [K, S].
|
| 131 |
+
"""
|
| 132 |
+
assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
|
| 133 |
+
assert timesteps <= self.timesteps, "invalid number of timesteps used to build the sequence from the pattern"
|
| 134 |
+
# use the proper layout based on whether we limit ourselves to valid steps only or not,
|
| 135 |
+
# note that using the valid_layout will result in a truncated sequence up to the valid steps
|
| 136 |
+
ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
|
| 137 |
+
# single item indexing being super slow with pytorch vs. numpy, so we use numpy here
|
| 138 |
+
indexes = torch.zeros(n_q, len(ref_layout), dtype=torch.long).numpy()
|
| 139 |
+
mask = torch.zeros(n_q, len(ref_layout), dtype=torch.bool).numpy()
|
| 140 |
+
# fill indexes with last sequence step value that will correspond to our special token
|
| 141 |
+
# the last value is n_q * timesteps as we have flattened z and append special token as the last token
|
| 142 |
+
# which will correspond to the index: n_q * timesteps
|
| 143 |
+
indexes[:] = n_q * timesteps
|
| 144 |
+
# iterate over the pattern and fill scattered indexes and mask
|
| 145 |
+
for s, sequence_coords in enumerate(ref_layout):
|
| 146 |
+
for coords in sequence_coords:
|
| 147 |
+
if coords.t < timesteps:
|
| 148 |
+
indexes[coords.q, s] = coords.t + coords.q * timesteps
|
| 149 |
+
mask[coords.q, s] = 1
|
| 150 |
+
indexes = torch.from_numpy(indexes).to(device)
|
| 151 |
+
mask = torch.from_numpy(mask).to(device)
|
| 152 |
+
return indexes, mask
|
| 153 |
+
|
| 154 |
+
def build_pattern_sequence(self, z: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
|
| 155 |
+
"""Build sequence corresponding to the pattern from the input tensor z.
|
| 156 |
+
The sequence is built using up to sequence_steps if specified, and non-pattern
|
| 157 |
+
coordinates are filled with the special token.
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
z (torch.Tensor): Input tensor of multi-codebooks sequence, of shape [B, K, T].
|
| 161 |
+
special_token (int): Special token used to fill non-pattern coordinates in the new sequence.
|
| 162 |
+
keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
|
| 163 |
+
Steps that are beyond valid steps will be replaced by the special_token in that case.
|
| 164 |
+
Returns:
|
| 165 |
+
values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, S] with S
|
| 166 |
+
corresponding either to the sequence_steps if provided, otherwise to the length of the pattern.
|
| 167 |
+
indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, S].
|
| 168 |
+
mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, S].
|
| 169 |
+
"""
|
| 170 |
+
B, K, T = z.shape
|
| 171 |
+
indexes, mask = self._build_pattern_sequence_scatter_indexes(
|
| 172 |
+
T, K, keep_only_valid_steps=keep_only_valid_steps, device=str(z.device)
|
| 173 |
+
)
|
| 174 |
+
z = z.view(B, -1)
|
| 175 |
+
# we append the special token as the last index of our flattened z tensor
|
| 176 |
+
z = torch.cat([z, torch.zeros_like(z[:, :1]) + special_token], dim=1)
|
| 177 |
+
values = z[:, indexes.view(-1)]
|
| 178 |
+
values = values.view(B, K, indexes.shape[-1])
|
| 179 |
+
return values, indexes, mask
|
| 180 |
+
|
| 181 |
+
def _build_reverted_sequence_scatter_indexes(self, sequence_steps: int, n_q: int,
|
| 182 |
+
keep_only_valid_steps: bool = False,
|
| 183 |
+
is_model_output: bool = False,
|
| 184 |
+
device: tp.Union[torch.device, str] = 'cpu'):
|
| 185 |
+
"""Builds scatter indexes required to retrieve the original multi-codebook sequence
|
| 186 |
+
from interleaving pattern.
|
| 187 |
+
|
| 188 |
+
Args:
|
| 189 |
+
sequence_steps (int): Sequence steps.
|
| 190 |
+
n_q (int): Number of codebooks.
|
| 191 |
+
keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
|
| 192 |
+
Steps that are beyond valid steps will be replaced by the special_token in that case.
|
| 193 |
+
is_model_output (bool): Whether to keep the sequence item corresponding to initial special token or not.
|
| 194 |
+
device (torch.device or str): Device for created tensors.
|
| 195 |
+
Returns:
|
| 196 |
+
indexes (torch.Tensor): Indexes for reconstructing the output, of shape [K, T].
|
| 197 |
+
mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
|
| 198 |
+
"""
|
| 199 |
+
ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
|
| 200 |
+
# TODO(jade): Do we want to further truncate to only valid timesteps here as well?
|
| 201 |
+
timesteps = self.timesteps
|
| 202 |
+
assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
|
| 203 |
+
assert sequence_steps <= len(ref_layout), \
|
| 204 |
+
f"sequence to revert is longer than the defined pattern: {sequence_steps} > {len(ref_layout)}"
|
| 205 |
+
|
| 206 |
+
# ensure we take the appropriate indexes to keep the model output from the first special token as well
|
| 207 |
+
if is_model_output and self.starts_with_special_token():
|
| 208 |
+
ref_layout = ref_layout[1:]
|
| 209 |
+
|
| 210 |
+
# single item indexing being super slow with pytorch vs. numpy, so we use numpy here
|
| 211 |
+
indexes = torch.zeros(n_q, timesteps, dtype=torch.long).numpy()
|
| 212 |
+
mask = torch.zeros(n_q, timesteps, dtype=torch.bool).numpy()
|
| 213 |
+
# fill indexes with last sequence step value that will correspond to our special token
|
| 214 |
+
indexes[:] = n_q * sequence_steps
|
| 215 |
+
for s, sequence_codes in enumerate(ref_layout):
|
| 216 |
+
if s < sequence_steps:
|
| 217 |
+
for code in sequence_codes:
|
| 218 |
+
if code.t < timesteps:
|
| 219 |
+
indexes[code.q, code.t] = s + code.q * sequence_steps
|
| 220 |
+
mask[code.q, code.t] = 1
|
| 221 |
+
indexes = torch.from_numpy(indexes).to(device)
|
| 222 |
+
mask = torch.from_numpy(mask).to(device)
|
| 223 |
+
return indexes, mask
|
| 224 |
+
|
| 225 |
+
def revert_pattern_sequence(self, s: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
|
| 226 |
+
"""Revert a sequence built from the pattern back to the original multi-codebook sequence without interleaving.
|
| 227 |
+
The sequence is reverted using up to timesteps if specified, and non-pattern coordinates
|
| 228 |
+
are filled with the special token.
|
| 229 |
+
|
| 230 |
+
Args:
|
| 231 |
+
s (torch.Tensor): Interleaved sequence tensor obtained from the pattern, of shape [B, K, S].
|
| 232 |
+
special_token (int or float): Special token used to fill non-pattern coordinates in the new sequence.
|
| 233 |
+
Returns:
|
| 234 |
+
values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, T] with T
|
| 235 |
+
corresponding either to the timesteps if provided, or the total timesteps in pattern otherwise.
|
| 236 |
+
indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, T].
|
| 237 |
+
mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
|
| 238 |
+
"""
|
| 239 |
+
B, K, S = s.shape
|
| 240 |
+
indexes, mask = self._build_reverted_sequence_scatter_indexes(
|
| 241 |
+
S, K, keep_only_valid_steps, is_model_output=False, device=str(s.device)
|
| 242 |
+
)
|
| 243 |
+
s = s.view(B, -1)
|
| 244 |
+
# we append the special token as the last index of our flattened z tensor
|
| 245 |
+
s = torch.cat([s, torch.zeros_like(s[:, :1]) + special_token], dim=1)
|
| 246 |
+
values = s[:, indexes.view(-1)]
|
| 247 |
+
values = values.view(B, K, indexes.shape[-1])
|
| 248 |
+
return values, indexes, mask
|
| 249 |
+
|
| 250 |
+
def revert_pattern_logits(self, logits: torch.Tensor, special_token: float, keep_only_valid_steps: bool = False):
|
| 251 |
+
"""Revert model logits obtained on a sequence built from the pattern
|
| 252 |
+
back to a tensor matching the original sequence.
|
| 253 |
+
|
| 254 |
+
This method is similar to ``revert_pattern_sequence`` with the following specificities:
|
| 255 |
+
1. It is designed to work with the extra cardinality dimension
|
| 256 |
+
2. We return the logits for the first sequence item that matches the special_token and
|
| 257 |
+
which matching target in the original sequence is the first item of the sequence,
|
| 258 |
+
while we skip the last logits as there is no matching target
|
| 259 |
+
"""
|
| 260 |
+
B, card, K, S = logits.shape
|
| 261 |
+
indexes, mask = self._build_reverted_sequence_scatter_indexes(
|
| 262 |
+
S, K, keep_only_valid_steps, is_model_output=True, device=logits.device
|
| 263 |
+
)
|
| 264 |
+
logits = logits.reshape(B, card, -1)
|
| 265 |
+
# we append the special token as the last index of our flattened z tensor
|
| 266 |
+
logits = torch.cat([logits, torch.zeros_like(logits[:, :, :1]) + special_token], dim=-1) # [B, card, K x S]
|
| 267 |
+
values = logits[:, :, indexes.view(-1)]
|
| 268 |
+
values = values.view(B, card, K, indexes.shape[-1])
|
| 269 |
+
return values, indexes, mask
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
class CodebooksPatternProvider(ABC):
|
| 273 |
+
"""Abstraction around providing pattern for interleaving codebooks.
|
| 274 |
+
|
| 275 |
+
The CodebooksPatternProvider abstraction allows to implement various strategies to
|
| 276 |
+
define interleaving pattern of sequences composed of multiple codebooks. For a given
|
| 277 |
+
number of codebooks `n_q`, the pattern provider can generate a specified pattern
|
| 278 |
+
corresponding to a sequence of `T` timesteps with `n_q` parallel codebooks. This pattern
|
| 279 |
+
can be used to construct a new sequence from the original codes respecting the specified
|
| 280 |
+
pattern. The pattern is defined as a list of list of code coordinates, code coordinate
|
| 281 |
+
being a tuple with the original timestep and codebook to build the new sequence.
|
| 282 |
+
Note that all patterns must start with an empty list that is then used to insert a first
|
| 283 |
+
sequence step of special tokens in the newly generated sequence.
|
| 284 |
+
|
| 285 |
+
Args:
|
| 286 |
+
n_q (int): number of codebooks.
|
| 287 |
+
cached (bool): if True, patterns for a given length are cached. In general
|
| 288 |
+
that should be true for efficiency reason to avoid synchronization points.
|
| 289 |
+
"""
|
| 290 |
+
def __init__(self, n_q: int, cached: bool = True):
|
| 291 |
+
assert n_q > 0
|
| 292 |
+
self.n_q = n_q
|
| 293 |
+
self.get_pattern = lru_cache(100)(self.get_pattern) # type: ignore
|
| 294 |
+
|
| 295 |
+
@abstractmethod
|
| 296 |
+
def get_pattern(self, timesteps: int) -> Pattern:
|
| 297 |
+
"""Builds pattern with specific interleaving between codebooks.
|
| 298 |
+
|
| 299 |
+
Args:
|
| 300 |
+
timesteps (int): Total number of timesteps.
|
| 301 |
+
"""
|
| 302 |
+
raise NotImplementedError()
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
class DelayedPatternProvider(CodebooksPatternProvider):
|
| 306 |
+
"""Provider for delayed pattern across delayed codebooks.
|
| 307 |
+
Codebooks are delayed in the sequence and sequence steps will contain codebooks
|
| 308 |
+
from different timesteps.
|
| 309 |
+
|
| 310 |
+
Example:
|
| 311 |
+
Taking timesteps=4 and n_q=3, delays=None, the multi-codebook sequence:
|
| 312 |
+
[[1, 2, 3, 4],
|
| 313 |
+
[1, 2, 3, 4],
|
| 314 |
+
[1, 2, 3, 4]]
|
| 315 |
+
The resulting sequence obtained from the returned pattern is:
|
| 316 |
+
[[S, 1, 2, 3, 4],
|
| 317 |
+
[S, S, 1, 2, 3],
|
| 318 |
+
[S, S, S, 1, 2]]
|
| 319 |
+
(with S being a special token)
|
| 320 |
+
|
| 321 |
+
Args:
|
| 322 |
+
n_q (int): Number of codebooks.
|
| 323 |
+
delays (list of int, optional): Delay for each of the codebooks.
|
| 324 |
+
If delays not defined, each codebook is delayed by 1 compared to the previous one.
|
| 325 |
+
flatten_first (int): Flatten the first N timesteps.
|
| 326 |
+
empty_initial (int): Prepend with N empty list of coordinates.
|
| 327 |
+
"""
|
| 328 |
+
def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None,
|
| 329 |
+
flatten_first: int = 0, empty_initial: int = 0):
|
| 330 |
+
super().__init__(n_q)
|
| 331 |
+
if delays is None:
|
| 332 |
+
delays = list(range(n_q))
|
| 333 |
+
self.delays = delays
|
| 334 |
+
self.flatten_first = flatten_first
|
| 335 |
+
self.empty_initial = empty_initial
|
| 336 |
+
assert len(self.delays) == self.n_q
|
| 337 |
+
assert sorted(self.delays) == self.delays
|
| 338 |
+
|
| 339 |
+
def get_pattern(self, timesteps: int) -> Pattern:
|
| 340 |
+
omit_special_token = self.empty_initial < 0
|
| 341 |
+
out: PatternLayout = [] if omit_special_token else [[]]
|
| 342 |
+
max_delay = max(self.delays)
|
| 343 |
+
if self.empty_initial:
|
| 344 |
+
out += [[] for _ in range(self.empty_initial)]
|
| 345 |
+
if self.flatten_first:
|
| 346 |
+
for t in range(min(timesteps, self.flatten_first)):
|
| 347 |
+
for q in range(self.n_q):
|
| 348 |
+
out.append([LayoutCoord(t, q)])
|
| 349 |
+
for t in range(self.flatten_first, timesteps + max_delay):
|
| 350 |
+
v = []
|
| 351 |
+
for q, delay in enumerate(self.delays):
|
| 352 |
+
t_for_q = t - delay
|
| 353 |
+
if t_for_q >= self.flatten_first:
|
| 354 |
+
v.append(LayoutCoord(t_for_q, q))
|
| 355 |
+
out.append(v)
|
| 356 |
+
return Pattern(out, n_q=self.n_q, timesteps=timesteps)
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
class ParallelPatternProvider(DelayedPatternProvider):
|
| 360 |
+
"""Provider for parallel pattern across codebooks.
|
| 361 |
+
This pattern provider is a special case of the delayed pattern with actually no delay,
|
| 362 |
+
hence delays=repeat(0, n_q).
|
| 363 |
+
|
| 364 |
+
Args:
|
| 365 |
+
n_q (int): Number of codebooks.
|
| 366 |
+
empty_initial (int): Prepend with N empty list of coordinates.
|
| 367 |
+
"""
|
| 368 |
+
def __init__(self, n_q: int, empty_initial: int = 0):
|
| 369 |
+
super().__init__(n_q, [0] * n_q, empty_initial=empty_initial)
|
| 370 |
+
|
| 371 |
+
|
| 372 |
+
class UnrolledPatternProvider(CodebooksPatternProvider):
|
| 373 |
+
"""Provider for unrolling codebooks pattern.
|
| 374 |
+
This pattern provider enables to represent the codebook flattened completely or only to some extend
|
| 375 |
+
while also specifying a given delay between the flattened codebooks representation, allowing to
|
| 376 |
+
unroll the codebooks in the sequence.
|
| 377 |
+
|
| 378 |
+
Example:
|
| 379 |
+
1. Flattening of the codebooks.
|
| 380 |
+
By default, the pattern provider will fully flatten the codebooks such as flattening=range(n_q),
|
| 381 |
+
taking n_q = 3 and timesteps = 4:
|
| 382 |
+
[[1, 2, 3, 4],
|
| 383 |
+
[1, 2, 3, 4],
|
| 384 |
+
[1, 2, 3, 4]]
|
| 385 |
+
will result into:
|
| 386 |
+
[[S, S, 1, S, S, 2, S, S, 3, S, S, 4],
|
| 387 |
+
[S, 1, S, S, 2, S, S, 3, S, S, 4, S],
|
| 388 |
+
[1, S, S, 2, S, S, 3, S, S, 4, S, S]]
|
| 389 |
+
2. Partial flattening of the codebooks. The ``flattening`` parameter allows to specify the inner step
|
| 390 |
+
for each of the codebook, allowing to define which codebook to flatten (or keep in parallel), for example
|
| 391 |
+
taking n_q = 3, timesteps = 4 and flattening = [0, 1, 1]:
|
| 392 |
+
[[1, 2, 3, 4],
|
| 393 |
+
[1, 2, 3, 4],
|
| 394 |
+
[1, 2, 3, 4]]
|
| 395 |
+
will result into:
|
| 396 |
+
[[S, 1, S, S, 2, S, S, 3, S, S, 4, S],
|
| 397 |
+
[S, 1, S, S, 2, S, S, 3, S, S, 4, S],
|
| 398 |
+
[1, S, S, 2, S, S, 3, S, S, 4, S, S]]
|
| 399 |
+
3. Flattening with delay. The ``delay`` parameter allows to further unroll the sequence of codebooks
|
| 400 |
+
allowing to specify the delay per codebook. Note that the delay between codebooks flattened to the
|
| 401 |
+
same inner timestep should be coherent. For example, taking n_q = 3, timesteps = 4, flattening = [0, 1, 1]
|
| 402 |
+
and delays = [0, 3, 3]:
|
| 403 |
+
[[1, 2, 3, 4],
|
| 404 |
+
[1, 2, 3, 4],
|
| 405 |
+
[1, 2, 3, 4]]
|
| 406 |
+
will result into:
|
| 407 |
+
[[S, S, S, 1, S, 2, S, 3, S, 4],
|
| 408 |
+
[S, S, S, 1, S, 2, S, 3, S, 4],
|
| 409 |
+
[1, 2, 3, S, 4, S, 5, S, 6, S]]
|
| 410 |
+
|
| 411 |
+
Args:
|
| 412 |
+
n_q (int): Number of codebooks.
|
| 413 |
+
flattening (list of int, optional): Flattening schema over the codebooks. If not defined,
|
| 414 |
+
the codebooks will be flattened to 1 codebook per step, meaning that the sequence will
|
| 415 |
+
have n_q extra steps for each timestep.
|
| 416 |
+
delays (list of int, optional): Delay for each of the codebooks. If not defined,
|
| 417 |
+
no delay is added and therefore will default to [0] * ``n_q``.
|
| 418 |
+
Note that two codebooks that will be flattened to the same inner step
|
| 419 |
+
should have the same delay, otherwise the pattern is considered as invalid.
|
| 420 |
+
"""
|
| 421 |
+
FlattenedCodebook = namedtuple('FlattenedCodebook', ['codebooks', 'delay'])
|
| 422 |
+
|
| 423 |
+
def __init__(self, n_q: int, flattening: tp.Optional[tp.List[int]] = None,
|
| 424 |
+
delays: tp.Optional[tp.List[int]] = None):
|
| 425 |
+
super().__init__(n_q)
|
| 426 |
+
if flattening is None:
|
| 427 |
+
flattening = list(range(n_q))
|
| 428 |
+
if delays is None:
|
| 429 |
+
delays = [0] * n_q
|
| 430 |
+
assert len(flattening) == n_q
|
| 431 |
+
assert len(delays) == n_q
|
| 432 |
+
assert sorted(flattening) == flattening
|
| 433 |
+
assert sorted(delays) == delays
|
| 434 |
+
self._flattened_codebooks = self._build_flattened_codebooks(delays, flattening)
|
| 435 |
+
self.max_delay = max(delays)
|
| 436 |
+
|
| 437 |
+
def _build_flattened_codebooks(self, delays: tp.List[int], flattening: tp.List[int]):
|
| 438 |
+
"""Build a flattened codebooks representation as a dictionary of inner step
|
| 439 |
+
and the actual codebook indices corresponding to the flattened codebook. For convenience, we
|
| 440 |
+
also store the delay associated to the flattened codebook to avoid maintaining an extra mapping.
|
| 441 |
+
"""
|
| 442 |
+
flattened_codebooks: dict = {}
|
| 443 |
+
for q, (inner_step, delay) in enumerate(zip(flattening, delays)):
|
| 444 |
+
if inner_step not in flattened_codebooks:
|
| 445 |
+
flat_codebook = UnrolledPatternProvider.FlattenedCodebook(codebooks=[q], delay=delay)
|
| 446 |
+
else:
|
| 447 |
+
flat_codebook = flattened_codebooks[inner_step]
|
| 448 |
+
assert flat_codebook.delay == delay, (
|
| 449 |
+
"Delay and flattening between codebooks is inconsistent: ",
|
| 450 |
+
"two codebooks flattened to the same position should have the same delay."
|
| 451 |
+
)
|
| 452 |
+
flat_codebook.codebooks.append(q)
|
| 453 |
+
flattened_codebooks[inner_step] = flat_codebook
|
| 454 |
+
return flattened_codebooks
|
| 455 |
+
|
| 456 |
+
@property
|
| 457 |
+
def _num_inner_steps(self):
|
| 458 |
+
"""Number of inner steps to unroll between timesteps in order to flatten the codebooks.
|
| 459 |
+
"""
|
| 460 |
+
return max([inner_step for inner_step in self._flattened_codebooks.keys()]) + 1
|
| 461 |
+
|
| 462 |
+
def num_virtual_steps(self, timesteps: int) -> int:
|
| 463 |
+
return timesteps * self._num_inner_steps + 1
|
| 464 |
+
|
| 465 |
+
def get_pattern(self, timesteps: int) -> Pattern:
|
| 466 |
+
"""Builds pattern for delay across codebooks.
|
| 467 |
+
|
| 468 |
+
Args:
|
| 469 |
+
timesteps (int): Total number of timesteps.
|
| 470 |
+
"""
|
| 471 |
+
# the PatternLayout is built as a tuple of sequence position and list of coordinates
|
| 472 |
+
# so that it can be reordered properly given the required delay between codebooks of given timesteps
|
| 473 |
+
indexed_out: list = [(-1, [])]
|
| 474 |
+
max_timesteps = timesteps + self.max_delay
|
| 475 |
+
for t in range(max_timesteps):
|
| 476 |
+
# for each timestep, we unroll the flattened codebooks,
|
| 477 |
+
# emitting the sequence step with the corresponding delay
|
| 478 |
+
for step in range(self._num_inner_steps):
|
| 479 |
+
if step in self._flattened_codebooks:
|
| 480 |
+
# we have codebooks at this virtual step to emit
|
| 481 |
+
step_codebooks = self._flattened_codebooks[step]
|
| 482 |
+
t_for_q = t + step_codebooks.delay
|
| 483 |
+
coords = [LayoutCoord(t, q) for q in step_codebooks.codebooks]
|
| 484 |
+
if t_for_q < max_timesteps and t < max_timesteps:
|
| 485 |
+
indexed_out.append((t_for_q, coords))
|
| 486 |
+
else:
|
| 487 |
+
# there is no codebook in this virtual step so we emit an empty list
|
| 488 |
+
indexed_out.append((t, []))
|
| 489 |
+
out = [coords for _, coords in sorted(indexed_out)]
|
| 490 |
+
return Pattern(out, n_q=self.n_q, timesteps=timesteps)
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
class CoarseFirstPattern(CodebooksPatternProvider):
|
| 494 |
+
"""First generates all the codebooks #1 (e.g. coarser), then the remaining ones,
|
| 495 |
+
potentially with delays.
|
| 496 |
+
|
| 497 |
+
..Warning:: You must always generate the full training duration at test time, for instance,
|
| 498 |
+
30 seconds, as otherwise, the fine codebooks will start being generated in an unexpected
|
| 499 |
+
location. This is due to the non causality of the remaining codebooks with respect to
|
| 500 |
+
the first ones.
|
| 501 |
+
|
| 502 |
+
Args:
|
| 503 |
+
n_q (int): Number of codebooks.
|
| 504 |
+
delays (list of int, optional): Delay for each of the codebooks.
|
| 505 |
+
If delays not defined, each codebook is delayed by 1 compared to the previous one.
|
| 506 |
+
"""
|
| 507 |
+
def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None):
|
| 508 |
+
super().__init__(n_q)
|
| 509 |
+
if delays is None:
|
| 510 |
+
delays = [0] * (n_q - 1)
|
| 511 |
+
self.delays = delays
|
| 512 |
+
assert len(self.delays) == self.n_q - 1
|
| 513 |
+
assert sorted(self.delays) == self.delays
|
| 514 |
+
|
| 515 |
+
def get_pattern(self, timesteps: int) -> Pattern:
|
| 516 |
+
out: PatternLayout = [[]]
|
| 517 |
+
for t in range(timesteps):
|
| 518 |
+
out.append([LayoutCoord(t, 0)])
|
| 519 |
+
max_delay = max(self.delays)
|
| 520 |
+
for t in range(timesteps + max_delay):
|
| 521 |
+
v = []
|
| 522 |
+
for q, delay in enumerate(self.delays):
|
| 523 |
+
t_for_q = t - delay
|
| 524 |
+
if t_for_q >= 0:
|
| 525 |
+
v.append(LayoutCoord(t_for_q, q + 1))
|
| 526 |
+
out.append(v)
|
| 527 |
+
return Pattern(out, n_q=self.n_q, timesteps=timesteps)
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
class MusicLMPattern(CodebooksPatternProvider):
|
| 531 |
+
"""Almost MusicLM style pattern. This is equivalent to full flattening
|
| 532 |
+
but in a different order.
|
| 533 |
+
|
| 534 |
+
Args:
|
| 535 |
+
n_q (int): Number of codebooks.
|
| 536 |
+
group_by (int): Number of codebooks to group together.
|
| 537 |
+
"""
|
| 538 |
+
def __init__(self, n_q: int, group_by: int = 2):
|
| 539 |
+
super().__init__(n_q)
|
| 540 |
+
self.group_by = group_by
|
| 541 |
+
|
| 542 |
+
def get_pattern(self, timesteps: int) -> Pattern:
|
| 543 |
+
out: PatternLayout = [[]]
|
| 544 |
+
for offset in range(0, self.n_q, self.group_by):
|
| 545 |
+
for t in range(timesteps):
|
| 546 |
+
for q in range(offset, offset + self.group_by):
|
| 547 |
+
out.append([LayoutCoord(t, q)])
|
| 548 |
+
return Pattern(out, n_q=self.n_q, timesteps=timesteps)
|
src/audiocraft/modules/conditioners.py
ADDED
|
@@ -0,0 +1,1416 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
from copy import deepcopy
|
| 9 |
+
from dataclasses import dataclass, field
|
| 10 |
+
from itertools import chain
|
| 11 |
+
import logging
|
| 12 |
+
import math
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
import random
|
| 15 |
+
import re
|
| 16 |
+
import typing as tp
|
| 17 |
+
import warnings
|
| 18 |
+
|
| 19 |
+
import einops
|
| 20 |
+
from num2words import num2words
|
| 21 |
+
import spacy
|
| 22 |
+
from transformers import RobertaTokenizer, T5EncoderModel, T5Tokenizer # type: ignore
|
| 23 |
+
import torch
|
| 24 |
+
from torch import nn
|
| 25 |
+
import torch.nn.functional as F
|
| 26 |
+
from torch.nn.utils.rnn import pad_sequence
|
| 27 |
+
|
| 28 |
+
from .chroma import ChromaExtractor
|
| 29 |
+
from .streaming import StreamingModule
|
| 30 |
+
from .transformer import create_sin_embedding
|
| 31 |
+
from ..data.audio import audio_read
|
| 32 |
+
from ..data.audio_dataset import SegmentInfo
|
| 33 |
+
from ..data.audio_utils import convert_audio
|
| 34 |
+
from ..environment import AudioCraftEnvironment
|
| 35 |
+
from ..quantization import ResidualVectorQuantizer
|
| 36 |
+
from ..utils.autocast import TorchAutocast
|
| 37 |
+
from ..utils.cache import EmbeddingCache
|
| 38 |
+
from ..utils.utils import collate, hash_trick, length_to_mask, load_clap_state_dict, warn_once
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
logger = logging.getLogger(__name__)
|
| 42 |
+
TextCondition = tp.Optional[str] # a text condition can be a string or None (if doesn't exist)
|
| 43 |
+
ConditionType = tp.Tuple[torch.Tensor, torch.Tensor] # condition, mask
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class WavCondition(tp.NamedTuple):
|
| 47 |
+
wav: torch.Tensor
|
| 48 |
+
length: torch.Tensor
|
| 49 |
+
sample_rate: tp.List[int]
|
| 50 |
+
path: tp.List[tp.Optional[str]] = []
|
| 51 |
+
seek_time: tp.List[tp.Optional[float]] = []
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
class JointEmbedCondition(tp.NamedTuple):
|
| 55 |
+
wav: torch.Tensor
|
| 56 |
+
text: tp.List[tp.Optional[str]]
|
| 57 |
+
length: torch.Tensor
|
| 58 |
+
sample_rate: tp.List[int]
|
| 59 |
+
path: tp.List[tp.Optional[str]] = []
|
| 60 |
+
seek_time: tp.List[tp.Optional[float]] = []
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
@dataclass
|
| 64 |
+
class ConditioningAttributes:
|
| 65 |
+
text: tp.Dict[str, tp.Optional[str]] = field(default_factory=dict)
|
| 66 |
+
wav: tp.Dict[str, WavCondition] = field(default_factory=dict)
|
| 67 |
+
joint_embed: tp.Dict[str, JointEmbedCondition] = field(default_factory=dict)
|
| 68 |
+
|
| 69 |
+
def __getitem__(self, item):
|
| 70 |
+
return getattr(self, item)
|
| 71 |
+
|
| 72 |
+
@property
|
| 73 |
+
def text_attributes(self):
|
| 74 |
+
return self.text.keys()
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def wav_attributes(self):
|
| 78 |
+
return self.wav.keys()
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def joint_embed_attributes(self):
|
| 82 |
+
return self.joint_embed.keys()
|
| 83 |
+
|
| 84 |
+
@property
|
| 85 |
+
def attributes(self):
|
| 86 |
+
return {
|
| 87 |
+
"text": self.text_attributes,
|
| 88 |
+
"wav": self.wav_attributes,
|
| 89 |
+
"joint_embed": self.joint_embed_attributes,
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
def to_flat_dict(self):
|
| 93 |
+
return {
|
| 94 |
+
**{f"text.{k}": v for k, v in self.text.items()},
|
| 95 |
+
**{f"wav.{k}": v for k, v in self.wav.items()},
|
| 96 |
+
**{f"joint_embed.{k}": v for k, v in self.joint_embed.items()}
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
@classmethod
|
| 100 |
+
def from_flat_dict(cls, x):
|
| 101 |
+
out = cls()
|
| 102 |
+
for k, v in x.items():
|
| 103 |
+
kind, att = k.split(".")
|
| 104 |
+
out[kind][att] = v
|
| 105 |
+
return out
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class SegmentWithAttributes(SegmentInfo):
|
| 109 |
+
"""Base class for all dataclasses that are used for conditioning.
|
| 110 |
+
All child classes should implement `to_condition_attributes` that converts
|
| 111 |
+
the existing attributes to a dataclass of type ConditioningAttributes.
|
| 112 |
+
"""
|
| 113 |
+
def to_condition_attributes(self) -> ConditioningAttributes:
|
| 114 |
+
raise NotImplementedError()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def nullify_condition(condition: ConditionType, dim: int = 1):
|
| 118 |
+
"""Transform an input condition to a null condition.
|
| 119 |
+
The way it is done by converting it to a single zero vector similarly
|
| 120 |
+
to how it is done inside WhiteSpaceTokenizer and NoopTokenizer.
|
| 121 |
+
|
| 122 |
+
Args:
|
| 123 |
+
condition (ConditionType): A tuple of condition and mask (tuple[torch.Tensor, torch.Tensor])
|
| 124 |
+
dim (int): The dimension that will be truncated (should be the time dimension)
|
| 125 |
+
WARNING!: dim should not be the batch dimension!
|
| 126 |
+
Returns:
|
| 127 |
+
ConditionType: A tuple of null condition and mask
|
| 128 |
+
"""
|
| 129 |
+
assert dim != 0, "dim cannot be the batch dimension!"
|
| 130 |
+
assert isinstance(condition, tuple) and \
|
| 131 |
+
isinstance(condition[0], torch.Tensor) and \
|
| 132 |
+
isinstance(condition[1], torch.Tensor), "'nullify_condition' got an unexpected input type!"
|
| 133 |
+
cond, mask = condition
|
| 134 |
+
B = cond.shape[0]
|
| 135 |
+
last_dim = cond.dim() - 1
|
| 136 |
+
out = cond.transpose(dim, last_dim)
|
| 137 |
+
out = 0. * out[..., :1]
|
| 138 |
+
out = out.transpose(dim, last_dim)
|
| 139 |
+
mask = torch.zeros((B, 1), device=out.device).int()
|
| 140 |
+
assert cond.dim() == out.dim()
|
| 141 |
+
return out, mask
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def nullify_wav(cond: WavCondition) -> WavCondition:
|
| 145 |
+
"""Transform a WavCondition to a nullified WavCondition.
|
| 146 |
+
It replaces the wav by a null tensor, forces its length to 0, and replaces metadata by dummy attributes.
|
| 147 |
+
|
| 148 |
+
Args:
|
| 149 |
+
cond (WavCondition): Wav condition with wav, tensor of shape [B, T].
|
| 150 |
+
Returns:
|
| 151 |
+
WavCondition: Nullified wav condition.
|
| 152 |
+
"""
|
| 153 |
+
null_wav, _ = nullify_condition((cond.wav, torch.zeros_like(cond.wav)), dim=cond.wav.dim() - 1)
|
| 154 |
+
return WavCondition(
|
| 155 |
+
wav=null_wav,
|
| 156 |
+
length=torch.tensor([0] * cond.wav.shape[0], device=cond.wav.device),
|
| 157 |
+
sample_rate=cond.sample_rate,
|
| 158 |
+
path=[None] * cond.wav.shape[0],
|
| 159 |
+
seek_time=[None] * cond.wav.shape[0],
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def nullify_joint_embed(embed: JointEmbedCondition) -> JointEmbedCondition:
|
| 164 |
+
"""Nullify the joint embedding condition by replacing it by a null tensor, forcing its length to 0,
|
| 165 |
+
and replacing metadata by dummy attributes.
|
| 166 |
+
|
| 167 |
+
Args:
|
| 168 |
+
cond (JointEmbedCondition): Joint embedding condition with wav and text, wav tensor of shape [B, C, T].
|
| 169 |
+
"""
|
| 170 |
+
null_wav, _ = nullify_condition((embed.wav, torch.zeros_like(embed.wav)), dim=embed.wav.dim() - 1)
|
| 171 |
+
return JointEmbedCondition(
|
| 172 |
+
wav=null_wav, text=[None] * len(embed.text),
|
| 173 |
+
length=torch.LongTensor([0]).to(embed.wav.device),
|
| 174 |
+
sample_rate=embed.sample_rate,
|
| 175 |
+
path=[None] * embed.wav.shape[0],
|
| 176 |
+
seek_time=[0] * embed.wav.shape[0],
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
class Tokenizer:
|
| 181 |
+
"""Base tokenizer implementation
|
| 182 |
+
(in case we want to introduce more advances tokenizers in the future).
|
| 183 |
+
"""
|
| 184 |
+
def __call__(self, texts: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 185 |
+
raise NotImplementedError()
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class WhiteSpaceTokenizer(Tokenizer):
|
| 189 |
+
"""This tokenizer should be used for natural language descriptions.
|
| 190 |
+
For example:
|
| 191 |
+
["he didn't, know he's going home.", 'shorter sentence'] =>
|
| 192 |
+
[[78, 62, 31, 4, 78, 25, 19, 34],
|
| 193 |
+
[59, 77, 0, 0, 0, 0, 0, 0]]
|
| 194 |
+
"""
|
| 195 |
+
PUNCTUATION = "?:!.,;"
|
| 196 |
+
|
| 197 |
+
def __init__(self, n_bins: int, pad_idx: int = 0, language: str = "en_core_web_sm",
|
| 198 |
+
lemma: bool = True, stopwords: bool = True) -> None:
|
| 199 |
+
self.n_bins = n_bins
|
| 200 |
+
self.pad_idx = pad_idx
|
| 201 |
+
self.lemma = lemma
|
| 202 |
+
self.stopwords = stopwords
|
| 203 |
+
try:
|
| 204 |
+
self.nlp = spacy.load(language)
|
| 205 |
+
except IOError:
|
| 206 |
+
spacy.cli.download(language) # type: ignore
|
| 207 |
+
self.nlp = spacy.load(language)
|
| 208 |
+
|
| 209 |
+
@tp.no_type_check
|
| 210 |
+
def __call__(self, texts: tp.List[tp.Optional[str]],
|
| 211 |
+
return_text: bool = False) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 212 |
+
"""Take a list of strings and convert them to a tensor of indices.
|
| 213 |
+
|
| 214 |
+
Args:
|
| 215 |
+
texts (list[str]): List of strings.
|
| 216 |
+
return_text (bool, optional): Whether to return text as additional tuple item. Defaults to False.
|
| 217 |
+
Returns:
|
| 218 |
+
tuple[torch.Tensor, torch.Tensor]:
|
| 219 |
+
- Indices of words in the LUT.
|
| 220 |
+
- And a mask indicating where the padding tokens are
|
| 221 |
+
"""
|
| 222 |
+
output, lengths = [], []
|
| 223 |
+
texts = deepcopy(texts)
|
| 224 |
+
for i, text in enumerate(texts):
|
| 225 |
+
# if current sample doesn't have a certain attribute, replace with pad token
|
| 226 |
+
if text is None:
|
| 227 |
+
output.append(torch.Tensor([self.pad_idx]))
|
| 228 |
+
lengths.append(0)
|
| 229 |
+
continue
|
| 230 |
+
|
| 231 |
+
# convert numbers to words
|
| 232 |
+
text = re.sub(r"(\d+)", lambda x: num2words(int(x.group(0))), text) # type: ignore
|
| 233 |
+
# normalize text
|
| 234 |
+
text = self.nlp(text) # type: ignore
|
| 235 |
+
# remove stopwords
|
| 236 |
+
if self.stopwords:
|
| 237 |
+
text = [w for w in text if not w.is_stop] # type: ignore
|
| 238 |
+
# remove punctuation
|
| 239 |
+
text = [w for w in text if w.text not in self.PUNCTUATION] # type: ignore
|
| 240 |
+
# lemmatize if needed
|
| 241 |
+
text = [getattr(t, "lemma_" if self.lemma else "text") for t in text] # type: ignore
|
| 242 |
+
|
| 243 |
+
texts[i] = " ".join(text)
|
| 244 |
+
lengths.append(len(text))
|
| 245 |
+
# convert to tensor
|
| 246 |
+
tokens = torch.Tensor([hash_trick(w, self.n_bins) for w in text])
|
| 247 |
+
output.append(tokens)
|
| 248 |
+
|
| 249 |
+
mask = length_to_mask(torch.IntTensor(lengths)).int()
|
| 250 |
+
padded_output = pad_sequence(output, padding_value=self.pad_idx).int().t()
|
| 251 |
+
if return_text:
|
| 252 |
+
return padded_output, mask, texts # type: ignore
|
| 253 |
+
return padded_output, mask
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
class NoopTokenizer(Tokenizer):
|
| 257 |
+
"""This tokenizer should be used for global conditioners such as: artist, genre, key, etc.
|
| 258 |
+
The difference between this and WhiteSpaceTokenizer is that NoopTokenizer does not split
|
| 259 |
+
strings, so "Jeff Buckley" will get it's own index. Whereas WhiteSpaceTokenizer will
|
| 260 |
+
split it to ["Jeff", "Buckley"] and return an index per word.
|
| 261 |
+
|
| 262 |
+
For example:
|
| 263 |
+
["Queen", "ABBA", "Jeff Buckley"] => [43, 55, 101]
|
| 264 |
+
["Metal", "Rock", "Classical"] => [0, 223, 51]
|
| 265 |
+
"""
|
| 266 |
+
def __init__(self, n_bins: int, pad_idx: int = 0):
|
| 267 |
+
self.n_bins = n_bins
|
| 268 |
+
self.pad_idx = pad_idx
|
| 269 |
+
|
| 270 |
+
def __call__(self, texts: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 271 |
+
output, lengths = [], []
|
| 272 |
+
for text in texts:
|
| 273 |
+
# if current sample doesn't have a certain attribute, replace with pad token
|
| 274 |
+
if text is None:
|
| 275 |
+
output.append(self.pad_idx)
|
| 276 |
+
lengths.append(0)
|
| 277 |
+
else:
|
| 278 |
+
output.append(hash_trick(text, self.n_bins))
|
| 279 |
+
lengths.append(1)
|
| 280 |
+
|
| 281 |
+
tokens = torch.LongTensor(output).unsqueeze(1)
|
| 282 |
+
mask = length_to_mask(torch.IntTensor(lengths)).int()
|
| 283 |
+
return tokens, mask
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
class BaseConditioner(nn.Module):
|
| 287 |
+
"""Base model for all conditioner modules.
|
| 288 |
+
We allow the output dim to be different than the hidden dim for two reasons:
|
| 289 |
+
1) keep our LUTs small when the vocab is large;
|
| 290 |
+
2) make all condition dims consistent.
|
| 291 |
+
|
| 292 |
+
Args:
|
| 293 |
+
dim (int): Hidden dim of the model.
|
| 294 |
+
output_dim (int): Output dim of the conditioner.
|
| 295 |
+
"""
|
| 296 |
+
def __init__(self, dim: int, output_dim: int):
|
| 297 |
+
super().__init__()
|
| 298 |
+
self.dim = dim
|
| 299 |
+
self.output_dim = output_dim
|
| 300 |
+
self.output_proj = nn.Linear(dim, output_dim)
|
| 301 |
+
|
| 302 |
+
def tokenize(self, *args, **kwargs) -> tp.Any:
|
| 303 |
+
"""Should be any part of the processing that will lead to a synchronization
|
| 304 |
+
point, e.g. BPE tokenization with transfer to the GPU.
|
| 305 |
+
|
| 306 |
+
The returned value will be saved and return later when calling forward().
|
| 307 |
+
"""
|
| 308 |
+
raise NotImplementedError()
|
| 309 |
+
|
| 310 |
+
def forward(self, inputs: tp.Any) -> ConditionType:
|
| 311 |
+
"""Gets input that should be used as conditioning (e.g, genre, description or a waveform).
|
| 312 |
+
Outputs a ConditionType, after the input data was embedded as a dense vector.
|
| 313 |
+
|
| 314 |
+
Returns:
|
| 315 |
+
ConditionType:
|
| 316 |
+
- A tensor of size [B, T, D] where B is the batch size, T is the length of the
|
| 317 |
+
output embedding and D is the dimension of the embedding.
|
| 318 |
+
- And a mask indicating where the padding tokens.
|
| 319 |
+
"""
|
| 320 |
+
raise NotImplementedError()
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
class TextConditioner(BaseConditioner):
|
| 324 |
+
...
|
| 325 |
+
|
| 326 |
+
|
| 327 |
+
class LUTConditioner(TextConditioner):
|
| 328 |
+
"""Lookup table TextConditioner.
|
| 329 |
+
|
| 330 |
+
Args:
|
| 331 |
+
n_bins (int): Number of bins.
|
| 332 |
+
dim (int): Hidden dim of the model (text-encoder/LUT).
|
| 333 |
+
output_dim (int): Output dim of the conditioner.
|
| 334 |
+
tokenizer (str): Name of the tokenizer.
|
| 335 |
+
pad_idx (int, optional): Index for padding token. Defaults to 0.
|
| 336 |
+
"""
|
| 337 |
+
def __init__(self, n_bins: int, dim: int, output_dim: int, tokenizer: str, pad_idx: int = 0):
|
| 338 |
+
super().__init__(dim, output_dim)
|
| 339 |
+
self.embed = nn.Embedding(n_bins, dim)
|
| 340 |
+
self.tokenizer: Tokenizer
|
| 341 |
+
if tokenizer == 'whitespace':
|
| 342 |
+
self.tokenizer = WhiteSpaceTokenizer(n_bins, pad_idx=pad_idx)
|
| 343 |
+
elif tokenizer == 'noop':
|
| 344 |
+
self.tokenizer = NoopTokenizer(n_bins, pad_idx=pad_idx)
|
| 345 |
+
else:
|
| 346 |
+
raise ValueError(f"unrecognized tokenizer `{tokenizer}`.")
|
| 347 |
+
|
| 348 |
+
def tokenize(self, x: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 349 |
+
device = self.embed.weight.device
|
| 350 |
+
tokens, mask = self.tokenizer(x)
|
| 351 |
+
tokens, mask = tokens.to(device), mask.to(device)
|
| 352 |
+
return tokens, mask
|
| 353 |
+
|
| 354 |
+
def forward(self, inputs: tp.Tuple[torch.Tensor, torch.Tensor]) -> ConditionType:
|
| 355 |
+
tokens, mask = inputs
|
| 356 |
+
embeds = self.embed(tokens)
|
| 357 |
+
embeds = self.output_proj(embeds)
|
| 358 |
+
embeds = (embeds * mask.unsqueeze(-1))
|
| 359 |
+
return embeds, mask
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
class T5Conditioner(TextConditioner):
|
| 363 |
+
"""T5-based TextConditioner.
|
| 364 |
+
|
| 365 |
+
Args:
|
| 366 |
+
name (str): Name of the T5 model.
|
| 367 |
+
output_dim (int): Output dim of the conditioner.
|
| 368 |
+
finetune (bool): Whether to fine-tune T5 at train time.
|
| 369 |
+
device (str): Device for T5 Conditioner.
|
| 370 |
+
autocast_dtype (tp.Optional[str], optional): Autocast dtype.
|
| 371 |
+
word_dropout (float, optional): Word dropout probability.
|
| 372 |
+
normalize_text (bool, optional): Whether to apply text normalization.
|
| 373 |
+
"""
|
| 374 |
+
MODELS = ["t5-small", "t5-base", "t5-large", "t5-3b", "t5-11b",
|
| 375 |
+
"google/flan-t5-small", "google/flan-t5-base", "google/flan-t5-large",
|
| 376 |
+
"google/flan-t5-xl", "google/flan-t5-xxl"]
|
| 377 |
+
MODELS_DIMS = {
|
| 378 |
+
"t5-small": 512,
|
| 379 |
+
"t5-base": 768,
|
| 380 |
+
"t5-large": 1024,
|
| 381 |
+
"t5-3b": 1024,
|
| 382 |
+
"t5-11b": 1024,
|
| 383 |
+
"google/flan-t5-small": 512,
|
| 384 |
+
"google/flan-t5-base": 768,
|
| 385 |
+
"google/flan-t5-large": 1024,
|
| 386 |
+
"google/flan-t5-3b": 1024,
|
| 387 |
+
"google/flan-t5-11b": 1024,
|
| 388 |
+
}
|
| 389 |
+
|
| 390 |
+
def __init__(self, name: str, output_dim: int, finetune: bool, device: str,
|
| 391 |
+
autocast_dtype: tp.Optional[str] = 'float32', word_dropout: float = 0.,
|
| 392 |
+
normalize_text: bool = False):
|
| 393 |
+
assert name in self.MODELS, f"Unrecognized t5 model name (should in {self.MODELS})"
|
| 394 |
+
super().__init__(self.MODELS_DIMS[name], output_dim)
|
| 395 |
+
self.device = device
|
| 396 |
+
self.name = name
|
| 397 |
+
self.finetune = finetune
|
| 398 |
+
self.word_dropout = word_dropout
|
| 399 |
+
if autocast_dtype is None or self.device == 'cpu':
|
| 400 |
+
self.autocast = TorchAutocast(enabled=False)
|
| 401 |
+
if self.device != 'cpu':
|
| 402 |
+
logger.warning("T5 has no autocast, this might lead to NaN")
|
| 403 |
+
else:
|
| 404 |
+
dtype = getattr(torch, autocast_dtype)
|
| 405 |
+
assert isinstance(dtype, torch.dtype)
|
| 406 |
+
logger.info(f"T5 will be evaluated with autocast as {autocast_dtype}")
|
| 407 |
+
self.autocast = TorchAutocast(enabled=True, device_type=self.device, dtype=dtype)
|
| 408 |
+
# Let's disable logging temporarily because T5 will vomit some errors otherwise.
|
| 409 |
+
# thanks https://gist.github.com/simon-weber/7853144
|
| 410 |
+
previous_level = logging.root.manager.disable
|
| 411 |
+
logging.disable(logging.ERROR)
|
| 412 |
+
with warnings.catch_warnings():
|
| 413 |
+
warnings.simplefilter("ignore")
|
| 414 |
+
try:
|
| 415 |
+
self.t5_tokenizer = T5Tokenizer.from_pretrained(name)
|
| 416 |
+
t5 = T5EncoderModel.from_pretrained(name).train(mode=finetune)
|
| 417 |
+
finally:
|
| 418 |
+
logging.disable(previous_level)
|
| 419 |
+
if finetune:
|
| 420 |
+
self.t5 = t5
|
| 421 |
+
else:
|
| 422 |
+
# this makes sure that the t5 models is not part
|
| 423 |
+
# of the saved checkpoint
|
| 424 |
+
self.__dict__['t5'] = t5.to(device)
|
| 425 |
+
|
| 426 |
+
self.normalize_text = normalize_text
|
| 427 |
+
if normalize_text:
|
| 428 |
+
self.text_normalizer = WhiteSpaceTokenizer(1, lemma=True, stopwords=True)
|
| 429 |
+
|
| 430 |
+
def tokenize(self, x: tp.List[tp.Optional[str]]) -> tp.Dict[str, torch.Tensor]:
|
| 431 |
+
# if current sample doesn't have a certain attribute, replace with empty string
|
| 432 |
+
entries: tp.List[str] = [xi if xi is not None else "" for xi in x]
|
| 433 |
+
if self.normalize_text:
|
| 434 |
+
_, _, entries = self.text_normalizer(entries, return_text=True)
|
| 435 |
+
if self.word_dropout > 0. and self.training:
|
| 436 |
+
new_entries = []
|
| 437 |
+
for entry in entries:
|
| 438 |
+
words = [word for word in entry.split(" ") if random.random() >= self.word_dropout]
|
| 439 |
+
new_entries.append(" ".join(words))
|
| 440 |
+
entries = new_entries
|
| 441 |
+
|
| 442 |
+
empty_idx = torch.LongTensor([i for i, xi in enumerate(entries) if xi == ""])
|
| 443 |
+
|
| 444 |
+
inputs = self.t5_tokenizer(entries, return_tensors='pt', padding=True).to(self.device)
|
| 445 |
+
mask = inputs['attention_mask']
|
| 446 |
+
mask[empty_idx, :] = 0 # zero-out index where the input is non-existant
|
| 447 |
+
return inputs
|
| 448 |
+
|
| 449 |
+
def forward(self, inputs: tp.Dict[str, torch.Tensor]) -> ConditionType:
|
| 450 |
+
mask = inputs['attention_mask']
|
| 451 |
+
with torch.set_grad_enabled(self.finetune), self.autocast:
|
| 452 |
+
embeds = self.t5(**inputs).last_hidden_state
|
| 453 |
+
embeds = self.output_proj(embeds.to(self.output_proj.weight))
|
| 454 |
+
embeds = (embeds * mask.unsqueeze(-1))
|
| 455 |
+
return embeds, mask
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
class WaveformConditioner(BaseConditioner):
|
| 459 |
+
"""Base class for all conditioners that take a waveform as input.
|
| 460 |
+
Classes that inherit must implement `_get_wav_embedding` that outputs
|
| 461 |
+
a continuous tensor, and `_downsampling_factor` that returns the down-sampling
|
| 462 |
+
factor of the embedding model.
|
| 463 |
+
|
| 464 |
+
Args:
|
| 465 |
+
dim (int): The internal representation dimension.
|
| 466 |
+
output_dim (int): Output dimension.
|
| 467 |
+
device (tp.Union[torch.device, str]): Device.
|
| 468 |
+
"""
|
| 469 |
+
def __init__(self, dim: int, output_dim: int, device: tp.Union[torch.device, str]):
|
| 470 |
+
super().__init__(dim, output_dim)
|
| 471 |
+
self.device = device
|
| 472 |
+
# if False no masking is done, used in ChromaStemConditioner when completing by periodicity a sample.
|
| 473 |
+
self._use_masking = True
|
| 474 |
+
|
| 475 |
+
def tokenize(self, x: WavCondition) -> WavCondition:
|
| 476 |
+
wav, length, sample_rate, path, seek_time = x
|
| 477 |
+
assert length is not None
|
| 478 |
+
return WavCondition(wav.to(self.device), length.to(self.device), sample_rate, path, seek_time)
|
| 479 |
+
|
| 480 |
+
def _get_wav_embedding(self, x: WavCondition) -> torch.Tensor:
|
| 481 |
+
"""Gets as input a WavCondition and returns a dense embedding."""
|
| 482 |
+
raise NotImplementedError()
|
| 483 |
+
|
| 484 |
+
def _downsampling_factor(self):
|
| 485 |
+
"""Returns the downsampling factor of the embedding model."""
|
| 486 |
+
raise NotImplementedError()
|
| 487 |
+
|
| 488 |
+
def forward(self, x: WavCondition) -> ConditionType:
|
| 489 |
+
"""Extract condition embedding and mask from a waveform and its metadata.
|
| 490 |
+
Args:
|
| 491 |
+
x (WavCondition): Waveform condition containing raw waveform and metadata.
|
| 492 |
+
Returns:
|
| 493 |
+
ConditionType: a dense vector representing the conditioning along with its mask
|
| 494 |
+
"""
|
| 495 |
+
wav, lengths, *_ = x
|
| 496 |
+
with torch.no_grad():
|
| 497 |
+
embeds = self._get_wav_embedding(x)
|
| 498 |
+
embeds = embeds.to(self.output_proj.weight)
|
| 499 |
+
embeds = self.output_proj(embeds)
|
| 500 |
+
|
| 501 |
+
if lengths is not None and self._use_masking:
|
| 502 |
+
lengths = lengths / self._downsampling_factor()
|
| 503 |
+
mask = length_to_mask(lengths, max_len=embeds.shape[1]).int() # type: ignore
|
| 504 |
+
else:
|
| 505 |
+
mask = torch.ones_like(embeds[..., 0])
|
| 506 |
+
embeds = (embeds * mask.unsqueeze(-1))
|
| 507 |
+
return embeds, mask
|
| 508 |
+
|
| 509 |
+
|
| 510 |
+
class ChromaStemConditioner(WaveformConditioner):
|
| 511 |
+
"""Chroma conditioner based on stems.
|
| 512 |
+
The ChromaStemConditioner uses DEMUCS to first filter out drums and bass, as
|
| 513 |
+
the drums and bass often dominate the chroma leading to the chroma features
|
| 514 |
+
not containing information about the melody.
|
| 515 |
+
|
| 516 |
+
Args:
|
| 517 |
+
output_dim (int): Output dimension for the conditioner.
|
| 518 |
+
sample_rate (int): Sample rate for the chroma extractor.
|
| 519 |
+
n_chroma (int): Number of chroma bins for the chroma extractor.
|
| 520 |
+
radix2_exp (int): Size of stft window for the chroma extractor (power of 2, e.g. 12 -> 2^12).
|
| 521 |
+
duration (int): duration used during training. This is later used for correct padding
|
| 522 |
+
in case we are using chroma as prefix.
|
| 523 |
+
match_len_on_eval (bool, optional): if True then all chromas are padded to the training
|
| 524 |
+
duration. Defaults to False.
|
| 525 |
+
eval_wavs (str, optional): path to a dataset manifest with waveform, this waveforms are used as
|
| 526 |
+
conditions during eval (for cases where we don't want to leak test conditions like MusicCaps).
|
| 527 |
+
Defaults to None.
|
| 528 |
+
n_eval_wavs (int, optional): limits the number of waveforms used for conditioning. Defaults to 0.
|
| 529 |
+
device (tp.Union[torch.device, str], optional): Device for the conditioner.
|
| 530 |
+
**kwargs: Additional parameters for the chroma extractor.
|
| 531 |
+
"""
|
| 532 |
+
def __init__(self, output_dim: int, sample_rate: int, n_chroma: int, radix2_exp: int,
|
| 533 |
+
duration: float, match_len_on_eval: bool = True, eval_wavs: tp.Optional[str] = None,
|
| 534 |
+
n_eval_wavs: int = 0, cache_path: tp.Optional[tp.Union[str, Path]] = None,
|
| 535 |
+
device: tp.Union[torch.device, str] = 'cpu', **kwargs):
|
| 536 |
+
from demucs import pretrained
|
| 537 |
+
super().__init__(dim=n_chroma, output_dim=output_dim, device=device)
|
| 538 |
+
self.autocast = TorchAutocast(enabled=device != 'cpu', device_type=self.device, dtype=torch.float32)
|
| 539 |
+
self.sample_rate = sample_rate
|
| 540 |
+
self.match_len_on_eval = match_len_on_eval
|
| 541 |
+
if match_len_on_eval:
|
| 542 |
+
self._use_masking = False
|
| 543 |
+
self.duration = duration
|
| 544 |
+
self.__dict__['demucs'] = pretrained.get_model('htdemucs').to(device)
|
| 545 |
+
stem_sources: list = self.demucs.sources # type: ignore
|
| 546 |
+
self.stem_indices = torch.LongTensor([stem_sources.index('vocals'), stem_sources.index('other')]).to(device)
|
| 547 |
+
self.chroma = ChromaExtractor(sample_rate=sample_rate, n_chroma=n_chroma,
|
| 548 |
+
radix2_exp=radix2_exp, **kwargs).to(device)
|
| 549 |
+
self.chroma_len = self._get_chroma_len()
|
| 550 |
+
self.eval_wavs: tp.Optional[torch.Tensor] = self._load_eval_wavs(eval_wavs, n_eval_wavs)
|
| 551 |
+
self.cache = None
|
| 552 |
+
if cache_path is not None:
|
| 553 |
+
self.cache = EmbeddingCache(Path(cache_path) / 'wav', self.device,
|
| 554 |
+
compute_embed_fn=self._get_full_chroma_for_cache,
|
| 555 |
+
extract_embed_fn=self._extract_chroma_chunk)
|
| 556 |
+
|
| 557 |
+
def _downsampling_factor(self) -> int:
|
| 558 |
+
return self.chroma.winhop
|
| 559 |
+
|
| 560 |
+
def _load_eval_wavs(self, path: tp.Optional[str], num_samples: int) -> tp.Optional[torch.Tensor]:
|
| 561 |
+
"""Load pre-defined waveforms from a json.
|
| 562 |
+
These waveforms will be used for chroma extraction during evaluation.
|
| 563 |
+
This is done to make the evaluation on MusicCaps fair (we shouldn't see the chromas of MusicCaps).
|
| 564 |
+
"""
|
| 565 |
+
if path is None:
|
| 566 |
+
return None
|
| 567 |
+
|
| 568 |
+
logger.info(f"Loading evaluation wavs from {path}")
|
| 569 |
+
from src.audiocraft.data.audio_dataset import AudioDataset
|
| 570 |
+
dataset: AudioDataset = AudioDataset.from_meta(
|
| 571 |
+
path, segment_duration=self.duration, min_audio_duration=self.duration,
|
| 572 |
+
sample_rate=self.sample_rate, channels=1)
|
| 573 |
+
|
| 574 |
+
if len(dataset) > 0:
|
| 575 |
+
eval_wavs = dataset.collater([dataset[i] for i in range(num_samples)]).to(self.device)
|
| 576 |
+
logger.info(f"Using {len(eval_wavs)} evaluation wavs for chroma-stem conditioner")
|
| 577 |
+
return eval_wavs
|
| 578 |
+
else:
|
| 579 |
+
raise ValueError("Could not find evaluation wavs, check lengths of wavs")
|
| 580 |
+
|
| 581 |
+
def reset_eval_wavs(self, eval_wavs: tp.Optional[torch.Tensor]) -> None:
|
| 582 |
+
self.eval_wavs = eval_wavs
|
| 583 |
+
|
| 584 |
+
def has_eval_wavs(self) -> bool:
|
| 585 |
+
return self.eval_wavs is not None
|
| 586 |
+
|
| 587 |
+
def _sample_eval_wavs(self, num_samples: int) -> torch.Tensor:
|
| 588 |
+
"""Sample wavs from a predefined list."""
|
| 589 |
+
assert self.eval_wavs is not None, "Cannot sample eval wavs as no eval wavs provided."
|
| 590 |
+
total_eval_wavs = len(self.eval_wavs)
|
| 591 |
+
out = self.eval_wavs
|
| 592 |
+
if num_samples > total_eval_wavs:
|
| 593 |
+
out = self.eval_wavs.repeat(num_samples // total_eval_wavs + 1, 1, 1)
|
| 594 |
+
return out[torch.randperm(len(out))][:num_samples]
|
| 595 |
+
|
| 596 |
+
def _get_chroma_len(self) -> int:
|
| 597 |
+
"""Get length of chroma during training."""
|
| 598 |
+
dummy_wav = torch.zeros((1, int(self.sample_rate * self.duration)), device=self.device)
|
| 599 |
+
dummy_chr = self.chroma(dummy_wav)
|
| 600 |
+
return dummy_chr.shape[1]
|
| 601 |
+
|
| 602 |
+
@torch.no_grad()
|
| 603 |
+
def _get_stemmed_wav(self, wav: torch.Tensor, sample_rate: int) -> torch.Tensor:
|
| 604 |
+
"""Get parts of the wav that holds the melody, extracting the main stems from the wav."""
|
| 605 |
+
from demucs.apply import apply_model
|
| 606 |
+
from demucs.audio import convert_audio
|
| 607 |
+
with self.autocast:
|
| 608 |
+
wav = convert_audio(
|
| 609 |
+
wav, sample_rate, self.demucs.samplerate, self.demucs.audio_channels) # type: ignore
|
| 610 |
+
stems = apply_model(self.demucs, wav, device=self.device)
|
| 611 |
+
stems = stems[:, self.stem_indices] # extract relevant stems for melody conditioning
|
| 612 |
+
mix_wav = stems.sum(1) # merge extracted stems to single waveform
|
| 613 |
+
mix_wav = convert_audio(mix_wav, self.demucs.samplerate, self.sample_rate, 1) # type: ignore
|
| 614 |
+
return mix_wav
|
| 615 |
+
|
| 616 |
+
@torch.no_grad()
|
| 617 |
+
def _extract_chroma(self, wav: torch.Tensor) -> torch.Tensor:
|
| 618 |
+
"""Extract chroma features from the waveform."""
|
| 619 |
+
with self.autocast:
|
| 620 |
+
return self.chroma(wav)
|
| 621 |
+
|
| 622 |
+
@torch.no_grad()
|
| 623 |
+
def _compute_wav_embedding(self, wav: torch.Tensor, sample_rate: int) -> torch.Tensor:
|
| 624 |
+
"""Compute wav embedding, applying stem and chroma extraction."""
|
| 625 |
+
# avoid 0-size tensors when we are working with null conds
|
| 626 |
+
if wav.shape[-1] == 1:
|
| 627 |
+
return self._extract_chroma(wav)
|
| 628 |
+
stems = self._get_stemmed_wav(wav, sample_rate)
|
| 629 |
+
chroma = self._extract_chroma(stems)
|
| 630 |
+
return chroma
|
| 631 |
+
|
| 632 |
+
@torch.no_grad()
|
| 633 |
+
def _get_full_chroma_for_cache(self, path: tp.Union[str, Path], x: WavCondition, idx: int) -> torch.Tensor:
|
| 634 |
+
"""Extract chroma from the whole audio waveform at the given path."""
|
| 635 |
+
wav, sr = audio_read(path)
|
| 636 |
+
wav = wav[None].to(self.device)
|
| 637 |
+
wav = convert_audio(wav, sr, self.sample_rate, to_channels=1)
|
| 638 |
+
chroma = self._compute_wav_embedding(wav, self.sample_rate)[0]
|
| 639 |
+
return chroma
|
| 640 |
+
|
| 641 |
+
def _extract_chroma_chunk(self, full_chroma: torch.Tensor, x: WavCondition, idx: int) -> torch.Tensor:
|
| 642 |
+
"""Extract a chunk of chroma from the full chroma derived from the full waveform."""
|
| 643 |
+
wav_length = x.wav.shape[-1]
|
| 644 |
+
seek_time = x.seek_time[idx]
|
| 645 |
+
assert seek_time is not None, (
|
| 646 |
+
"WavCondition seek_time is required "
|
| 647 |
+
"when extracting chroma chunks from pre-computed chroma.")
|
| 648 |
+
full_chroma = full_chroma.float()
|
| 649 |
+
frame_rate = self.sample_rate / self._downsampling_factor()
|
| 650 |
+
target_length = int(frame_rate * wav_length / self.sample_rate)
|
| 651 |
+
index = int(frame_rate * seek_time)
|
| 652 |
+
out = full_chroma[index: index + target_length]
|
| 653 |
+
out = F.pad(out[None], (0, 0, 0, target_length - out.shape[0]))[0]
|
| 654 |
+
return out.to(self.device)
|
| 655 |
+
|
| 656 |
+
@torch.no_grad()
|
| 657 |
+
def _get_wav_embedding(self, x: WavCondition) -> torch.Tensor:
|
| 658 |
+
"""Get the wav embedding from the WavCondition.
|
| 659 |
+
The conditioner will either extract the embedding on-the-fly computing it from the condition wav directly
|
| 660 |
+
or will rely on the embedding cache to load the pre-computed embedding if relevant.
|
| 661 |
+
"""
|
| 662 |
+
sampled_wav: tp.Optional[torch.Tensor] = None
|
| 663 |
+
if not self.training and self.eval_wavs is not None:
|
| 664 |
+
warn_once(logger, "Using precomputed evaluation wavs!")
|
| 665 |
+
sampled_wav = self._sample_eval_wavs(len(x.wav))
|
| 666 |
+
|
| 667 |
+
no_undefined_paths = all(p is not None for p in x.path)
|
| 668 |
+
no_nullified_cond = x.wav.shape[-1] > 1
|
| 669 |
+
if sampled_wav is not None:
|
| 670 |
+
chroma = self._compute_wav_embedding(sampled_wav, self.sample_rate)
|
| 671 |
+
elif self.cache is not None and no_undefined_paths and no_nullified_cond:
|
| 672 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 673 |
+
chroma = self.cache.get_embed_from_cache(paths, x)
|
| 674 |
+
else:
|
| 675 |
+
assert all(sr == x.sample_rate[0] for sr in x.sample_rate), "All sample rates in batch should be equal."
|
| 676 |
+
chroma = self._compute_wav_embedding(x.wav, x.sample_rate[0])
|
| 677 |
+
|
| 678 |
+
if self.match_len_on_eval:
|
| 679 |
+
B, T, C = chroma.shape
|
| 680 |
+
if T > self.chroma_len:
|
| 681 |
+
chroma = chroma[:, :self.chroma_len]
|
| 682 |
+
logger.debug(f"Chroma was truncated to match length! ({T} -> {chroma.shape[1]})")
|
| 683 |
+
elif T < self.chroma_len:
|
| 684 |
+
n_repeat = int(math.ceil(self.chroma_len / T))
|
| 685 |
+
chroma = chroma.repeat(1, n_repeat, 1)
|
| 686 |
+
chroma = chroma[:, :self.chroma_len]
|
| 687 |
+
logger.debug(f"Chroma was repeated to match length! ({T} -> {chroma.shape[1]})")
|
| 688 |
+
|
| 689 |
+
return chroma
|
| 690 |
+
|
| 691 |
+
def tokenize(self, x: WavCondition) -> WavCondition:
|
| 692 |
+
"""Apply WavConditioner tokenization and populate cache if needed."""
|
| 693 |
+
x = super().tokenize(x)
|
| 694 |
+
no_undefined_paths = all(p is not None for p in x.path)
|
| 695 |
+
if self.cache is not None and no_undefined_paths:
|
| 696 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 697 |
+
self.cache.populate_embed_cache(paths, x)
|
| 698 |
+
return x
|
| 699 |
+
|
| 700 |
+
|
| 701 |
+
class JointEmbeddingConditioner(BaseConditioner):
|
| 702 |
+
"""Joint embedding conditioning supporting both audio or text conditioning.
|
| 703 |
+
|
| 704 |
+
Args:
|
| 705 |
+
dim (int): Dimension.
|
| 706 |
+
output_dim (int): Output dimension.
|
| 707 |
+
device (str): Device.
|
| 708 |
+
attribute (str): Attribute used by the conditioner.
|
| 709 |
+
autocast_dtype (str): Autocast for the conditioner.
|
| 710 |
+
quantize (bool): Whether to quantize the CLAP embedding.
|
| 711 |
+
n_q (int): Number of residual quantizers (used if quantize is true).
|
| 712 |
+
bins (int): Quantizers' codebooks size (used if quantize is true).
|
| 713 |
+
kwargs: Additional parameters for residual vector quantizer.
|
| 714 |
+
"""
|
| 715 |
+
def __init__(self, dim: int, output_dim: int, device: str, attribute: str,
|
| 716 |
+
autocast_dtype: tp.Optional[str] = 'float32', quantize: bool = True,
|
| 717 |
+
n_q: int = 12, bins: int = 1024, **kwargs):
|
| 718 |
+
super().__init__(dim=dim, output_dim=output_dim)
|
| 719 |
+
self.device = device
|
| 720 |
+
self.attribute = attribute
|
| 721 |
+
if autocast_dtype is None or device == 'cpu':
|
| 722 |
+
self.autocast = TorchAutocast(enabled=False)
|
| 723 |
+
logger.warning("JointEmbeddingConditioner has no autocast, this might lead to NaN.")
|
| 724 |
+
else:
|
| 725 |
+
dtype = getattr(torch, autocast_dtype)
|
| 726 |
+
assert isinstance(dtype, torch.dtype)
|
| 727 |
+
logger.info(f"JointEmbeddingConditioner will be evaluated with autocast as {autocast_dtype}.")
|
| 728 |
+
self.autocast = TorchAutocast(enabled=True, device_type=self.device, dtype=dtype)
|
| 729 |
+
# residual vector quantizer to discretize the conditioned embedding
|
| 730 |
+
self.quantizer: tp.Optional[ResidualVectorQuantizer] = None
|
| 731 |
+
if quantize:
|
| 732 |
+
self.quantizer = ResidualVectorQuantizer(dim, n_q=n_q, bins=bins, **kwargs)
|
| 733 |
+
|
| 734 |
+
def _get_embed(self, x: JointEmbedCondition) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 735 |
+
"""Get joint embedding in latent space from the inputs.
|
| 736 |
+
|
| 737 |
+
Returns:
|
| 738 |
+
tuple[torch.Tensor, torch.Tensor]: Tensor for the latent embedding
|
| 739 |
+
and corresponding empty indexes.
|
| 740 |
+
"""
|
| 741 |
+
raise NotImplementedError()
|
| 742 |
+
|
| 743 |
+
def forward(self, x: JointEmbedCondition) -> ConditionType:
|
| 744 |
+
with self.autocast:
|
| 745 |
+
embed, empty_idx = self._get_embed(x)
|
| 746 |
+
if self.quantizer is not None:
|
| 747 |
+
embed = embed.view(-1, self.dim, 1)
|
| 748 |
+
q_res = self.quantizer(embed, frame_rate=1)
|
| 749 |
+
out_embed = q_res.x.view(-1, self.dim)
|
| 750 |
+
else:
|
| 751 |
+
out_embed = embed
|
| 752 |
+
out_embed = self.output_proj(out_embed).view(-1, 1, self.output_dim)
|
| 753 |
+
mask = torch.ones(*out_embed.shape[:2], device=out_embed.device)
|
| 754 |
+
mask[empty_idx, :] = 0 # zero-out index where the input is non-existant
|
| 755 |
+
out_embed = (out_embed * mask.unsqueeze(-1))
|
| 756 |
+
return out_embed, mask
|
| 757 |
+
|
| 758 |
+
def tokenize(self, x: JointEmbedCondition) -> JointEmbedCondition:
|
| 759 |
+
return x
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
class CLAPEmbeddingConditioner(JointEmbeddingConditioner):
|
| 763 |
+
"""Joint Embedding conditioner based on pre-trained CLAP model.
|
| 764 |
+
|
| 765 |
+
This CLAP-based conditioner supports a caching mechanism
|
| 766 |
+
over the computed embeddings for faster training.
|
| 767 |
+
|
| 768 |
+
Args:
|
| 769 |
+
dim (int): Dimension.
|
| 770 |
+
output_dim (int): Output dimension.
|
| 771 |
+
device (str): Device.
|
| 772 |
+
attribute (str): Attribute used by the conditioner.
|
| 773 |
+
quantize (bool): Whether to quantize the CLAP embedding.
|
| 774 |
+
n_q (int): Number of residual quantizers (used if quantize is true).
|
| 775 |
+
bins (int): Quantizers' codebooks size (used if quantize is true).
|
| 776 |
+
checkpoint (str): Path to CLAP checkpoint.
|
| 777 |
+
model_arch (str): CLAP model architecture.
|
| 778 |
+
enable_fusion (bool): Enable fusion for CLAP model.
|
| 779 |
+
sample_rate (int): Sample rate used by CLAP model.
|
| 780 |
+
max_audio_length (float): Maximum audio length for CLAP model.
|
| 781 |
+
audio_stride (float): Stride to use for getting a CLAP embedding on the full sequence.
|
| 782 |
+
normalize (bool): Whether to normalize the CLAP embedding.
|
| 783 |
+
text_p (float): Probability of using text representation instead of audio at train time.
|
| 784 |
+
batch_size (Optional[int]): Batch size for CLAP embedding computation.
|
| 785 |
+
autocast_dtype (str): Autocast for the conditioner.
|
| 786 |
+
cache_path (Optional[str]): Path for pre-computed embeddings caching.
|
| 787 |
+
kwargs: Additional parameters for residual vector quantizer.
|
| 788 |
+
"""
|
| 789 |
+
def __init__(self, dim: int, output_dim: int, device: str, attribute: str,
|
| 790 |
+
quantize: bool, n_q: int, bins: int, checkpoint: tp.Union[str, Path], model_arch: str,
|
| 791 |
+
enable_fusion: bool, sample_rate: int, max_audio_length: int, audio_stride: int,
|
| 792 |
+
normalize: bool, text_p: bool, batch_size: tp.Optional[int] = None,
|
| 793 |
+
autocast_dtype: tp.Optional[str] = 'float32', cache_path: tp.Optional[str] = None, **kwargs):
|
| 794 |
+
try:
|
| 795 |
+
import laion_clap # type: ignore
|
| 796 |
+
except ImportError:
|
| 797 |
+
raise ImportError("Please install CLAP to use the CLAPEmbeddingConditioner: 'pip install laion_clap'")
|
| 798 |
+
warnings.warn("Sample rate for CLAP conditioner was fixed in version v1.1.0, (from 44.1 to 48 kHz). "
|
| 799 |
+
"Please retrain all models.")
|
| 800 |
+
checkpoint = AudioCraftEnvironment.resolve_reference_path(checkpoint)
|
| 801 |
+
clap_tokenize = RobertaTokenizer.from_pretrained('roberta-base')
|
| 802 |
+
clap_model = laion_clap.CLAP_Module(enable_fusion=enable_fusion, amodel=model_arch)
|
| 803 |
+
load_clap_state_dict(clap_model, checkpoint)
|
| 804 |
+
clap_model.eval()
|
| 805 |
+
clap_model.to(device)
|
| 806 |
+
super().__init__(dim=dim, output_dim=output_dim, device=device, attribute=attribute,
|
| 807 |
+
autocast_dtype=autocast_dtype, quantize=quantize, n_q=n_q, bins=bins,
|
| 808 |
+
**kwargs)
|
| 809 |
+
self.checkpoint = checkpoint
|
| 810 |
+
self.enable_fusion = enable_fusion
|
| 811 |
+
self.model_arch = model_arch
|
| 812 |
+
self.clap: laion_clap.CLAP_Module
|
| 813 |
+
self.clap_tokenize: RobertaTokenizer
|
| 814 |
+
self.clap_sample_rate = sample_rate
|
| 815 |
+
self.clap_max_frames = int(self.clap_sample_rate * max_audio_length)
|
| 816 |
+
self.clap_stride = int(self.clap_sample_rate * audio_stride)
|
| 817 |
+
self.batch_size = batch_size or 1
|
| 818 |
+
self.normalize = normalize
|
| 819 |
+
self.text_p = text_p
|
| 820 |
+
self.__dict__['clap_tokenize'] = clap_tokenize
|
| 821 |
+
self.__dict__['clap'] = clap_model
|
| 822 |
+
self.wav_cache, self.text_cache = None, None
|
| 823 |
+
if cache_path is not None:
|
| 824 |
+
self.wav_cache = EmbeddingCache(Path(cache_path) / 'wav', self.device,
|
| 825 |
+
compute_embed_fn=self._get_wav_embedding_for_cache,
|
| 826 |
+
extract_embed_fn=self._extract_wav_embedding_chunk)
|
| 827 |
+
self.text_cache = EmbeddingCache(Path(cache_path) / 'text', self.device,
|
| 828 |
+
compute_embed_fn=self._get_text_embedding_for_cache)
|
| 829 |
+
|
| 830 |
+
def _tokenizer(self, texts: tp.Union[str, tp.List[str]]) -> dict:
|
| 831 |
+
# we use the default params from CLAP module here as well
|
| 832 |
+
return self.clap_tokenize(texts, padding="max_length", truncation=True, max_length=77, return_tensors="pt")
|
| 833 |
+
|
| 834 |
+
def _compute_text_embedding(self, text: tp.List[str]) -> torch.Tensor:
|
| 835 |
+
"""Compute text embedding from CLAP model on a given a batch of text.
|
| 836 |
+
|
| 837 |
+
Args:
|
| 838 |
+
text (list[str]): List of text for the batch, with B items.
|
| 839 |
+
Returns:
|
| 840 |
+
torch.Tensor: CLAP embedding derived from text, of shape [B, 1, D], with D the CLAP embedding dimension.
|
| 841 |
+
"""
|
| 842 |
+
with torch.no_grad():
|
| 843 |
+
embed = self.clap.get_text_embedding(text, tokenizer=self._tokenizer, use_tensor=True)
|
| 844 |
+
return embed.view(embed.size(0), 1, embed.size(-1))
|
| 845 |
+
|
| 846 |
+
def _get_text_embedding_for_cache(self, path: tp.Union[Path, str],
|
| 847 |
+
x: JointEmbedCondition, idx: int) -> torch.Tensor:
|
| 848 |
+
"""Get text embedding function for the cache."""
|
| 849 |
+
text = x.text[idx]
|
| 850 |
+
text = text if text is not None else ""
|
| 851 |
+
return self._compute_text_embedding([text])[0]
|
| 852 |
+
|
| 853 |
+
def _preprocess_wav(self, wav: torch.Tensor, length: torch.Tensor, sample_rates: tp.List[int]) -> torch.Tensor:
|
| 854 |
+
"""Preprocess wav to expected format by CLAP model.
|
| 855 |
+
|
| 856 |
+
Args:
|
| 857 |
+
wav (torch.Tensor): Audio wav, of shape [B, C, T].
|
| 858 |
+
length (torch.Tensor): Actual length of the audio for each item in the batch, of shape [B].
|
| 859 |
+
sample_rates (list[int]): Sample rates for each sample in the batch
|
| 860 |
+
Returns:
|
| 861 |
+
torch.Tensor: Audio wav of shape [B, T].
|
| 862 |
+
"""
|
| 863 |
+
assert wav.dim() == 3, "Expecting wav to be [B, C, T]"
|
| 864 |
+
if sample_rates is not None:
|
| 865 |
+
_wav = []
|
| 866 |
+
for i, audio in enumerate(wav):
|
| 867 |
+
sr = sample_rates[i]
|
| 868 |
+
audio = convert_audio(audio, from_rate=sr, to_rate=self.clap_sample_rate, to_channels=1)
|
| 869 |
+
_wav.append(audio)
|
| 870 |
+
wav = torch.stack(_wav, dim=0)
|
| 871 |
+
wav = wav.mean(dim=1)
|
| 872 |
+
return wav
|
| 873 |
+
|
| 874 |
+
def _compute_wav_embedding(self, wav: torch.Tensor, length: torch.Tensor,
|
| 875 |
+
sample_rates: tp.List[int], reduce_mean: bool = False) -> torch.Tensor:
|
| 876 |
+
"""Compute audio wave embedding from CLAP model.
|
| 877 |
+
|
| 878 |
+
Since CLAP operates on a fixed sequence length audio inputs and we need to process longer audio sequences,
|
| 879 |
+
we calculate the wav embeddings on `clap_max_frames` windows with `clap_stride`-second stride and
|
| 880 |
+
average the resulting embeddings.
|
| 881 |
+
|
| 882 |
+
Args:
|
| 883 |
+
wav (torch.Tensor): Audio wav, of shape [B, C, T].
|
| 884 |
+
length (torch.Tensor): Actual length of the audio for each item in the batch, of shape [B].
|
| 885 |
+
sample_rates (list[int]): Sample rates for each sample in the batch.
|
| 886 |
+
reduce_mean (bool): Whether to get the average tensor.
|
| 887 |
+
Returns:
|
| 888 |
+
torch.Tensor: Audio embedding of shape [B, F, D], F being the number of chunks, D the dimension.
|
| 889 |
+
"""
|
| 890 |
+
with torch.no_grad():
|
| 891 |
+
wav = self._preprocess_wav(wav, length, sample_rates)
|
| 892 |
+
B, T = wav.shape
|
| 893 |
+
if T >= self.clap_max_frames:
|
| 894 |
+
wav = wav.unfold(-1, self.clap_max_frames, self.clap_stride) # [B, F, T]
|
| 895 |
+
else:
|
| 896 |
+
wav = wav.view(-1, 1, T) # [B, F, T] with F=1
|
| 897 |
+
wav = einops.rearrange(wav, 'b f t -> (b f) t')
|
| 898 |
+
embed_list = []
|
| 899 |
+
for i in range(0, wav.size(0), self.batch_size):
|
| 900 |
+
_wav = wav[i:i+self.batch_size, ...]
|
| 901 |
+
_embed = self.clap.get_audio_embedding_from_data(_wav, use_tensor=True)
|
| 902 |
+
embed_list.append(_embed)
|
| 903 |
+
embed = torch.cat(embed_list, dim=0)
|
| 904 |
+
embed = einops.rearrange(embed, '(b f) d -> b f d', b=B)
|
| 905 |
+
if reduce_mean:
|
| 906 |
+
embed = embed.mean(dim=1, keepdim=True)
|
| 907 |
+
return embed # [B, F, D] with F=1 if reduce_mean is True
|
| 908 |
+
|
| 909 |
+
def _get_wav_embedding_for_cache(self, path: tp.Union[str, Path],
|
| 910 |
+
x: JointEmbedCondition, idx: int) -> torch.Tensor:
|
| 911 |
+
"""Compute audio wave embedding for the cache.
|
| 912 |
+
The embedding is computed on a given audio read from file.
|
| 913 |
+
|
| 914 |
+
Args:
|
| 915 |
+
path (str or Path): Path to the full audio file.
|
| 916 |
+
Returns:
|
| 917 |
+
torch.Tensor: Single-item tensor of shape [F, D], F being the number of chunks, D the dimension.
|
| 918 |
+
"""
|
| 919 |
+
wav, sr = audio_read(path) # [C, T]
|
| 920 |
+
wav = wav.unsqueeze(0).to(self.device) # [1, C, T]
|
| 921 |
+
wav_len = torch.LongTensor([wav.shape[-1]]).to(self.device)
|
| 922 |
+
embed = self._compute_wav_embedding(wav, wav_len, [sr], reduce_mean=False) # [B, F, D]
|
| 923 |
+
return embed.squeeze(0) # [F, D]
|
| 924 |
+
|
| 925 |
+
def _extract_wav_embedding_chunk(self, full_embed: torch.Tensor, x: JointEmbedCondition, idx: int) -> torch.Tensor:
|
| 926 |
+
"""Extract the chunk of embedding matching the seek_time and length from the full CLAP audio embedding.
|
| 927 |
+
|
| 928 |
+
Args:
|
| 929 |
+
full_embed (torch.Tensor): CLAP embedding computed on the full wave, of shape [F, D].
|
| 930 |
+
x (JointEmbedCondition): Joint embedding condition for the full batch.
|
| 931 |
+
idx (int): Index considered for the given embedding to extract.
|
| 932 |
+
Returns:
|
| 933 |
+
torch.Tensor: Wav embedding averaged on sliding window, of shape [1, D].
|
| 934 |
+
"""
|
| 935 |
+
sample_rate = x.sample_rate[idx]
|
| 936 |
+
seek_time = x.seek_time[idx]
|
| 937 |
+
seek_time = 0. if seek_time is None else seek_time
|
| 938 |
+
clap_stride = int(self.clap_stride / self.clap_sample_rate) * sample_rate
|
| 939 |
+
end_seek_time = seek_time + self.clap_max_frames / self.clap_sample_rate
|
| 940 |
+
start_offset = int(seek_time * sample_rate // clap_stride)
|
| 941 |
+
end_offset = int(end_seek_time * sample_rate // clap_stride)
|
| 942 |
+
wav_embed = full_embed[start_offset:end_offset, ...]
|
| 943 |
+
wav_embed = wav_embed.mean(dim=0, keepdim=True)
|
| 944 |
+
return wav_embed.to(self.device) # [F, D]
|
| 945 |
+
|
| 946 |
+
def _get_text_embedding(self, x: JointEmbedCondition) -> torch.Tensor:
|
| 947 |
+
"""Get CLAP embedding from a batch of text descriptions."""
|
| 948 |
+
no_nullified_cond = x.wav.shape[-1] > 1 # we don't want to read from cache when condition dropout
|
| 949 |
+
if self.text_cache is not None and no_nullified_cond:
|
| 950 |
+
assert all(p is not None for p in x.path), "Cache requires all JointEmbedCondition paths to be provided"
|
| 951 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 952 |
+
embed = self.text_cache.get_embed_from_cache(paths, x)
|
| 953 |
+
else:
|
| 954 |
+
text = [xi if xi is not None else "" for xi in x.text]
|
| 955 |
+
embed = self._compute_text_embedding(text)
|
| 956 |
+
if self.normalize:
|
| 957 |
+
embed = torch.nn.functional.normalize(embed, p=2.0, dim=-1)
|
| 958 |
+
return embed
|
| 959 |
+
|
| 960 |
+
def _get_wav_embedding(self, x: JointEmbedCondition) -> torch.Tensor:
|
| 961 |
+
"""Get CLAP embedding from a batch of audio tensors (and corresponding sample rates)."""
|
| 962 |
+
no_undefined_paths = all(p is not None for p in x.path)
|
| 963 |
+
no_nullified_cond = x.wav.shape[-1] > 1 # we don't want to read from cache when condition dropout
|
| 964 |
+
if self.wav_cache is not None and no_undefined_paths and no_nullified_cond:
|
| 965 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 966 |
+
embed = self.wav_cache.get_embed_from_cache(paths, x)
|
| 967 |
+
else:
|
| 968 |
+
embed = self._compute_wav_embedding(x.wav, x.length, x.sample_rate, reduce_mean=True)
|
| 969 |
+
if self.normalize:
|
| 970 |
+
embed = torch.nn.functional.normalize(embed, p=2.0, dim=-1)
|
| 971 |
+
return embed
|
| 972 |
+
|
| 973 |
+
def tokenize(self, x: JointEmbedCondition) -> JointEmbedCondition:
|
| 974 |
+
# Trying to limit as much as possible sync points when the cache is warm.
|
| 975 |
+
no_undefined_paths = all(p is not None for p in x.path)
|
| 976 |
+
if self.wav_cache is not None and no_undefined_paths:
|
| 977 |
+
assert all([p is not None for p in x.path]), "Cache requires all JointEmbedCondition paths to be provided"
|
| 978 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 979 |
+
self.wav_cache.populate_embed_cache(paths, x)
|
| 980 |
+
if self.text_cache is not None and no_undefined_paths:
|
| 981 |
+
assert all([p is not None for p in x.path]), "Cache requires all JointEmbedCondition paths to be provided"
|
| 982 |
+
paths = [Path(p) for p in x.path if p is not None]
|
| 983 |
+
self.text_cache.populate_embed_cache(paths, x)
|
| 984 |
+
return x
|
| 985 |
+
|
| 986 |
+
def _get_embed(self, x: JointEmbedCondition) -> tp.Tuple[torch.Tensor, torch.Tensor]:
|
| 987 |
+
"""Extract shared latent representation from either the wav or the text using CLAP."""
|
| 988 |
+
# decide whether to use text embedding at train time or not
|
| 989 |
+
use_text_embed = random.random() < self.text_p
|
| 990 |
+
if self.training and not use_text_embed:
|
| 991 |
+
embed = self._get_wav_embedding(x)
|
| 992 |
+
empty_idx = torch.LongTensor([]) # we assume we always have the audio wav
|
| 993 |
+
else:
|
| 994 |
+
embed = self._get_text_embedding(x)
|
| 995 |
+
empty_idx = torch.LongTensor([i for i, xi in enumerate(x.text) if xi is None or xi == ""])
|
| 996 |
+
return embed, empty_idx
|
| 997 |
+
|
| 998 |
+
|
| 999 |
+
def dropout_condition(sample: ConditioningAttributes, condition_type: str, condition: str) -> ConditioningAttributes:
|
| 1000 |
+
"""Utility function for nullifying an attribute inside an ConditioningAttributes object.
|
| 1001 |
+
If the condition is of type "wav", then nullify it using `nullify_condition` function.
|
| 1002 |
+
If the condition is of any other type, set its value to None.
|
| 1003 |
+
Works in-place.
|
| 1004 |
+
"""
|
| 1005 |
+
if condition_type not in ['text', 'wav', 'joint_embed']:
|
| 1006 |
+
raise ValueError(
|
| 1007 |
+
"dropout_condition got an unexpected condition type!"
|
| 1008 |
+
f" expected 'text', 'wav' or 'joint_embed' but got '{condition_type}'"
|
| 1009 |
+
)
|
| 1010 |
+
|
| 1011 |
+
if condition not in getattr(sample, condition_type):
|
| 1012 |
+
raise ValueError(
|
| 1013 |
+
"dropout_condition received an unexpected condition!"
|
| 1014 |
+
f" expected wav={sample.wav.keys()} and text={sample.text.keys()}"
|
| 1015 |
+
f" but got '{condition}' of type '{condition_type}'!"
|
| 1016 |
+
)
|
| 1017 |
+
|
| 1018 |
+
if condition_type == 'wav':
|
| 1019 |
+
wav_cond = sample.wav[condition]
|
| 1020 |
+
sample.wav[condition] = nullify_wav(wav_cond)
|
| 1021 |
+
elif condition_type == 'joint_embed':
|
| 1022 |
+
embed = sample.joint_embed[condition]
|
| 1023 |
+
sample.joint_embed[condition] = nullify_joint_embed(embed)
|
| 1024 |
+
else:
|
| 1025 |
+
sample.text[condition] = None
|
| 1026 |
+
|
| 1027 |
+
return sample
|
| 1028 |
+
|
| 1029 |
+
|
| 1030 |
+
class DropoutModule(nn.Module):
|
| 1031 |
+
"""Base module for all dropout modules."""
|
| 1032 |
+
def __init__(self, seed: int = 1234):
|
| 1033 |
+
super().__init__()
|
| 1034 |
+
self.rng = torch.Generator()
|
| 1035 |
+
self.rng.manual_seed(seed)
|
| 1036 |
+
|
| 1037 |
+
|
| 1038 |
+
class AttributeDropout(DropoutModule):
|
| 1039 |
+
"""Dropout with a given probability per attribute.
|
| 1040 |
+
This is different from the behavior of ClassifierFreeGuidanceDropout as this allows for attributes
|
| 1041 |
+
to be dropped out separately. For example, "artist" can be dropped while "genre" remains.
|
| 1042 |
+
This is in contrast to ClassifierFreeGuidanceDropout where if "artist" is dropped "genre"
|
| 1043 |
+
must also be dropped.
|
| 1044 |
+
|
| 1045 |
+
Args:
|
| 1046 |
+
p (tp.Dict[str, float]): A dict mapping between attributes and dropout probability. For example:
|
| 1047 |
+
...
|
| 1048 |
+
"genre": 0.1,
|
| 1049 |
+
"artist": 0.5,
|
| 1050 |
+
"wav": 0.25,
|
| 1051 |
+
...
|
| 1052 |
+
active_on_eval (bool, optional): Whether the dropout is active at eval. Default to False.
|
| 1053 |
+
seed (int, optional): Random seed.
|
| 1054 |
+
"""
|
| 1055 |
+
def __init__(self, p: tp.Dict[str, tp.Dict[str, float]], active_on_eval: bool = False, seed: int = 1234):
|
| 1056 |
+
super().__init__(seed=seed)
|
| 1057 |
+
self.active_on_eval = active_on_eval
|
| 1058 |
+
# construct dict that return the values from p otherwise 0
|
| 1059 |
+
self.p = {}
|
| 1060 |
+
for condition_type, probs in p.items():
|
| 1061 |
+
self.p[condition_type] = defaultdict(lambda: 0, probs)
|
| 1062 |
+
|
| 1063 |
+
def forward(self, samples: tp.List[ConditioningAttributes]) -> tp.List[ConditioningAttributes]:
|
| 1064 |
+
"""
|
| 1065 |
+
Args:
|
| 1066 |
+
samples (list[ConditioningAttributes]): List of conditions.
|
| 1067 |
+
Returns:
|
| 1068 |
+
list[ConditioningAttributes]: List of conditions after certain attributes were set to None.
|
| 1069 |
+
"""
|
| 1070 |
+
if not self.training and not self.active_on_eval:
|
| 1071 |
+
return samples
|
| 1072 |
+
|
| 1073 |
+
samples = deepcopy(samples)
|
| 1074 |
+
for condition_type, ps in self.p.items(): # for condition types [text, wav]
|
| 1075 |
+
for condition, p in ps.items(): # for attributes of each type (e.g., [artist, genre])
|
| 1076 |
+
if torch.rand(1, generator=self.rng).item() < p:
|
| 1077 |
+
for sample in samples:
|
| 1078 |
+
dropout_condition(sample, condition_type, condition)
|
| 1079 |
+
return samples
|
| 1080 |
+
|
| 1081 |
+
def __repr__(self):
|
| 1082 |
+
return f"AttributeDropout({dict(self.p)})"
|
| 1083 |
+
|
| 1084 |
+
|
| 1085 |
+
class ClassifierFreeGuidanceDropout(DropoutModule):
|
| 1086 |
+
"""Classifier Free Guidance dropout.
|
| 1087 |
+
All attributes are dropped with the same probability.
|
| 1088 |
+
|
| 1089 |
+
Args:
|
| 1090 |
+
p (float): Probability to apply condition dropout during training.
|
| 1091 |
+
seed (int): Random seed.
|
| 1092 |
+
"""
|
| 1093 |
+
def __init__(self, p: float, seed: int = 1234):
|
| 1094 |
+
super().__init__(seed=seed)
|
| 1095 |
+
self.p = p
|
| 1096 |
+
|
| 1097 |
+
def forward(self, samples: tp.List[ConditioningAttributes]) -> tp.List[ConditioningAttributes]:
|
| 1098 |
+
"""
|
| 1099 |
+
Args:
|
| 1100 |
+
samples (list[ConditioningAttributes]): List of conditions.
|
| 1101 |
+
Returns:
|
| 1102 |
+
list[ConditioningAttributes]: List of conditions after all attributes were set to None.
|
| 1103 |
+
"""
|
| 1104 |
+
if not self.training:
|
| 1105 |
+
return samples
|
| 1106 |
+
|
| 1107 |
+
# decide on which attributes to drop in a batched fashion
|
| 1108 |
+
drop = torch.rand(1, generator=self.rng).item() < self.p
|
| 1109 |
+
if not drop:
|
| 1110 |
+
return samples
|
| 1111 |
+
|
| 1112 |
+
# nullify conditions of all attributes
|
| 1113 |
+
samples = deepcopy(samples)
|
| 1114 |
+
for condition_type in ["wav", "text"]:
|
| 1115 |
+
for sample in samples:
|
| 1116 |
+
for condition in sample.attributes[condition_type]:
|
| 1117 |
+
dropout_condition(sample, condition_type, condition)
|
| 1118 |
+
return samples
|
| 1119 |
+
|
| 1120 |
+
def __repr__(self):
|
| 1121 |
+
return f"ClassifierFreeGuidanceDropout(p={self.p})"
|
| 1122 |
+
|
| 1123 |
+
|
| 1124 |
+
class ConditioningProvider(nn.Module):
|
| 1125 |
+
"""Prepare and provide conditions given all the supported conditioners.
|
| 1126 |
+
|
| 1127 |
+
Args:
|
| 1128 |
+
conditioners (dict): Dictionary of conditioners.
|
| 1129 |
+
device (torch.device or str, optional): Device for conditioners and output condition types.
|
| 1130 |
+
"""
|
| 1131 |
+
def __init__(self, conditioners: tp.Dict[str, BaseConditioner], device: tp.Union[torch.device, str] = "cpu"):
|
| 1132 |
+
super().__init__()
|
| 1133 |
+
self.device = device
|
| 1134 |
+
self.conditioners = nn.ModuleDict(conditioners)
|
| 1135 |
+
|
| 1136 |
+
@property
|
| 1137 |
+
def joint_embed_conditions(self):
|
| 1138 |
+
return [m.attribute for m in self.conditioners.values() if isinstance(m, JointEmbeddingConditioner)]
|
| 1139 |
+
|
| 1140 |
+
@property
|
| 1141 |
+
def has_joint_embed_conditions(self):
|
| 1142 |
+
return len(self.joint_embed_conditions) > 0
|
| 1143 |
+
|
| 1144 |
+
@property
|
| 1145 |
+
def text_conditions(self):
|
| 1146 |
+
return [k for k, v in self.conditioners.items() if isinstance(v, TextConditioner)]
|
| 1147 |
+
|
| 1148 |
+
@property
|
| 1149 |
+
def wav_conditions(self):
|
| 1150 |
+
return [k for k, v in self.conditioners.items() if isinstance(v, WaveformConditioner)]
|
| 1151 |
+
|
| 1152 |
+
@property
|
| 1153 |
+
def has_wav_condition(self):
|
| 1154 |
+
return len(self.wav_conditions) > 0
|
| 1155 |
+
|
| 1156 |
+
def tokenize(self, inputs: tp.List[ConditioningAttributes]) -> tp.Dict[str, tp.Any]:
|
| 1157 |
+
"""Match attributes/wavs with existing conditioners in self, and compute tokenize them accordingly.
|
| 1158 |
+
This should be called before starting any real GPU work to avoid synchronization points.
|
| 1159 |
+
This will return a dict matching conditioner names to their arbitrary tokenized representations.
|
| 1160 |
+
|
| 1161 |
+
Args:
|
| 1162 |
+
inputs (list[ConditioningAttributes]): List of ConditioningAttributes objects containing
|
| 1163 |
+
text and wav conditions.
|
| 1164 |
+
"""
|
| 1165 |
+
assert all([isinstance(x, ConditioningAttributes) for x in inputs]), (
|
| 1166 |
+
"Got unexpected types input for conditioner! should be tp.List[ConditioningAttributes]",
|
| 1167 |
+
f" but types were {set([type(x) for x in inputs])}"
|
| 1168 |
+
)
|
| 1169 |
+
|
| 1170 |
+
output = {}
|
| 1171 |
+
text = self._collate_text(inputs)
|
| 1172 |
+
wavs = self._collate_wavs(inputs)
|
| 1173 |
+
joint_embeds = self._collate_joint_embeds(inputs)
|
| 1174 |
+
|
| 1175 |
+
assert set(text.keys() | wavs.keys() | joint_embeds.keys()).issubset(set(self.conditioners.keys())), (
|
| 1176 |
+
f"Got an unexpected attribute! Expected {self.conditioners.keys()}, ",
|
| 1177 |
+
f"got {text.keys(), wavs.keys(), joint_embeds.keys()}"
|
| 1178 |
+
)
|
| 1179 |
+
|
| 1180 |
+
for attribute, batch in chain(text.items(), wavs.items(), joint_embeds.items()):
|
| 1181 |
+
output[attribute] = self.conditioners[attribute].tokenize(batch)
|
| 1182 |
+
return output
|
| 1183 |
+
|
| 1184 |
+
def forward(self, tokenized: tp.Dict[str, tp.Any]) -> tp.Dict[str, ConditionType]:
|
| 1185 |
+
"""Compute pairs of `(embedding, mask)` using the configured conditioners and the tokenized representations.
|
| 1186 |
+
The output is for example:
|
| 1187 |
+
{
|
| 1188 |
+
"genre": (torch.Tensor([B, 1, D_genre]), torch.Tensor([B, 1])),
|
| 1189 |
+
"description": (torch.Tensor([B, T_desc, D_desc]), torch.Tensor([B, T_desc])),
|
| 1190 |
+
...
|
| 1191 |
+
}
|
| 1192 |
+
|
| 1193 |
+
Args:
|
| 1194 |
+
tokenized (dict): Dict of tokenized representations as returned by `tokenize()`.
|
| 1195 |
+
"""
|
| 1196 |
+
output = {}
|
| 1197 |
+
for attribute, inputs in tokenized.items():
|
| 1198 |
+
condition, mask = self.conditioners[attribute](inputs)
|
| 1199 |
+
output[attribute] = (condition, mask)
|
| 1200 |
+
return output
|
| 1201 |
+
|
| 1202 |
+
def _collate_text(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, tp.List[tp.Optional[str]]]:
|
| 1203 |
+
"""Given a list of ConditioningAttributes objects, compile a dictionary where the keys
|
| 1204 |
+
are the attributes and the values are the aggregated input per attribute.
|
| 1205 |
+
For example:
|
| 1206 |
+
Input:
|
| 1207 |
+
[
|
| 1208 |
+
ConditioningAttributes(text={"genre": "Rock", "description": "A rock song with a guitar solo"}, wav=...),
|
| 1209 |
+
ConditioningAttributes(text={"genre": "Hip-hop", "description": "A hip-hop verse"}, wav=...),
|
| 1210 |
+
]
|
| 1211 |
+
Output:
|
| 1212 |
+
{
|
| 1213 |
+
"genre": ["Rock", "Hip-hop"],
|
| 1214 |
+
"description": ["A rock song with a guitar solo", "A hip-hop verse"]
|
| 1215 |
+
}
|
| 1216 |
+
|
| 1217 |
+
Args:
|
| 1218 |
+
samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
|
| 1219 |
+
Returns:
|
| 1220 |
+
dict[str, list[str, optional]]: A dictionary mapping an attribute name to text batch.
|
| 1221 |
+
"""
|
| 1222 |
+
out: tp.Dict[str, tp.List[tp.Optional[str]]] = defaultdict(list)
|
| 1223 |
+
texts = [x.text for x in samples]
|
| 1224 |
+
for text in texts:
|
| 1225 |
+
for condition in self.text_conditions:
|
| 1226 |
+
out[condition].append(text[condition])
|
| 1227 |
+
return out
|
| 1228 |
+
|
| 1229 |
+
def _collate_wavs(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, WavCondition]:
|
| 1230 |
+
"""Generate a dict where the keys are attributes by which we fetch similar wavs,
|
| 1231 |
+
and the values are Tensors of wavs according to said attributes.
|
| 1232 |
+
|
| 1233 |
+
*Note*: by the time the samples reach this function, each sample should have some waveform
|
| 1234 |
+
inside the "wav" attribute. It should be either:
|
| 1235 |
+
1. A real waveform
|
| 1236 |
+
2. A null waveform due to the sample having no similar waveforms (nullified by the dataset)
|
| 1237 |
+
3. A null waveform due to it being dropped in a dropout module (nullified by dropout)
|
| 1238 |
+
|
| 1239 |
+
Args:
|
| 1240 |
+
samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
|
| 1241 |
+
Returns:
|
| 1242 |
+
dict[str, WavCondition]: A dictionary mapping an attribute name to wavs.
|
| 1243 |
+
"""
|
| 1244 |
+
wavs = defaultdict(list)
|
| 1245 |
+
lengths = defaultdict(list)
|
| 1246 |
+
sample_rates = defaultdict(list)
|
| 1247 |
+
paths = defaultdict(list)
|
| 1248 |
+
seek_times = defaultdict(list)
|
| 1249 |
+
out: tp.Dict[str, WavCondition] = {}
|
| 1250 |
+
|
| 1251 |
+
for sample in samples:
|
| 1252 |
+
for attribute in self.wav_conditions:
|
| 1253 |
+
wav, length, sample_rate, path, seek_time = sample.wav[attribute]
|
| 1254 |
+
assert wav.dim() == 3, f"Got wav with dim={wav.dim()}, but expected 3 [1, C, T]"
|
| 1255 |
+
assert wav.size(0) == 1, f"Got wav [B, C, T] with shape={wav.shape}, but expected B == 1"
|
| 1256 |
+
# mono-channel conditioning
|
| 1257 |
+
wav = wav.mean(1, keepdim=True) # [1, 1, T]
|
| 1258 |
+
wavs[attribute].append(wav.flatten()) # [T]
|
| 1259 |
+
lengths[attribute].append(length)
|
| 1260 |
+
sample_rates[attribute].extend(sample_rate)
|
| 1261 |
+
paths[attribute].extend(path)
|
| 1262 |
+
seek_times[attribute].extend(seek_time)
|
| 1263 |
+
|
| 1264 |
+
# stack all wavs to a single tensor
|
| 1265 |
+
for attribute in self.wav_conditions:
|
| 1266 |
+
stacked_wav, _ = collate(wavs[attribute], dim=0)
|
| 1267 |
+
out[attribute] = WavCondition(
|
| 1268 |
+
stacked_wav.unsqueeze(1), torch.cat(lengths[attribute]), sample_rates[attribute],
|
| 1269 |
+
paths[attribute], seek_times[attribute])
|
| 1270 |
+
|
| 1271 |
+
return out
|
| 1272 |
+
|
| 1273 |
+
def _collate_joint_embeds(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, JointEmbedCondition]:
|
| 1274 |
+
"""Generate a dict where the keys are attributes by which we compute joint embeddings,
|
| 1275 |
+
and the values are Tensors of pre-computed embeddings and the corresponding text attributes.
|
| 1276 |
+
|
| 1277 |
+
Args:
|
| 1278 |
+
samples (list[ConditioningAttributes]): List of ConditioningAttributes samples.
|
| 1279 |
+
Returns:
|
| 1280 |
+
A dictionary mapping an attribute name to joint embeddings.
|
| 1281 |
+
"""
|
| 1282 |
+
texts = defaultdict(list)
|
| 1283 |
+
wavs = defaultdict(list)
|
| 1284 |
+
lengths = defaultdict(list)
|
| 1285 |
+
sample_rates = defaultdict(list)
|
| 1286 |
+
paths = defaultdict(list)
|
| 1287 |
+
seek_times = defaultdict(list)
|
| 1288 |
+
channels: int = 0
|
| 1289 |
+
|
| 1290 |
+
out = {}
|
| 1291 |
+
for sample in samples:
|
| 1292 |
+
for attribute in self.joint_embed_conditions:
|
| 1293 |
+
wav, text, length, sample_rate, path, seek_time = sample.joint_embed[attribute]
|
| 1294 |
+
assert wav.dim() == 3
|
| 1295 |
+
if channels == 0:
|
| 1296 |
+
channels = wav.size(1)
|
| 1297 |
+
else:
|
| 1298 |
+
assert channels == wav.size(1), "not all audio has same number of channels in batch"
|
| 1299 |
+
assert wav.size(0) == 1, "Expecting single-wav batch in the collate method"
|
| 1300 |
+
wav = einops.rearrange(wav, "b c t -> (b c t)") # [1, C, T] => [C * T]
|
| 1301 |
+
wavs[attribute].append(wav)
|
| 1302 |
+
texts[attribute].extend(text)
|
| 1303 |
+
lengths[attribute].append(length)
|
| 1304 |
+
sample_rates[attribute].extend(sample_rate)
|
| 1305 |
+
paths[attribute].extend(path)
|
| 1306 |
+
seek_times[attribute].extend(seek_time)
|
| 1307 |
+
|
| 1308 |
+
for attribute in self.joint_embed_conditions:
|
| 1309 |
+
stacked_texts = texts[attribute]
|
| 1310 |
+
stacked_paths = paths[attribute]
|
| 1311 |
+
stacked_seek_times = seek_times[attribute]
|
| 1312 |
+
stacked_wavs = pad_sequence(wavs[attribute]).to(self.device)
|
| 1313 |
+
stacked_wavs = einops.rearrange(stacked_wavs, "(c t) b -> b c t", c=channels)
|
| 1314 |
+
stacked_sample_rates = sample_rates[attribute]
|
| 1315 |
+
stacked_lengths = torch.cat(lengths[attribute]).to(self.device)
|
| 1316 |
+
assert stacked_lengths.size(0) == stacked_wavs.size(0)
|
| 1317 |
+
assert len(stacked_sample_rates) == stacked_wavs.size(0)
|
| 1318 |
+
assert len(stacked_texts) == stacked_wavs.size(0)
|
| 1319 |
+
out[attribute] = JointEmbedCondition(
|
| 1320 |
+
text=stacked_texts, wav=stacked_wavs,
|
| 1321 |
+
length=stacked_lengths, sample_rate=stacked_sample_rates,
|
| 1322 |
+
path=stacked_paths, seek_time=stacked_seek_times)
|
| 1323 |
+
|
| 1324 |
+
return out
|
| 1325 |
+
|
| 1326 |
+
|
| 1327 |
+
class ConditionFuser(StreamingModule):
|
| 1328 |
+
"""Condition fuser handles the logic to combine the different conditions
|
| 1329 |
+
to the actual model input.
|
| 1330 |
+
|
| 1331 |
+
Args:
|
| 1332 |
+
fuse2cond (tp.Dict[str, str]): A dictionary that says how to fuse
|
| 1333 |
+
each condition. For example:
|
| 1334 |
+
{
|
| 1335 |
+
"prepend": ["description"],
|
| 1336 |
+
"sum": ["genre", "bpm"],
|
| 1337 |
+
"cross": ["description"],
|
| 1338 |
+
}
|
| 1339 |
+
cross_attention_pos_emb (bool, optional): Use positional embeddings in cross attention.
|
| 1340 |
+
cross_attention_pos_emb_scale (int): Scale for positional embeddings in cross attention if used.
|
| 1341 |
+
"""
|
| 1342 |
+
FUSING_METHODS = ["sum", "prepend", "cross", "input_interpolate"]
|
| 1343 |
+
|
| 1344 |
+
def __init__(self, fuse2cond: tp.Dict[str, tp.List[str]], cross_attention_pos_emb: bool = False,
|
| 1345 |
+
cross_attention_pos_emb_scale: float = 1.0):
|
| 1346 |
+
super().__init__()
|
| 1347 |
+
assert all(
|
| 1348 |
+
[k in self.FUSING_METHODS for k in fuse2cond.keys()]
|
| 1349 |
+
), f"Got invalid fuse method, allowed methods: {self.FUSING_METHODS}"
|
| 1350 |
+
self.cross_attention_pos_emb = cross_attention_pos_emb
|
| 1351 |
+
self.cross_attention_pos_emb_scale = cross_attention_pos_emb_scale
|
| 1352 |
+
self.fuse2cond: tp.Dict[str, tp.List[str]] = fuse2cond
|
| 1353 |
+
self.cond2fuse: tp.Dict[str, str] = {}
|
| 1354 |
+
for fuse_method, conditions in fuse2cond.items():
|
| 1355 |
+
for condition in conditions:
|
| 1356 |
+
self.cond2fuse[condition] = fuse_method
|
| 1357 |
+
|
| 1358 |
+
def forward(
|
| 1359 |
+
self,
|
| 1360 |
+
input: torch.Tensor,
|
| 1361 |
+
conditions: tp.Dict[str, ConditionType]
|
| 1362 |
+
) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
|
| 1363 |
+
"""Fuse the conditions to the provided model input.
|
| 1364 |
+
|
| 1365 |
+
Args:
|
| 1366 |
+
input (torch.Tensor): Transformer input.
|
| 1367 |
+
conditions (dict[str, ConditionType]): Dict of conditions.
|
| 1368 |
+
Returns:
|
| 1369 |
+
tuple[torch.Tensor, torch.Tensor]: The first tensor is the transformer input
|
| 1370 |
+
after the conditions have been fused. The second output tensor is the tensor
|
| 1371 |
+
used for cross-attention or None if no cross attention inputs exist.
|
| 1372 |
+
"""
|
| 1373 |
+
B, T, _ = input.shape
|
| 1374 |
+
|
| 1375 |
+
if 'offsets' in self._streaming_state:
|
| 1376 |
+
first_step = False
|
| 1377 |
+
offsets = self._streaming_state['offsets']
|
| 1378 |
+
else:
|
| 1379 |
+
first_step = True
|
| 1380 |
+
offsets = torch.zeros(input.shape[0], dtype=torch.long, device=input.device)
|
| 1381 |
+
|
| 1382 |
+
assert set(conditions.keys()).issubset(set(self.cond2fuse.keys())), \
|
| 1383 |
+
f"given conditions contain unknown attributes for fuser, " \
|
| 1384 |
+
f"expected {self.cond2fuse.keys()}, got {conditions.keys()}"
|
| 1385 |
+
cross_attention_output = None
|
| 1386 |
+
for cond_type, (cond, cond_mask) in conditions.items():
|
| 1387 |
+
op = self.cond2fuse[cond_type]
|
| 1388 |
+
if op == 'sum':
|
| 1389 |
+
input += cond
|
| 1390 |
+
elif op == 'input_interpolate':
|
| 1391 |
+
cond = einops.rearrange(cond, "b t d -> b d t")
|
| 1392 |
+
cond = F.interpolate(cond, size=input.shape[1])
|
| 1393 |
+
input += einops.rearrange(cond, "b d t -> b t d")
|
| 1394 |
+
elif op == 'prepend':
|
| 1395 |
+
if first_step:
|
| 1396 |
+
input = torch.cat([cond, input], dim=1)
|
| 1397 |
+
elif op == 'cross':
|
| 1398 |
+
if cross_attention_output is not None:
|
| 1399 |
+
cross_attention_output = torch.cat([cross_attention_output, cond], dim=1)
|
| 1400 |
+
else:
|
| 1401 |
+
cross_attention_output = cond
|
| 1402 |
+
else:
|
| 1403 |
+
raise ValueError(f"unknown op ({op})")
|
| 1404 |
+
|
| 1405 |
+
if self.cross_attention_pos_emb and cross_attention_output is not None:
|
| 1406 |
+
positions = torch.arange(
|
| 1407 |
+
cross_attention_output.shape[1],
|
| 1408 |
+
device=cross_attention_output.device
|
| 1409 |
+
).view(1, -1, 1)
|
| 1410 |
+
pos_emb = create_sin_embedding(positions, cross_attention_output.shape[-1])
|
| 1411 |
+
cross_attention_output = cross_attention_output + self.cross_attention_pos_emb_scale * pos_emb
|
| 1412 |
+
|
| 1413 |
+
if self._is_streaming:
|
| 1414 |
+
self._streaming_state['offsets'] = offsets + T
|
| 1415 |
+
|
| 1416 |
+
return input, cross_attention_output
|
src/audiocraft/modules/conv.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import math
|
| 8 |
+
import typing as tp
|
| 9 |
+
import warnings
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from torch import nn
|
| 13 |
+
from torch.nn import functional as F
|
| 14 |
+
from torch.nn.utils import spectral_norm, weight_norm
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
CONV_NORMALIZATIONS = frozenset(['none', 'weight_norm', 'spectral_norm',
|
| 18 |
+
'time_group_norm'])
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def apply_parametrization_norm(module: nn.Module, norm: str = 'none'):
|
| 22 |
+
assert norm in CONV_NORMALIZATIONS
|
| 23 |
+
if norm == 'weight_norm':
|
| 24 |
+
return weight_norm(module)
|
| 25 |
+
elif norm == 'spectral_norm':
|
| 26 |
+
return spectral_norm(module)
|
| 27 |
+
else:
|
| 28 |
+
# We already check was in CONV_NORMALIZATION, so any other choice
|
| 29 |
+
# doesn't need reparametrization.
|
| 30 |
+
return module
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def get_norm_module(module: nn.Module, causal: bool = False, norm: str = 'none', **norm_kwargs):
|
| 34 |
+
"""Return the proper normalization module. If causal is True, this will ensure the returned
|
| 35 |
+
module is causal, or return an error if the normalization doesn't support causal evaluation.
|
| 36 |
+
"""
|
| 37 |
+
assert norm in CONV_NORMALIZATIONS
|
| 38 |
+
if norm == 'time_group_norm':
|
| 39 |
+
if causal:
|
| 40 |
+
raise ValueError("GroupNorm doesn't support causal evaluation.")
|
| 41 |
+
assert isinstance(module, nn.modules.conv._ConvNd)
|
| 42 |
+
return nn.GroupNorm(1, module.out_channels, **norm_kwargs)
|
| 43 |
+
else:
|
| 44 |
+
return nn.Identity()
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def get_extra_padding_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int,
|
| 48 |
+
padding_total: int = 0) -> int:
|
| 49 |
+
"""See `pad_for_conv1d`."""
|
| 50 |
+
length = x.shape[-1]
|
| 51 |
+
n_frames = (length - kernel_size + padding_total) / stride + 1
|
| 52 |
+
ideal_length = (math.ceil(n_frames) - 1) * stride + (kernel_size - padding_total)
|
| 53 |
+
return ideal_length - length
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def pad_for_conv1d(x: torch.Tensor, kernel_size: int, stride: int, padding_total: int = 0):
|
| 57 |
+
"""Pad for a convolution to make sure that the last window is full.
|
| 58 |
+
Extra padding is added at the end. This is required to ensure that we can rebuild
|
| 59 |
+
an output of the same length, as otherwise, even with padding, some time steps
|
| 60 |
+
might get removed.
|
| 61 |
+
For instance, with total padding = 4, kernel size = 4, stride = 2:
|
| 62 |
+
0 0 1 2 3 4 5 0 0 # (0s are padding)
|
| 63 |
+
1 2 3 # (output frames of a convolution, last 0 is never used)
|
| 64 |
+
0 0 1 2 3 4 5 0 # (output of tr. conv., but pos. 5 is going to get removed as padding)
|
| 65 |
+
1 2 3 4 # once you removed padding, we are missing one time step !
|
| 66 |
+
"""
|
| 67 |
+
extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total)
|
| 68 |
+
return F.pad(x, (0, extra_padding))
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def pad1d(x: torch.Tensor, paddings: tp.Tuple[int, int], mode: str = 'constant', value: float = 0.):
|
| 72 |
+
"""Tiny wrapper around F.pad, just to allow for reflect padding on small input.
|
| 73 |
+
If this is the case, we insert extra 0 padding to the right before the reflection happen.
|
| 74 |
+
"""
|
| 75 |
+
length = x.shape[-1]
|
| 76 |
+
padding_left, padding_right = paddings
|
| 77 |
+
assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right)
|
| 78 |
+
if mode == 'reflect':
|
| 79 |
+
max_pad = max(padding_left, padding_right)
|
| 80 |
+
extra_pad = 0
|
| 81 |
+
if length <= max_pad:
|
| 82 |
+
extra_pad = max_pad - length + 1
|
| 83 |
+
x = F.pad(x, (0, extra_pad))
|
| 84 |
+
padded = F.pad(x, paddings, mode, value)
|
| 85 |
+
end = padded.shape[-1] - extra_pad
|
| 86 |
+
return padded[..., :end]
|
| 87 |
+
else:
|
| 88 |
+
return F.pad(x, paddings, mode, value)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def unpad1d(x: torch.Tensor, paddings: tp.Tuple[int, int]):
|
| 92 |
+
"""Remove padding from x, handling properly zero padding. Only for 1d!"""
|
| 93 |
+
padding_left, padding_right = paddings
|
| 94 |
+
assert padding_left >= 0 and padding_right >= 0, (padding_left, padding_right)
|
| 95 |
+
assert (padding_left + padding_right) <= x.shape[-1]
|
| 96 |
+
end = x.shape[-1] - padding_right
|
| 97 |
+
return x[..., padding_left: end]
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class NormConv1d(nn.Module):
|
| 101 |
+
"""Wrapper around Conv1d and normalization applied to this conv
|
| 102 |
+
to provide a uniform interface across normalization approaches.
|
| 103 |
+
"""
|
| 104 |
+
def __init__(self, *args, causal: bool = False, norm: str = 'none',
|
| 105 |
+
norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
| 106 |
+
super().__init__()
|
| 107 |
+
self.conv = apply_parametrization_norm(nn.Conv1d(*args, **kwargs), norm)
|
| 108 |
+
self.norm = get_norm_module(self.conv, causal, norm, **norm_kwargs)
|
| 109 |
+
self.norm_type = norm
|
| 110 |
+
|
| 111 |
+
def forward(self, x):
|
| 112 |
+
x = self.conv(x)
|
| 113 |
+
x = self.norm(x)
|
| 114 |
+
return x
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
class NormConv2d(nn.Module):
|
| 118 |
+
"""Wrapper around Conv2d and normalization applied to this conv
|
| 119 |
+
to provide a uniform interface across normalization approaches.
|
| 120 |
+
"""
|
| 121 |
+
def __init__(self, *args, norm: str = 'none', norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
| 122 |
+
super().__init__()
|
| 123 |
+
self.conv = apply_parametrization_norm(nn.Conv2d(*args, **kwargs), norm)
|
| 124 |
+
self.norm = get_norm_module(self.conv, causal=False, norm=norm, **norm_kwargs)
|
| 125 |
+
self.norm_type = norm
|
| 126 |
+
|
| 127 |
+
def forward(self, x):
|
| 128 |
+
x = self.conv(x)
|
| 129 |
+
x = self.norm(x)
|
| 130 |
+
return x
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
class NormConvTranspose1d(nn.Module):
|
| 134 |
+
"""Wrapper around ConvTranspose1d and normalization applied to this conv
|
| 135 |
+
to provide a uniform interface across normalization approaches.
|
| 136 |
+
"""
|
| 137 |
+
def __init__(self, *args, causal: bool = False, norm: str = 'none',
|
| 138 |
+
norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
| 139 |
+
super().__init__()
|
| 140 |
+
self.convtr = apply_parametrization_norm(nn.ConvTranspose1d(*args, **kwargs), norm)
|
| 141 |
+
self.norm = get_norm_module(self.convtr, causal, norm, **norm_kwargs)
|
| 142 |
+
self.norm_type = norm
|
| 143 |
+
|
| 144 |
+
def forward(self, x):
|
| 145 |
+
x = self.convtr(x)
|
| 146 |
+
x = self.norm(x)
|
| 147 |
+
return x
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
class NormConvTranspose2d(nn.Module):
|
| 151 |
+
"""Wrapper around ConvTranspose2d and normalization applied to this conv
|
| 152 |
+
to provide a uniform interface across normalization approaches.
|
| 153 |
+
"""
|
| 154 |
+
def __init__(self, *args, norm: str = 'none', norm_kwargs: tp.Dict[str, tp.Any] = {}, **kwargs):
|
| 155 |
+
super().__init__()
|
| 156 |
+
self.convtr = apply_parametrization_norm(nn.ConvTranspose2d(*args, **kwargs), norm)
|
| 157 |
+
self.norm = get_norm_module(self.convtr, causal=False, norm=norm, **norm_kwargs)
|
| 158 |
+
|
| 159 |
+
def forward(self, x):
|
| 160 |
+
x = self.convtr(x)
|
| 161 |
+
x = self.norm(x)
|
| 162 |
+
return x
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class StreamableConv1d(nn.Module):
|
| 166 |
+
"""Conv1d with some builtin handling of asymmetric or causal padding
|
| 167 |
+
and normalization.
|
| 168 |
+
"""
|
| 169 |
+
def __init__(self, in_channels: int, out_channels: int,
|
| 170 |
+
kernel_size: int, stride: int = 1, dilation: int = 1,
|
| 171 |
+
groups: int = 1, bias: bool = True, causal: bool = False,
|
| 172 |
+
norm: str = 'none', norm_kwargs: tp.Dict[str, tp.Any] = {},
|
| 173 |
+
pad_mode: str = 'reflect'):
|
| 174 |
+
super().__init__()
|
| 175 |
+
# warn user on unusual setup between dilation and stride
|
| 176 |
+
if stride > 1 and dilation > 1:
|
| 177 |
+
warnings.warn("StreamableConv1d has been initialized with stride > 1 and dilation > 1"
|
| 178 |
+
f" (kernel_size={kernel_size} stride={stride}, dilation={dilation}).")
|
| 179 |
+
self.conv = NormConv1d(in_channels, out_channels, kernel_size, stride,
|
| 180 |
+
dilation=dilation, groups=groups, bias=bias, causal=causal,
|
| 181 |
+
norm=norm, norm_kwargs=norm_kwargs)
|
| 182 |
+
self.causal = causal
|
| 183 |
+
self.pad_mode = pad_mode
|
| 184 |
+
|
| 185 |
+
def forward(self, x):
|
| 186 |
+
B, C, T = x.shape
|
| 187 |
+
kernel_size = self.conv.conv.kernel_size[0]
|
| 188 |
+
stride = self.conv.conv.stride[0]
|
| 189 |
+
dilation = self.conv.conv.dilation[0]
|
| 190 |
+
kernel_size = (kernel_size - 1) * dilation + 1 # effective kernel size with dilations
|
| 191 |
+
padding_total = kernel_size - stride
|
| 192 |
+
extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride, padding_total)
|
| 193 |
+
if self.causal:
|
| 194 |
+
# Left padding for causal
|
| 195 |
+
x = pad1d(x, (padding_total, extra_padding), mode=self.pad_mode)
|
| 196 |
+
else:
|
| 197 |
+
# Asymmetric padding required for odd strides
|
| 198 |
+
padding_right = padding_total // 2
|
| 199 |
+
padding_left = padding_total - padding_right
|
| 200 |
+
x = pad1d(x, (padding_left, padding_right + extra_padding), mode=self.pad_mode)
|
| 201 |
+
return self.conv(x)
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
class StreamableConvTranspose1d(nn.Module):
|
| 205 |
+
"""ConvTranspose1d with some builtin handling of asymmetric or causal padding
|
| 206 |
+
and normalization.
|
| 207 |
+
"""
|
| 208 |
+
def __init__(self, in_channels: int, out_channels: int,
|
| 209 |
+
kernel_size: int, stride: int = 1, causal: bool = False,
|
| 210 |
+
norm: str = 'none', trim_right_ratio: float = 1.,
|
| 211 |
+
norm_kwargs: tp.Dict[str, tp.Any] = {}):
|
| 212 |
+
super().__init__()
|
| 213 |
+
self.convtr = NormConvTranspose1d(in_channels, out_channels, kernel_size, stride,
|
| 214 |
+
causal=causal, norm=norm, norm_kwargs=norm_kwargs)
|
| 215 |
+
self.causal = causal
|
| 216 |
+
self.trim_right_ratio = trim_right_ratio
|
| 217 |
+
assert self.causal or self.trim_right_ratio == 1., \
|
| 218 |
+
"`trim_right_ratio` != 1.0 only makes sense for causal convolutions"
|
| 219 |
+
assert self.trim_right_ratio >= 0. and self.trim_right_ratio <= 1.
|
| 220 |
+
|
| 221 |
+
def forward(self, x):
|
| 222 |
+
kernel_size = self.convtr.convtr.kernel_size[0]
|
| 223 |
+
stride = self.convtr.convtr.stride[0]
|
| 224 |
+
padding_total = kernel_size - stride
|
| 225 |
+
|
| 226 |
+
y = self.convtr(x)
|
| 227 |
+
|
| 228 |
+
# We will only trim fixed padding. Extra padding from `pad_for_conv1d` would be
|
| 229 |
+
# removed at the very end, when keeping only the right length for the output,
|
| 230 |
+
# as removing it here would require also passing the length at the matching layer
|
| 231 |
+
# in the encoder.
|
| 232 |
+
if self.causal:
|
| 233 |
+
# Trim the padding on the right according to the specified ratio
|
| 234 |
+
# if trim_right_ratio = 1.0, trim everything from right
|
| 235 |
+
padding_right = math.ceil(padding_total * self.trim_right_ratio)
|
| 236 |
+
padding_left = padding_total - padding_right
|
| 237 |
+
y = unpad1d(y, (padding_left, padding_right))
|
| 238 |
+
else:
|
| 239 |
+
# Asymmetric padding required for odd strides
|
| 240 |
+
padding_right = padding_total // 2
|
| 241 |
+
padding_left = padding_total - padding_right
|
| 242 |
+
y = unpad1d(y, (padding_left, padding_right))
|
| 243 |
+
return y
|
src/audiocraft/modules/diffusion_schedule.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Functions for Noise Schedule, defines diffusion process, reverse process and data processor.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from collections import namedtuple
|
| 12 |
+
import random
|
| 13 |
+
import typing as tp
|
| 14 |
+
import julius
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
TrainingItem = namedtuple("TrainingItem", "noisy noise step")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def betas_from_alpha_bar(alpha_bar):
|
| 21 |
+
alphas = torch.cat([torch.Tensor([alpha_bar[0]]), alpha_bar[1:]/alpha_bar[:-1]])
|
| 22 |
+
return 1 - alphas
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class SampleProcessor(torch.nn.Module):
|
| 26 |
+
def project_sample(self, x: torch.Tensor):
|
| 27 |
+
"""Project the original sample to the 'space' where the diffusion will happen."""
|
| 28 |
+
return x
|
| 29 |
+
|
| 30 |
+
def return_sample(self, z: torch.Tensor):
|
| 31 |
+
"""Project back from diffusion space to the actual sample space."""
|
| 32 |
+
return z
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class MultiBandProcessor(SampleProcessor):
|
| 36 |
+
"""
|
| 37 |
+
MultiBand sample processor. The input audio is splitted across
|
| 38 |
+
frequency bands evenly distributed in mel-scale.
|
| 39 |
+
|
| 40 |
+
Each band will be rescaled to match the power distribution
|
| 41 |
+
of Gaussian noise in that band, using online metrics
|
| 42 |
+
computed on the first few samples.
|
| 43 |
+
|
| 44 |
+
Args:
|
| 45 |
+
n_bands (int): Number of mel-bands to split the signal over.
|
| 46 |
+
sample_rate (int): Sample rate of the audio.
|
| 47 |
+
num_samples (int): Number of samples to use to fit the rescaling
|
| 48 |
+
for each band. The processor won't be stable
|
| 49 |
+
until it has seen that many samples.
|
| 50 |
+
power_std (float or list/tensor): The rescaling factor computed to match the
|
| 51 |
+
power of Gaussian noise in each band is taken to
|
| 52 |
+
that power, i.e. `1.` means full correction of the energy
|
| 53 |
+
in each band, and values less than `1` means only partial
|
| 54 |
+
correction. Can be used to balance the relative importance
|
| 55 |
+
of low vs. high freq in typical audio signals.
|
| 56 |
+
"""
|
| 57 |
+
def __init__(self, n_bands: int = 8, sample_rate: float = 24_000,
|
| 58 |
+
num_samples: int = 10_000, power_std: tp.Union[float, tp.List[float], torch.Tensor] = 1.):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.n_bands = n_bands
|
| 61 |
+
self.split_bands = julius.SplitBands(sample_rate, n_bands=n_bands)
|
| 62 |
+
self.num_samples = num_samples
|
| 63 |
+
self.power_std = power_std
|
| 64 |
+
if isinstance(power_std, list):
|
| 65 |
+
assert len(power_std) == n_bands
|
| 66 |
+
power_std = torch.tensor(power_std)
|
| 67 |
+
self.register_buffer('counts', torch.zeros(1))
|
| 68 |
+
self.register_buffer('sum_x', torch.zeros(n_bands))
|
| 69 |
+
self.register_buffer('sum_x2', torch.zeros(n_bands))
|
| 70 |
+
self.register_buffer('sum_target_x2', torch.zeros(n_bands))
|
| 71 |
+
self.counts: torch.Tensor
|
| 72 |
+
self.sum_x: torch.Tensor
|
| 73 |
+
self.sum_x2: torch.Tensor
|
| 74 |
+
self.sum_target_x2: torch.Tensor
|
| 75 |
+
|
| 76 |
+
@property
|
| 77 |
+
def mean(self):
|
| 78 |
+
mean = self.sum_x / self.counts
|
| 79 |
+
return mean
|
| 80 |
+
|
| 81 |
+
@property
|
| 82 |
+
def std(self):
|
| 83 |
+
std = (self.sum_x2 / self.counts - self.mean**2).clamp(min=0).sqrt()
|
| 84 |
+
return std
|
| 85 |
+
|
| 86 |
+
@property
|
| 87 |
+
def target_std(self):
|
| 88 |
+
target_std = self.sum_target_x2 / self.counts
|
| 89 |
+
return target_std
|
| 90 |
+
|
| 91 |
+
def project_sample(self, x: torch.Tensor):
|
| 92 |
+
assert x.dim() == 3
|
| 93 |
+
bands = self.split_bands(x)
|
| 94 |
+
if self.counts.item() < self.num_samples:
|
| 95 |
+
ref_bands = self.split_bands(torch.randn_like(x))
|
| 96 |
+
self.counts += len(x)
|
| 97 |
+
self.sum_x += bands.mean(dim=(2, 3)).sum(dim=1)
|
| 98 |
+
self.sum_x2 += bands.pow(2).mean(dim=(2, 3)).sum(dim=1)
|
| 99 |
+
self.sum_target_x2 += ref_bands.pow(2).mean(dim=(2, 3)).sum(dim=1)
|
| 100 |
+
rescale = (self.target_std / self.std.clamp(min=1e-12)) ** self.power_std # same output size
|
| 101 |
+
bands = (bands - self.mean.view(-1, 1, 1, 1)) * rescale.view(-1, 1, 1, 1)
|
| 102 |
+
return bands.sum(dim=0)
|
| 103 |
+
|
| 104 |
+
def return_sample(self, x: torch.Tensor):
|
| 105 |
+
assert x.dim() == 3
|
| 106 |
+
bands = self.split_bands(x)
|
| 107 |
+
rescale = (self.std / self.target_std) ** self.power_std
|
| 108 |
+
bands = bands * rescale.view(-1, 1, 1, 1) + self.mean.view(-1, 1, 1, 1)
|
| 109 |
+
return bands.sum(dim=0)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class NoiseSchedule:
|
| 113 |
+
"""Noise schedule for diffusion.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
beta_t0 (float): Variance of the first diffusion step.
|
| 117 |
+
beta_t1 (float): Variance of the last diffusion step.
|
| 118 |
+
beta_exp (float): Power schedule exponent
|
| 119 |
+
num_steps (int): Number of diffusion step.
|
| 120 |
+
variance (str): choice of the sigma value for the denoising eq. Choices: "beta" or "beta_tilde"
|
| 121 |
+
clip (float): clipping value for the denoising steps
|
| 122 |
+
rescale (float): rescaling value to avoid vanishing signals unused by default (i.e 1)
|
| 123 |
+
repartition (str): shape of the schedule only power schedule is supported
|
| 124 |
+
sample_processor (SampleProcessor): Module that normalize data to match better the gaussian distribution
|
| 125 |
+
noise_scale (float): Scaling factor for the noise
|
| 126 |
+
"""
|
| 127 |
+
def __init__(self, beta_t0: float = 1e-4, beta_t1: float = 0.02, num_steps: int = 1000, variance: str = 'beta',
|
| 128 |
+
clip: float = 5., rescale: float = 1., device='cuda', beta_exp: float = 1,
|
| 129 |
+
repartition: str = "power", alpha_sigmoid: dict = {}, n_bands: tp.Optional[int] = None,
|
| 130 |
+
sample_processor: SampleProcessor = SampleProcessor(), noise_scale: float = 1.0, **kwargs):
|
| 131 |
+
|
| 132 |
+
self.beta_t0 = beta_t0
|
| 133 |
+
self.beta_t1 = beta_t1
|
| 134 |
+
self.variance = variance
|
| 135 |
+
self.num_steps = num_steps
|
| 136 |
+
self.clip = clip
|
| 137 |
+
self.sample_processor = sample_processor
|
| 138 |
+
self.rescale = rescale
|
| 139 |
+
self.n_bands = n_bands
|
| 140 |
+
self.noise_scale = noise_scale
|
| 141 |
+
assert n_bands is None
|
| 142 |
+
if repartition == "power":
|
| 143 |
+
self.betas = torch.linspace(beta_t0 ** (1 / beta_exp), beta_t1 ** (1 / beta_exp), num_steps,
|
| 144 |
+
device=device, dtype=torch.float) ** beta_exp
|
| 145 |
+
else:
|
| 146 |
+
raise RuntimeError('Not implemented')
|
| 147 |
+
self.rng = random.Random(1234)
|
| 148 |
+
|
| 149 |
+
def get_beta(self, step: tp.Union[int, torch.Tensor]):
|
| 150 |
+
if self.n_bands is None:
|
| 151 |
+
return self.betas[step]
|
| 152 |
+
else:
|
| 153 |
+
return self.betas[:, step] # [n_bands, len(step)]
|
| 154 |
+
|
| 155 |
+
def get_initial_noise(self, x: torch.Tensor):
|
| 156 |
+
if self.n_bands is None:
|
| 157 |
+
return torch.randn_like(x)
|
| 158 |
+
return torch.randn((x.size(0), self.n_bands, x.size(2)))
|
| 159 |
+
|
| 160 |
+
def get_alpha_bar(self, step: tp.Optional[tp.Union[int, torch.Tensor]] = None) -> torch.Tensor:
|
| 161 |
+
"""Return 'alpha_bar', either for a given step, or as a tensor with its value for each step."""
|
| 162 |
+
if step is None:
|
| 163 |
+
return (1 - self.betas).cumprod(dim=-1) # works for simgle and multi bands
|
| 164 |
+
if type(step) is int:
|
| 165 |
+
return (1 - self.betas[:step + 1]).prod()
|
| 166 |
+
else:
|
| 167 |
+
return (1 - self.betas).cumprod(dim=0)[step].view(-1, 1, 1)
|
| 168 |
+
|
| 169 |
+
def get_training_item(self, x: torch.Tensor, tensor_step: bool = False) -> TrainingItem:
|
| 170 |
+
"""Create a noisy data item for diffusion model training:
|
| 171 |
+
|
| 172 |
+
Args:
|
| 173 |
+
x (torch.Tensor): clean audio data torch.tensor(bs, 1, T)
|
| 174 |
+
tensor_step (bool): If tensor_step = false, only one step t is sample,
|
| 175 |
+
the whole batch is diffused to the same step and t is int.
|
| 176 |
+
If tensor_step = true, t is a tensor of size (x.size(0),)
|
| 177 |
+
every element of the batch is diffused to a independently sampled.
|
| 178 |
+
"""
|
| 179 |
+
step: tp.Union[int, torch.Tensor]
|
| 180 |
+
if tensor_step:
|
| 181 |
+
bs = x.size(0)
|
| 182 |
+
step = torch.randint(0, self.num_steps, size=(bs,), device=x.device)
|
| 183 |
+
else:
|
| 184 |
+
step = self.rng.randrange(self.num_steps)
|
| 185 |
+
alpha_bar = self.get_alpha_bar(step) # [batch_size, n_bands, 1]
|
| 186 |
+
|
| 187 |
+
x = self.sample_processor.project_sample(x)
|
| 188 |
+
noise = torch.randn_like(x)
|
| 189 |
+
noisy = (alpha_bar.sqrt() / self.rescale) * x + (1 - alpha_bar).sqrt() * noise * self.noise_scale
|
| 190 |
+
return TrainingItem(noisy, noise, step)
|
| 191 |
+
|
| 192 |
+
def generate(self, model: torch.nn.Module, initial: tp.Optional[torch.Tensor] = None,
|
| 193 |
+
condition: tp.Optional[torch.Tensor] = None, return_list: bool = False):
|
| 194 |
+
"""Full ddpm reverse process.
|
| 195 |
+
|
| 196 |
+
Args:
|
| 197 |
+
model (nn.Module): Diffusion model.
|
| 198 |
+
initial (tensor): Initial Noise.
|
| 199 |
+
condition (tensor): Input conditionning Tensor (e.g. encodec compressed representation).
|
| 200 |
+
return_list (bool): Whether to return the whole process or only the sampled point.
|
| 201 |
+
"""
|
| 202 |
+
alpha_bar = self.get_alpha_bar(step=self.num_steps - 1)
|
| 203 |
+
current = initial
|
| 204 |
+
iterates = [initial]
|
| 205 |
+
for step in range(self.num_steps)[::-1]:
|
| 206 |
+
with torch.no_grad():
|
| 207 |
+
estimate = model(current, step, condition=condition).sample
|
| 208 |
+
alpha = 1 - self.betas[step]
|
| 209 |
+
previous = (current - (1 - alpha) / (1 - alpha_bar).sqrt() * estimate) / alpha.sqrt()
|
| 210 |
+
previous_alpha_bar = self.get_alpha_bar(step=step - 1)
|
| 211 |
+
if step == 0:
|
| 212 |
+
sigma2 = 0
|
| 213 |
+
elif self.variance == 'beta':
|
| 214 |
+
sigma2 = 1 - alpha
|
| 215 |
+
elif self.variance == 'beta_tilde':
|
| 216 |
+
sigma2 = (1 - previous_alpha_bar) / (1 - alpha_bar) * (1 - alpha)
|
| 217 |
+
elif self.variance == 'none':
|
| 218 |
+
sigma2 = 0
|
| 219 |
+
else:
|
| 220 |
+
raise ValueError(f'Invalid variance type {self.variance}')
|
| 221 |
+
|
| 222 |
+
if sigma2 > 0:
|
| 223 |
+
previous += sigma2**0.5 * torch.randn_like(previous) * self.noise_scale
|
| 224 |
+
if self.clip:
|
| 225 |
+
previous = previous.clamp(-self.clip, self.clip)
|
| 226 |
+
current = previous
|
| 227 |
+
alpha_bar = previous_alpha_bar
|
| 228 |
+
if step == 0:
|
| 229 |
+
previous *= self.rescale
|
| 230 |
+
if return_list:
|
| 231 |
+
iterates.append(previous.cpu())
|
| 232 |
+
|
| 233 |
+
if return_list:
|
| 234 |
+
return iterates
|
| 235 |
+
else:
|
| 236 |
+
return self.sample_processor.return_sample(previous)
|
| 237 |
+
|
| 238 |
+
def generate_subsampled(self, model: torch.nn.Module, initial: torch.Tensor, step_list: tp.Optional[list] = None,
|
| 239 |
+
condition: tp.Optional[torch.Tensor] = None, return_list: bool = False):
|
| 240 |
+
"""Reverse process that only goes through Markov chain states in step_list."""
|
| 241 |
+
if step_list is None:
|
| 242 |
+
step_list = list(range(1000))[::-50] + [0]
|
| 243 |
+
alpha_bar = self.get_alpha_bar(step=self.num_steps - 1)
|
| 244 |
+
alpha_bars_subsampled = (1 - self.betas).cumprod(dim=0)[list(reversed(step_list))].cpu()
|
| 245 |
+
betas_subsampled = betas_from_alpha_bar(alpha_bars_subsampled)
|
| 246 |
+
current = initial * self.noise_scale
|
| 247 |
+
iterates = [current]
|
| 248 |
+
for idx, step in enumerate(step_list[:-1]):
|
| 249 |
+
with torch.no_grad():
|
| 250 |
+
estimate = model(current, step, condition=condition).sample * self.noise_scale
|
| 251 |
+
alpha = 1 - betas_subsampled[-1 - idx]
|
| 252 |
+
previous = (current - (1 - alpha) / (1 - alpha_bar).sqrt() * estimate) / alpha.sqrt()
|
| 253 |
+
previous_alpha_bar = self.get_alpha_bar(step_list[idx + 1])
|
| 254 |
+
if step == step_list[-2]:
|
| 255 |
+
sigma2 = 0
|
| 256 |
+
previous_alpha_bar = torch.tensor(1.0)
|
| 257 |
+
else:
|
| 258 |
+
sigma2 = (1 - previous_alpha_bar) / (1 - alpha_bar) * (1 - alpha)
|
| 259 |
+
if sigma2 > 0:
|
| 260 |
+
previous += sigma2**0.5 * torch.randn_like(previous) * self.noise_scale
|
| 261 |
+
if self.clip:
|
| 262 |
+
previous = previous.clamp(-self.clip, self.clip)
|
| 263 |
+
current = previous
|
| 264 |
+
alpha_bar = previous_alpha_bar
|
| 265 |
+
if step == 0:
|
| 266 |
+
previous *= self.rescale
|
| 267 |
+
if return_list:
|
| 268 |
+
iterates.append(previous.cpu())
|
| 269 |
+
if return_list:
|
| 270 |
+
return iterates
|
| 271 |
+
else:
|
| 272 |
+
return self.sample_processor.return_sample(previous)
|
src/audiocraft/modules/lstm.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
from torch import nn
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class StreamableLSTM(nn.Module):
|
| 11 |
+
"""LSTM without worrying about the hidden state, nor the layout of the data.
|
| 12 |
+
Expects input as convolutional layout.
|
| 13 |
+
"""
|
| 14 |
+
def __init__(self, dimension: int, num_layers: int = 2, skip: bool = True):
|
| 15 |
+
super().__init__()
|
| 16 |
+
self.skip = skip
|
| 17 |
+
self.lstm = nn.LSTM(dimension, dimension, num_layers)
|
| 18 |
+
|
| 19 |
+
def forward(self, x):
|
| 20 |
+
x = x.permute(2, 0, 1)
|
| 21 |
+
y, _ = self.lstm(x)
|
| 22 |
+
if self.skip:
|
| 23 |
+
y = y + x
|
| 24 |
+
y = y.permute(1, 2, 0)
|
| 25 |
+
return y
|
src/audiocraft/modules/rope.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import typing as tp
|
| 8 |
+
|
| 9 |
+
from torch import nn
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class XPos(nn.Module):
|
| 14 |
+
"""Length-extrapolatable positional embedding (xPos) from [Sun et al 2022](https://arxiv.org/abs/2212.10554v1).
|
| 15 |
+
This applies an exponential decay to the RoPE rotation matrix.
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
dim (int): Embedding dimension.
|
| 19 |
+
smoothing (float): Smoothing factor applied to the decay rates.
|
| 20 |
+
base_scale (int): Base decay rate, given in terms of scaling time.
|
| 21 |
+
device (torch.device, optional): Device on which to initialize the module.
|
| 22 |
+
dtype (torch.dtype): dtype to use to generate the embedding.
|
| 23 |
+
"""
|
| 24 |
+
def __init__(self, dim: int, smoothing: float = 0.4, base_scale: int = 512,
|
| 25 |
+
device=None, dtype: torch.dtype = torch.float32):
|
| 26 |
+
super().__init__()
|
| 27 |
+
assert dim % 2 == 0
|
| 28 |
+
assert dtype in [torch.float64, torch.float32]
|
| 29 |
+
self.dtype = dtype
|
| 30 |
+
self.base_scale = base_scale
|
| 31 |
+
|
| 32 |
+
half_dim = dim // 2
|
| 33 |
+
adim = torch.arange(half_dim, device=device, dtype=dtype)
|
| 34 |
+
decay_rates = (adim / half_dim + smoothing) / (1.0 + smoothing)
|
| 35 |
+
self.register_buffer("decay_rates", decay_rates)
|
| 36 |
+
self.decay: tp.Optional[torch.Tensor] = None
|
| 37 |
+
|
| 38 |
+
def get_decay(self, start: int, end: int):
|
| 39 |
+
"""Create complex decay tensor, cache values for fast computation."""
|
| 40 |
+
if self.decay is None or end > self.decay.shape[0]:
|
| 41 |
+
assert isinstance(self.decay_rates, torch.Tensor) # Satisfy type checker.
|
| 42 |
+
idx = torch.arange(end, device=self.decay_rates.device, dtype=self.dtype)
|
| 43 |
+
power = idx / self.base_scale
|
| 44 |
+
scale = self.decay_rates ** power.unsqueeze(-1)
|
| 45 |
+
self.decay = torch.polar(scale, torch.zeros_like(scale))
|
| 46 |
+
return self.decay[start:end] # [T, C/2]
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class RotaryEmbedding(nn.Module):
|
| 50 |
+
"""Rotary positional embedding (RoPE) from [Su et al 2022](https://arxiv.org/abs/2104.09864).
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
dim (int): Embedding dimension (twice the number of frequencies).
|
| 54 |
+
max_period (float): Maximum period of the rotation frequencies.
|
| 55 |
+
xpos (bool): Use xPos, applies an exponential decay to rotation matrix.
|
| 56 |
+
scale (float): Scale of positional embedding, set to 0 to deactivate.
|
| 57 |
+
device (torch.device, optional): Device on which to initialize the module.
|
| 58 |
+
dtype (torch.dtype): dtype to use to generate the embedding.
|
| 59 |
+
"""
|
| 60 |
+
def __init__(self, dim: int, max_period: float = 10000.0, xpos: bool = False,
|
| 61 |
+
scale: float = 1.0, device=None, dtype: torch.dtype = torch.float32):
|
| 62 |
+
super().__init__()
|
| 63 |
+
assert dim % 2 == 0
|
| 64 |
+
self.scale = scale
|
| 65 |
+
assert dtype in [torch.float64, torch.float32]
|
| 66 |
+
self.dtype = dtype
|
| 67 |
+
|
| 68 |
+
adim = torch.arange(0, dim, 2, device=device, dtype=dtype)[: (dim // 2)]
|
| 69 |
+
frequencies = 1.0 / (max_period ** (adim / dim))
|
| 70 |
+
self.register_buffer("frequencies", frequencies)
|
| 71 |
+
self.rotation: tp.Optional[torch.Tensor] = None
|
| 72 |
+
|
| 73 |
+
self.xpos = XPos(dim, device=device, dtype=dtype) if xpos else None
|
| 74 |
+
|
| 75 |
+
def get_rotation(self, start: int, end: int):
|
| 76 |
+
"""Create complex rotation tensor, cache values for fast computation."""
|
| 77 |
+
if self.rotation is None or end > self.rotation.shape[0]:
|
| 78 |
+
assert isinstance(self.frequencies, torch.Tensor) # Satisfy type checker.
|
| 79 |
+
idx = torch.arange(end, device=self.frequencies.device, dtype=self.dtype)
|
| 80 |
+
angles = torch.outer(idx, self.frequencies)
|
| 81 |
+
self.rotation = torch.polar(torch.ones_like(angles), angles)
|
| 82 |
+
return self.rotation[start:end]
|
| 83 |
+
|
| 84 |
+
def rotate(self, x: torch.Tensor, start: int = 0, time_dim: int = 1, invert_decay: bool = False):
|
| 85 |
+
"""Apply rope rotation to query or key tensor."""
|
| 86 |
+
T = x.shape[time_dim]
|
| 87 |
+
target_shape = [1] * x.dim()
|
| 88 |
+
target_shape[time_dim] = T
|
| 89 |
+
target_shape[-1] = -1
|
| 90 |
+
rotation = self.get_rotation(start, start + T).view(target_shape)
|
| 91 |
+
|
| 92 |
+
if self.xpos:
|
| 93 |
+
decay = self.xpos.get_decay(start, start + T).view(target_shape)
|
| 94 |
+
else:
|
| 95 |
+
decay = 1.0
|
| 96 |
+
|
| 97 |
+
if invert_decay:
|
| 98 |
+
decay = decay ** -1
|
| 99 |
+
|
| 100 |
+
x_complex = torch.view_as_complex(x.to(self.dtype).reshape(*x.shape[:-1], -1, 2))
|
| 101 |
+
scaled_rotation = (rotation * decay) * self.scale + (1.0 - self.scale)
|
| 102 |
+
x_out = torch.view_as_real(x_complex * scaled_rotation).view_as(x)
|
| 103 |
+
|
| 104 |
+
return x_out.type_as(x)
|
| 105 |
+
|
| 106 |
+
def rotate_qk(self, query: torch.Tensor, key: torch.Tensor, start: int = 0, time_dim: int = 1):
|
| 107 |
+
""" Apply rope rotation to both query and key tensors.
|
| 108 |
+
Supports streaming mode, in which query and key are not expected to have the same shape.
|
| 109 |
+
In streaming mode, key will be of length [P + C] with P the cached past timesteps, but
|
| 110 |
+
query will be [C] (typically C == 1).
|
| 111 |
+
|
| 112 |
+
Args:
|
| 113 |
+
query (torch.Tensor): Query to rotate.
|
| 114 |
+
key (torch.Tensor): Key to rotate.
|
| 115 |
+
start (int): Start index of the sequence for time offset.
|
| 116 |
+
time_dim (int): which dimension represent the time steps.
|
| 117 |
+
"""
|
| 118 |
+
query_timesteps = query.shape[time_dim]
|
| 119 |
+
key_timesteps = key.shape[time_dim]
|
| 120 |
+
streaming_offset = key_timesteps - query_timesteps
|
| 121 |
+
|
| 122 |
+
query_out = self.rotate(query, start + streaming_offset, time_dim)
|
| 123 |
+
key_out = self.rotate(key, start, time_dim, invert_decay=True)
|
| 124 |
+
|
| 125 |
+
return query_out, key_out
|
src/audiocraft/modules/seanet.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import typing as tp
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
|
| 12 |
+
from .conv import StreamableConv1d, StreamableConvTranspose1d
|
| 13 |
+
from .lstm import StreamableLSTM
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class SEANetResnetBlock(nn.Module):
|
| 17 |
+
"""Residual block from SEANet model.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
dim (int): Dimension of the input/output.
|
| 21 |
+
kernel_sizes (list): List of kernel sizes for the convolutions.
|
| 22 |
+
dilations (list): List of dilations for the convolutions.
|
| 23 |
+
activation (str): Activation function.
|
| 24 |
+
activation_params (dict): Parameters to provide to the activation function.
|
| 25 |
+
norm (str): Normalization method.
|
| 26 |
+
norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
|
| 27 |
+
causal (bool): Whether to use fully causal convolution.
|
| 28 |
+
pad_mode (str): Padding mode for the convolutions.
|
| 29 |
+
compress (int): Reduced dimensionality in residual branches (from Demucs v3).
|
| 30 |
+
true_skip (bool): Whether to use true skip connection or a simple
|
| 31 |
+
(streamable) convolution as the skip connection.
|
| 32 |
+
"""
|
| 33 |
+
def __init__(self, dim: int, kernel_sizes: tp.List[int] = [3, 1], dilations: tp.List[int] = [1, 1],
|
| 34 |
+
activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
|
| 35 |
+
norm: str = 'none', norm_params: tp.Dict[str, tp.Any] = {}, causal: bool = False,
|
| 36 |
+
pad_mode: str = 'reflect', compress: int = 2, true_skip: bool = True):
|
| 37 |
+
super().__init__()
|
| 38 |
+
assert len(kernel_sizes) == len(dilations), 'Number of kernel sizes should match number of dilations'
|
| 39 |
+
act = getattr(nn, activation)
|
| 40 |
+
hidden = dim // compress
|
| 41 |
+
block = []
|
| 42 |
+
for i, (kernel_size, dilation) in enumerate(zip(kernel_sizes, dilations)):
|
| 43 |
+
in_chs = dim if i == 0 else hidden
|
| 44 |
+
out_chs = dim if i == len(kernel_sizes) - 1 else hidden
|
| 45 |
+
block += [
|
| 46 |
+
act(**activation_params),
|
| 47 |
+
StreamableConv1d(in_chs, out_chs, kernel_size=kernel_size, dilation=dilation,
|
| 48 |
+
norm=norm, norm_kwargs=norm_params,
|
| 49 |
+
causal=causal, pad_mode=pad_mode),
|
| 50 |
+
]
|
| 51 |
+
self.block = nn.Sequential(*block)
|
| 52 |
+
self.shortcut: nn.Module
|
| 53 |
+
if true_skip:
|
| 54 |
+
self.shortcut = nn.Identity()
|
| 55 |
+
else:
|
| 56 |
+
self.shortcut = StreamableConv1d(dim, dim, kernel_size=1, norm=norm, norm_kwargs=norm_params,
|
| 57 |
+
causal=causal, pad_mode=pad_mode)
|
| 58 |
+
|
| 59 |
+
def forward(self, x):
|
| 60 |
+
return self.shortcut(x) + self.block(x)
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class SEANetEncoder(nn.Module):
|
| 64 |
+
"""SEANet encoder.
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
channels (int): Audio channels.
|
| 68 |
+
dimension (int): Intermediate representation dimension.
|
| 69 |
+
n_filters (int): Base width for the model.
|
| 70 |
+
n_residual_layers (int): nb of residual layers.
|
| 71 |
+
ratios (Sequence[int]): kernel size and stride ratios. The encoder uses downsampling ratios instead of
|
| 72 |
+
upsampling ratios, hence it will use the ratios in the reverse order to the ones specified here
|
| 73 |
+
that must match the decoder order. We use the decoder order as some models may only employ the decoder.
|
| 74 |
+
activation (str): Activation function.
|
| 75 |
+
activation_params (dict): Parameters to provide to the activation function.
|
| 76 |
+
norm (str): Normalization method.
|
| 77 |
+
norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
|
| 78 |
+
kernel_size (int): Kernel size for the initial convolution.
|
| 79 |
+
last_kernel_size (int): Kernel size for the initial convolution.
|
| 80 |
+
residual_kernel_size (int): Kernel size for the residual layers.
|
| 81 |
+
dilation_base (int): How much to increase the dilation with each layer.
|
| 82 |
+
causal (bool): Whether to use fully causal convolution.
|
| 83 |
+
pad_mode (str): Padding mode for the convolutions.
|
| 84 |
+
true_skip (bool): Whether to use true skip connection or a simple
|
| 85 |
+
(streamable) convolution as the skip connection in the residual network blocks.
|
| 86 |
+
compress (int): Reduced dimensionality in residual branches (from Demucs v3).
|
| 87 |
+
lstm (int): Number of LSTM layers at the end of the encoder.
|
| 88 |
+
disable_norm_outer_blocks (int): Number of blocks for which we don't apply norm.
|
| 89 |
+
For the encoder, it corresponds to the N first blocks.
|
| 90 |
+
"""
|
| 91 |
+
def __init__(self, channels: int = 1, dimension: int = 128, n_filters: int = 32, n_residual_layers: int = 3,
|
| 92 |
+
ratios: tp.List[int] = [8, 5, 4, 2], activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
|
| 93 |
+
norm: str = 'none', norm_params: tp.Dict[str, tp.Any] = {}, kernel_size: int = 7,
|
| 94 |
+
last_kernel_size: int = 7, residual_kernel_size: int = 3, dilation_base: int = 2, causal: bool = False,
|
| 95 |
+
pad_mode: str = 'reflect', true_skip: bool = True, compress: int = 2, lstm: int = 0,
|
| 96 |
+
disable_norm_outer_blocks: int = 0):
|
| 97 |
+
super().__init__()
|
| 98 |
+
self.channels = channels
|
| 99 |
+
self.dimension = dimension
|
| 100 |
+
self.n_filters = n_filters
|
| 101 |
+
self.ratios = list(reversed(ratios))
|
| 102 |
+
del ratios
|
| 103 |
+
self.n_residual_layers = n_residual_layers
|
| 104 |
+
self.hop_length = np.prod(self.ratios)
|
| 105 |
+
self.n_blocks = len(self.ratios) + 2 # first and last conv + residual blocks
|
| 106 |
+
self.disable_norm_outer_blocks = disable_norm_outer_blocks
|
| 107 |
+
assert self.disable_norm_outer_blocks >= 0 and self.disable_norm_outer_blocks <= self.n_blocks, \
|
| 108 |
+
"Number of blocks for which to disable norm is invalid." \
|
| 109 |
+
"It should be lower or equal to the actual number of blocks in the network and greater or equal to 0."
|
| 110 |
+
|
| 111 |
+
act = getattr(nn, activation)
|
| 112 |
+
mult = 1
|
| 113 |
+
model: tp.List[nn.Module] = [
|
| 114 |
+
StreamableConv1d(channels, mult * n_filters, kernel_size,
|
| 115 |
+
norm='none' if self.disable_norm_outer_blocks >= 1 else norm,
|
| 116 |
+
norm_kwargs=norm_params, causal=causal, pad_mode=pad_mode)
|
| 117 |
+
]
|
| 118 |
+
# Downsample to raw audio scale
|
| 119 |
+
for i, ratio in enumerate(self.ratios):
|
| 120 |
+
block_norm = 'none' if self.disable_norm_outer_blocks >= i + 2 else norm
|
| 121 |
+
# Add residual layers
|
| 122 |
+
for j in range(n_residual_layers):
|
| 123 |
+
model += [
|
| 124 |
+
SEANetResnetBlock(mult * n_filters, kernel_sizes=[residual_kernel_size, 1],
|
| 125 |
+
dilations=[dilation_base ** j, 1],
|
| 126 |
+
norm=block_norm, norm_params=norm_params,
|
| 127 |
+
activation=activation, activation_params=activation_params,
|
| 128 |
+
causal=causal, pad_mode=pad_mode, compress=compress, true_skip=true_skip)]
|
| 129 |
+
|
| 130 |
+
# Add downsampling layers
|
| 131 |
+
model += [
|
| 132 |
+
act(**activation_params),
|
| 133 |
+
StreamableConv1d(mult * n_filters, mult * n_filters * 2,
|
| 134 |
+
kernel_size=ratio * 2, stride=ratio,
|
| 135 |
+
norm=block_norm, norm_kwargs=norm_params,
|
| 136 |
+
causal=causal, pad_mode=pad_mode),
|
| 137 |
+
]
|
| 138 |
+
mult *= 2
|
| 139 |
+
|
| 140 |
+
if lstm:
|
| 141 |
+
model += [StreamableLSTM(mult * n_filters, num_layers=lstm)]
|
| 142 |
+
|
| 143 |
+
model += [
|
| 144 |
+
act(**activation_params),
|
| 145 |
+
StreamableConv1d(mult * n_filters, dimension, last_kernel_size,
|
| 146 |
+
norm='none' if self.disable_norm_outer_blocks == self.n_blocks else norm,
|
| 147 |
+
norm_kwargs=norm_params, causal=causal, pad_mode=pad_mode)
|
| 148 |
+
]
|
| 149 |
+
|
| 150 |
+
self.model = nn.Sequential(*model)
|
| 151 |
+
|
| 152 |
+
def forward(self, x):
|
| 153 |
+
return self.model(x)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
class SEANetDecoder(nn.Module):
|
| 157 |
+
"""SEANet decoder.
|
| 158 |
+
|
| 159 |
+
Args:
|
| 160 |
+
channels (int): Audio channels.
|
| 161 |
+
dimension (int): Intermediate representation dimension.
|
| 162 |
+
n_filters (int): Base width for the model.
|
| 163 |
+
n_residual_layers (int): nb of residual layers.
|
| 164 |
+
ratios (Sequence[int]): kernel size and stride ratios.
|
| 165 |
+
activation (str): Activation function.
|
| 166 |
+
activation_params (dict): Parameters to provide to the activation function.
|
| 167 |
+
final_activation (str): Final activation function after all convolutions.
|
| 168 |
+
final_activation_params (dict): Parameters to provide to the activation function.
|
| 169 |
+
norm (str): Normalization method.
|
| 170 |
+
norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
|
| 171 |
+
kernel_size (int): Kernel size for the initial convolution.
|
| 172 |
+
last_kernel_size (int): Kernel size for the initial convolution.
|
| 173 |
+
residual_kernel_size (int): Kernel size for the residual layers.
|
| 174 |
+
dilation_base (int): How much to increase the dilation with each layer.
|
| 175 |
+
causal (bool): Whether to use fully causal convolution.
|
| 176 |
+
pad_mode (str): Padding mode for the convolutions.
|
| 177 |
+
true_skip (bool): Whether to use true skip connection or a simple.
|
| 178 |
+
(streamable) convolution as the skip connection in the residual network blocks.
|
| 179 |
+
compress (int): Reduced dimensionality in residual branches (from Demucs v3).
|
| 180 |
+
lstm (int): Number of LSTM layers at the end of the encoder.
|
| 181 |
+
disable_norm_outer_blocks (int): Number of blocks for which we don't apply norm.
|
| 182 |
+
For the decoder, it corresponds to the N last blocks.
|
| 183 |
+
trim_right_ratio (float): Ratio for trimming at the right of the transposed convolution under the causal setup.
|
| 184 |
+
If equal to 1.0, it means that all the trimming is done at the right.
|
| 185 |
+
"""
|
| 186 |
+
def __init__(self, channels: int = 1, dimension: int = 128, n_filters: int = 32, n_residual_layers: int = 3,
|
| 187 |
+
ratios: tp.List[int] = [8, 5, 4, 2], activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
|
| 188 |
+
final_activation: tp.Optional[str] = None, final_activation_params: tp.Optional[dict] = None,
|
| 189 |
+
norm: str = 'none', norm_params: tp.Dict[str, tp.Any] = {}, kernel_size: int = 7,
|
| 190 |
+
last_kernel_size: int = 7, residual_kernel_size: int = 3, dilation_base: int = 2, causal: bool = False,
|
| 191 |
+
pad_mode: str = 'reflect', true_skip: bool = True, compress: int = 2, lstm: int = 0,
|
| 192 |
+
disable_norm_outer_blocks: int = 0, trim_right_ratio: float = 1.0):
|
| 193 |
+
super().__init__()
|
| 194 |
+
self.dimension = dimension
|
| 195 |
+
self.channels = channels
|
| 196 |
+
self.n_filters = n_filters
|
| 197 |
+
self.ratios = ratios
|
| 198 |
+
del ratios
|
| 199 |
+
self.n_residual_layers = n_residual_layers
|
| 200 |
+
self.hop_length = np.prod(self.ratios)
|
| 201 |
+
self.n_blocks = len(self.ratios) + 2 # first and last conv + residual blocks
|
| 202 |
+
self.disable_norm_outer_blocks = disable_norm_outer_blocks
|
| 203 |
+
assert self.disable_norm_outer_blocks >= 0 and self.disable_norm_outer_blocks <= self.n_blocks, \
|
| 204 |
+
"Number of blocks for which to disable norm is invalid." \
|
| 205 |
+
"It should be lower or equal to the actual number of blocks in the network and greater or equal to 0."
|
| 206 |
+
|
| 207 |
+
act = getattr(nn, activation)
|
| 208 |
+
mult = int(2 ** len(self.ratios))
|
| 209 |
+
model: tp.List[nn.Module] = [
|
| 210 |
+
StreamableConv1d(dimension, mult * n_filters, kernel_size,
|
| 211 |
+
norm='none' if self.disable_norm_outer_blocks == self.n_blocks else norm,
|
| 212 |
+
norm_kwargs=norm_params, causal=causal, pad_mode=pad_mode)
|
| 213 |
+
]
|
| 214 |
+
|
| 215 |
+
if lstm:
|
| 216 |
+
model += [StreamableLSTM(mult * n_filters, num_layers=lstm)]
|
| 217 |
+
|
| 218 |
+
# Upsample to raw audio scale
|
| 219 |
+
for i, ratio in enumerate(self.ratios):
|
| 220 |
+
block_norm = 'none' if self.disable_norm_outer_blocks >= self.n_blocks - (i + 1) else norm
|
| 221 |
+
# Add upsampling layers
|
| 222 |
+
model += [
|
| 223 |
+
act(**activation_params),
|
| 224 |
+
StreamableConvTranspose1d(mult * n_filters, mult * n_filters // 2,
|
| 225 |
+
kernel_size=ratio * 2, stride=ratio,
|
| 226 |
+
norm=block_norm, norm_kwargs=norm_params,
|
| 227 |
+
causal=causal, trim_right_ratio=trim_right_ratio),
|
| 228 |
+
]
|
| 229 |
+
# Add residual layers
|
| 230 |
+
for j in range(n_residual_layers):
|
| 231 |
+
model += [
|
| 232 |
+
SEANetResnetBlock(mult * n_filters // 2, kernel_sizes=[residual_kernel_size, 1],
|
| 233 |
+
dilations=[dilation_base ** j, 1],
|
| 234 |
+
activation=activation, activation_params=activation_params,
|
| 235 |
+
norm=block_norm, norm_params=norm_params, causal=causal,
|
| 236 |
+
pad_mode=pad_mode, compress=compress, true_skip=true_skip)]
|
| 237 |
+
|
| 238 |
+
mult //= 2
|
| 239 |
+
|
| 240 |
+
# Add final layers
|
| 241 |
+
model += [
|
| 242 |
+
act(**activation_params),
|
| 243 |
+
StreamableConv1d(n_filters, channels, last_kernel_size,
|
| 244 |
+
norm='none' if self.disable_norm_outer_blocks >= 1 else norm,
|
| 245 |
+
norm_kwargs=norm_params, causal=causal, pad_mode=pad_mode)
|
| 246 |
+
]
|
| 247 |
+
# Add optional final activation to decoder (eg. tanh)
|
| 248 |
+
if final_activation is not None:
|
| 249 |
+
final_act = getattr(nn, final_activation)
|
| 250 |
+
final_activation_params = final_activation_params or {}
|
| 251 |
+
model += [
|
| 252 |
+
final_act(**final_activation_params)
|
| 253 |
+
]
|
| 254 |
+
self.model = nn.Sequential(*model)
|
| 255 |
+
|
| 256 |
+
def forward(self, z):
|
| 257 |
+
y = self.model(z)
|
| 258 |
+
return y
|
src/audiocraft/modules/streaming.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Streaming module API that should be implemented by all Streaming components,
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from contextlib import contextmanager
|
| 12 |
+
import typing as tp
|
| 13 |
+
from torch import nn
|
| 14 |
+
import torch
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
State = tp.Dict[str, torch.Tensor]
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class StreamingModule(nn.Module):
|
| 21 |
+
"""Common API for streaming components.
|
| 22 |
+
|
| 23 |
+
Each streaming component has a streaming state, which is just a dict[str, Tensor].
|
| 24 |
+
By convention, the first dim of each tensor must be the batch size.
|
| 25 |
+
Don't use dots in the key names, as this would clash with submodules
|
| 26 |
+
(like in state_dict).
|
| 27 |
+
|
| 28 |
+
If `self._is_streaming` is True, the component should use and remember
|
| 29 |
+
the proper state inside `self._streaming_state`.
|
| 30 |
+
|
| 31 |
+
To set a streaming component in streaming state, use
|
| 32 |
+
|
| 33 |
+
with module.streaming():
|
| 34 |
+
...
|
| 35 |
+
|
| 36 |
+
This will automatically reset the streaming state when exiting the context manager.
|
| 37 |
+
This also automatically propagates to all streaming children module.
|
| 38 |
+
|
| 39 |
+
Some module might also implement the `StreamingModule.flush` method, although
|
| 40 |
+
this one is trickier, as all parents module must be StreamingModule and implement
|
| 41 |
+
it as well for it to work properly. See `StreamingSequential` after.
|
| 42 |
+
"""
|
| 43 |
+
def __init__(self) -> None:
|
| 44 |
+
super().__init__()
|
| 45 |
+
self._streaming_state: State = {}
|
| 46 |
+
self._is_streaming = False
|
| 47 |
+
|
| 48 |
+
def _apply_named_streaming(self, fn: tp.Any):
|
| 49 |
+
for name, module in self.named_modules():
|
| 50 |
+
if isinstance(module, StreamingModule):
|
| 51 |
+
fn(name, module)
|
| 52 |
+
|
| 53 |
+
def _set_streaming(self, streaming: bool):
|
| 54 |
+
def _set_streaming(name, module):
|
| 55 |
+
module._is_streaming = streaming
|
| 56 |
+
self._apply_named_streaming(_set_streaming)
|
| 57 |
+
|
| 58 |
+
@contextmanager
|
| 59 |
+
def streaming(self):
|
| 60 |
+
"""Context manager to enter streaming mode. Reset streaming state on exit."""
|
| 61 |
+
self._set_streaming(True)
|
| 62 |
+
try:
|
| 63 |
+
yield
|
| 64 |
+
finally:
|
| 65 |
+
self._set_streaming(False)
|
| 66 |
+
self.reset_streaming()
|
| 67 |
+
|
| 68 |
+
def reset_streaming(self):
|
| 69 |
+
"""Reset the streaming state."""
|
| 70 |
+
def _reset(name: str, module: StreamingModule):
|
| 71 |
+
module._streaming_state.clear()
|
| 72 |
+
|
| 73 |
+
self._apply_named_streaming(_reset)
|
| 74 |
+
|
| 75 |
+
def get_streaming_state(self) -> State:
|
| 76 |
+
"""Return the streaming state, including that of sub-modules."""
|
| 77 |
+
state: State = {}
|
| 78 |
+
|
| 79 |
+
def _add(name: str, module: StreamingModule):
|
| 80 |
+
if name:
|
| 81 |
+
name += "."
|
| 82 |
+
for key, value in module._streaming_state.items():
|
| 83 |
+
state[name + key] = value
|
| 84 |
+
|
| 85 |
+
self._apply_named_streaming(_add)
|
| 86 |
+
return state
|
| 87 |
+
|
| 88 |
+
def set_streaming_state(self, state: State):
|
| 89 |
+
"""Set the streaming state, including that of sub-modules."""
|
| 90 |
+
state = dict(state)
|
| 91 |
+
|
| 92 |
+
def _set(name: str, module: StreamingModule):
|
| 93 |
+
if name:
|
| 94 |
+
name += "."
|
| 95 |
+
module._streaming_state.clear()
|
| 96 |
+
for key, value in list(state.items()):
|
| 97 |
+
# complexity is not ideal here, but probably fine.
|
| 98 |
+
if key.startswith(name):
|
| 99 |
+
local_key = key[len(name):]
|
| 100 |
+
if '.' not in local_key:
|
| 101 |
+
module._streaming_state[local_key] = value
|
| 102 |
+
del state[key]
|
| 103 |
+
|
| 104 |
+
self._apply_named_streaming(_set)
|
| 105 |
+
assert len(state) == 0, list(state.keys())
|
| 106 |
+
|
| 107 |
+
def flush(self, x: tp.Optional[torch.Tensor] = None):
|
| 108 |
+
"""Flush any remaining outputs that were waiting for completion.
|
| 109 |
+
Typically, for convolutions, this will add the final padding
|
| 110 |
+
and process the last buffer.
|
| 111 |
+
|
| 112 |
+
This should take an optional argument `x`, which will be provided
|
| 113 |
+
if a module before this one in the streaming pipeline has already
|
| 114 |
+
spitted out a flushed out buffer.
|
| 115 |
+
"""
|
| 116 |
+
if x is None:
|
| 117 |
+
return None
|
| 118 |
+
else:
|
| 119 |
+
return self(x)
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
class StreamingSequential(StreamingModule, nn.Sequential):
|
| 123 |
+
"""A streaming compatible alternative of `nn.Sequential`.
|
| 124 |
+
"""
|
| 125 |
+
def flush(self, x: tp.Optional[torch.Tensor] = None):
|
| 126 |
+
for module in self:
|
| 127 |
+
if isinstance(module, StreamingModule):
|
| 128 |
+
x = module.flush(x)
|
| 129 |
+
elif x is not None:
|
| 130 |
+
x = module(x)
|
| 131 |
+
return x
|
src/audiocraft/modules/transformer.py
ADDED
|
@@ -0,0 +1,755 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Transformer model, with streaming support, xformer attention support
|
| 9 |
+
and easy causal attention with a potentially finite receptive field.
|
| 10 |
+
|
| 11 |
+
See `StreamingTransformer` for more information.
|
| 12 |
+
|
| 13 |
+
Unlike regular PyTorch Transformer, we make the hard choice that batches are first.
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
import typing as tp
|
| 17 |
+
|
| 18 |
+
from einops import rearrange
|
| 19 |
+
import torch
|
| 20 |
+
import torch.nn as nn
|
| 21 |
+
from torch.nn import functional as F
|
| 22 |
+
from torch.utils.checkpoint import checkpoint as torch_checkpoint
|
| 23 |
+
from xformers import ops
|
| 24 |
+
|
| 25 |
+
from .rope import RotaryEmbedding
|
| 26 |
+
from .streaming import StreamingModule
|
| 27 |
+
|
| 28 |
+
_efficient_attention_backend: str = 'torch'
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def set_efficient_attention_backend(backend: str = 'torch'):
|
| 32 |
+
# Using torch by default, it seems a bit faster on older P100 GPUs (~20% faster).
|
| 33 |
+
global _efficient_attention_backend
|
| 34 |
+
assert _efficient_attention_backend in ['xformers', 'torch']
|
| 35 |
+
_efficient_attention_backend = backend
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _get_attention_time_dimension(memory_efficient: bool) -> int:
|
| 39 |
+
if _efficient_attention_backend == 'torch' and memory_efficient:
|
| 40 |
+
return 2
|
| 41 |
+
else:
|
| 42 |
+
return 1
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _is_profiled() -> bool:
|
| 46 |
+
# Return true if we are currently running with a xformers profiler activated.
|
| 47 |
+
try:
|
| 48 |
+
from xformers.profiler import profiler
|
| 49 |
+
except ImportError:
|
| 50 |
+
return False
|
| 51 |
+
return profiler._Profiler._CURRENT_PROFILER is not None
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def create_norm_fn(norm_type: str, dim: int, **kwargs) -> nn.Module:
|
| 55 |
+
"""Create normalization module for transformer encoder layer.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
norm_type (str): Normalization method.
|
| 59 |
+
dim (int): Dimension of the normalized layer.
|
| 60 |
+
**kwargs (dict): Additional parameters for normalization layer.
|
| 61 |
+
Returns:
|
| 62 |
+
nn.Module: Normalization module.
|
| 63 |
+
"""
|
| 64 |
+
if norm_type == 'layer_norm':
|
| 65 |
+
return nn.LayerNorm(dim, eps=1e-5, **kwargs)
|
| 66 |
+
else:
|
| 67 |
+
raise ValueError(f"Unknown norm type: {norm_type}")
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def create_sin_embedding(positions: torch.Tensor, dim: int, max_period: float = 10000,
|
| 71 |
+
dtype: torch.dtype = torch.float32) -> torch.Tensor:
|
| 72 |
+
"""Create sinusoidal positional embedding, with shape `[B, T, C]`.
|
| 73 |
+
|
| 74 |
+
Args:
|
| 75 |
+
positions (torch.Tensor): LongTensor of positions.
|
| 76 |
+
dim (int): Dimension of the embedding.
|
| 77 |
+
max_period (float): Maximum period of the cosine/sine functions.
|
| 78 |
+
dtype (torch.dtype or str): dtype to use to generate the embedding.
|
| 79 |
+
Returns:
|
| 80 |
+
torch.Tensor: Sinusoidal positional embedding.
|
| 81 |
+
"""
|
| 82 |
+
# We aim for BTC format
|
| 83 |
+
assert dim % 2 == 0
|
| 84 |
+
half_dim = dim // 2
|
| 85 |
+
positions = positions.to(dtype)
|
| 86 |
+
adim = torch.arange(half_dim, device=positions.device, dtype=dtype).view(1, 1, -1)
|
| 87 |
+
max_period_tensor = torch.full([], max_period, device=positions.device, dtype=dtype) # avoid sync point
|
| 88 |
+
phase = positions / (max_period_tensor ** (adim / (half_dim - 1)))
|
| 89 |
+
return torch.cat([torch.cos(phase), torch.sin(phase)], dim=-1)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def expand_repeated_kv(x: torch.Tensor, n_rep: int, memory_efficient: bool) -> torch.Tensor:
|
| 93 |
+
"""torch.repeat_interleave(x, dim=2, repeats=n_rep) from xlformers."""
|
| 94 |
+
if n_rep == 1:
|
| 95 |
+
return x
|
| 96 |
+
if _efficient_attention_backend == 'torch' and memory_efficient:
|
| 97 |
+
bs, n_kv_heads, slen, head_dim = x.shape
|
| 98 |
+
return (
|
| 99 |
+
x[:, :, None, :, :]
|
| 100 |
+
.expand(bs, n_kv_heads, n_rep, slen, head_dim)
|
| 101 |
+
.reshape(bs, n_kv_heads * n_rep, slen, head_dim)
|
| 102 |
+
)
|
| 103 |
+
else:
|
| 104 |
+
bs, slen, n_kv_heads, head_dim = x.shape
|
| 105 |
+
return (
|
| 106 |
+
x[:, :, :, None, :]
|
| 107 |
+
.expand(bs, slen, n_kv_heads, n_rep, head_dim)
|
| 108 |
+
.reshape(bs, slen, n_kv_heads * n_rep, head_dim)
|
| 109 |
+
)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
class LayerScale(nn.Module):
|
| 113 |
+
"""Layer scale from [Touvron et al 2021] (https://arxiv.org/pdf/2103.17239.pdf).
|
| 114 |
+
This rescales diagonally the residual outputs close to 0, with a learnt scale.
|
| 115 |
+
|
| 116 |
+
Args:
|
| 117 |
+
channels (int): Number of channels.
|
| 118 |
+
init (float): Initial scale.
|
| 119 |
+
channel_last (bool): If True, expect `[*, C]` shaped tensors, otherwise, `[*, C, T]`.
|
| 120 |
+
device (torch.device or str, optional): Device on which to initialize the module.
|
| 121 |
+
dtype (torch.dtype, optional): dtype to use to initialize the module.
|
| 122 |
+
"""
|
| 123 |
+
def __init__(self, channels: int, init: float = 1e-4, channel_last: bool = True,
|
| 124 |
+
device=None, dtype=None):
|
| 125 |
+
super().__init__()
|
| 126 |
+
self.channel_last = channel_last
|
| 127 |
+
self.scale = nn.Parameter(
|
| 128 |
+
torch.full((channels,), init,
|
| 129 |
+
requires_grad=True, device=device, dtype=dtype))
|
| 130 |
+
|
| 131 |
+
def forward(self, x: torch.Tensor):
|
| 132 |
+
if self.channel_last:
|
| 133 |
+
return self.scale * x
|
| 134 |
+
else:
|
| 135 |
+
return self.scale[:, None] * x
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
class StreamingMultiheadAttention(StreamingModule):
|
| 139 |
+
"""Similar to `nn.MultiheadAttention` but with support for streaming, causal evaluation.
|
| 140 |
+
|
| 141 |
+
Args:
|
| 142 |
+
embed_dim (int): Dimension to project to.
|
| 143 |
+
num_heads (int): Number of heads.
|
| 144 |
+
dropout (float): Dropout level.
|
| 145 |
+
bias (bool): Use bias in projections.
|
| 146 |
+
causal (bool): Causal mask applied automatically.
|
| 147 |
+
past_context (int, optional): Receptive field for the causal mask, infinite if None.
|
| 148 |
+
custom (bool): Use custom MHA implementation, for testing / benchmarking.
|
| 149 |
+
memory_efficient (bool): Use xformers based memory efficient attention.
|
| 150 |
+
attention_as_float32 (bool): Perform the attention as float32
|
| 151 |
+
(especially important with memory_efficient as autocast won't do this automatically).
|
| 152 |
+
rope (`RotaryEmbedding`, optional): Rope embedding to use.
|
| 153 |
+
cross_attention: Should be true when used as a cross attention.
|
| 154 |
+
All keys and values must be available at once, streaming is only for the queries.
|
| 155 |
+
Cannot be used with `causal` or `rope` (as it wouldn't make sens to
|
| 156 |
+
interpret the time steps in the keys relative to those in the queries).
|
| 157 |
+
safe_streaming (bool): Bug fix, will go away with xformers update.
|
| 158 |
+
qk_layer_norm (bool): Layer normalization applied to queries and keys before dot product.
|
| 159 |
+
kv_repeat (int): If > 1, will repeat keys and queries multiple times (need to divide num_heads).
|
| 160 |
+
This will lead to faster decoding time on A100 or other GPUs with tensorcore.
|
| 161 |
+
device (torch.device, optional): Device on which to initialize.
|
| 162 |
+
dtype (torch.dtype, optional): dtype to use.
|
| 163 |
+
"""
|
| 164 |
+
def __init__(self, embed_dim: int, num_heads: int, dropout: float = 0.0, bias: bool = True,
|
| 165 |
+
causal: bool = False, past_context: tp.Optional[int] = None, custom: bool = False,
|
| 166 |
+
memory_efficient: bool = False, attention_as_float32: bool = False,
|
| 167 |
+
rope: tp.Optional[RotaryEmbedding] = None, cross_attention: bool = False,
|
| 168 |
+
safe_streaming: bool = True, qk_layer_norm: bool = False, kv_repeat: int = 1,
|
| 169 |
+
device=None, dtype=None):
|
| 170 |
+
super().__init__()
|
| 171 |
+
factory_kwargs = {'device': device, 'dtype': dtype}
|
| 172 |
+
if past_context is not None:
|
| 173 |
+
assert causal
|
| 174 |
+
|
| 175 |
+
self.embed_dim = embed_dim
|
| 176 |
+
self.causal = causal
|
| 177 |
+
self.past_context = past_context
|
| 178 |
+
self.memory_efficient = memory_efficient
|
| 179 |
+
self.attention_as_float32 = attention_as_float32
|
| 180 |
+
self.rope = rope
|
| 181 |
+
self.cross_attention = cross_attention
|
| 182 |
+
self.safe_streaming = safe_streaming
|
| 183 |
+
self.num_heads = num_heads
|
| 184 |
+
self.dropout = dropout
|
| 185 |
+
self.kv_repeat = kv_repeat
|
| 186 |
+
if cross_attention:
|
| 187 |
+
assert not causal, "Causal cannot work with cross attention."
|
| 188 |
+
assert rope is None, "Rope cannot work with cross attention."
|
| 189 |
+
|
| 190 |
+
if memory_efficient:
|
| 191 |
+
_verify_xformers_memory_efficient_compat()
|
| 192 |
+
|
| 193 |
+
self.custom = _is_custom(custom, memory_efficient)
|
| 194 |
+
if self.custom:
|
| 195 |
+
out_dim = embed_dim
|
| 196 |
+
assert num_heads % kv_repeat == 0
|
| 197 |
+
assert not cross_attention or kv_repeat == 1
|
| 198 |
+
num_kv = num_heads // kv_repeat
|
| 199 |
+
kv_dim = (embed_dim // num_heads) * num_kv
|
| 200 |
+
out_dim += 2 * kv_dim
|
| 201 |
+
in_proj = nn.Linear(embed_dim, out_dim, bias=bias, **factory_kwargs)
|
| 202 |
+
# We try to follow the default PyTorch MHA convention, to easily compare results.
|
| 203 |
+
self.in_proj_weight = in_proj.weight
|
| 204 |
+
self.in_proj_bias = in_proj.bias
|
| 205 |
+
if bias:
|
| 206 |
+
self.in_proj_bias.data.zero_() # Following Pytorch convention
|
| 207 |
+
self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias, **factory_kwargs)
|
| 208 |
+
if bias:
|
| 209 |
+
self.out_proj.bias.data.zero_()
|
| 210 |
+
else:
|
| 211 |
+
assert not qk_layer_norm
|
| 212 |
+
assert kv_repeat == 1
|
| 213 |
+
self.mha = nn.MultiheadAttention(
|
| 214 |
+
embed_dim, num_heads, dropout=dropout, bias=bias, batch_first=True,
|
| 215 |
+
**factory_kwargs)
|
| 216 |
+
self.qk_layer_norm = qk_layer_norm
|
| 217 |
+
if qk_layer_norm:
|
| 218 |
+
assert self.custom
|
| 219 |
+
assert kv_repeat == 1
|
| 220 |
+
ln_dim = embed_dim
|
| 221 |
+
self.q_layer_norm = nn.LayerNorm(ln_dim)
|
| 222 |
+
self.k_layer_norm = nn.LayerNorm(ln_dim)
|
| 223 |
+
|
| 224 |
+
def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
|
| 225 |
+
if not self.custom:
|
| 226 |
+
# Support compat with regular MHA
|
| 227 |
+
keys = [n for n, _ in self.mha.named_parameters()]
|
| 228 |
+
for key in keys:
|
| 229 |
+
if prefix + key in state_dict:
|
| 230 |
+
state_dict[prefix + "mha." + key] = state_dict.pop(prefix + key)
|
| 231 |
+
super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
|
| 232 |
+
|
| 233 |
+
def _get_mask(self, current_steps: int, device: torch.device, dtype: torch.dtype):
|
| 234 |
+
# Return a causal mask, accounting for potentially stored past keys/values
|
| 235 |
+
# We actually return a bias for the attention score, as this has the same
|
| 236 |
+
# convention both in the builtin MHA in Pytorch, and Xformers functions.
|
| 237 |
+
time_dim = _get_attention_time_dimension(self.memory_efficient)
|
| 238 |
+
if self.memory_efficient:
|
| 239 |
+
from xformers.ops import LowerTriangularMask
|
| 240 |
+
if current_steps == 1:
|
| 241 |
+
# If we only have one step, then we do not need a mask.
|
| 242 |
+
return None
|
| 243 |
+
elif 'past_keys' in self._streaming_state:
|
| 244 |
+
raise RuntimeError("Not supported at the moment")
|
| 245 |
+
else:
|
| 246 |
+
# Then we can safely use a lower triangular mask
|
| 247 |
+
return LowerTriangularMask()
|
| 248 |
+
if self._streaming_state:
|
| 249 |
+
past_keys = self._streaming_state['past_keys']
|
| 250 |
+
past_steps = past_keys.shape[time_dim]
|
| 251 |
+
else:
|
| 252 |
+
past_steps = 0
|
| 253 |
+
|
| 254 |
+
queries_pos = torch.arange(
|
| 255 |
+
past_steps, current_steps + past_steps, device=device).view(-1, 1)
|
| 256 |
+
keys_pos = torch.arange(past_steps + current_steps, device=device).view(1, -1)
|
| 257 |
+
delta = queries_pos - keys_pos
|
| 258 |
+
valid = delta >= 0
|
| 259 |
+
if self.past_context is not None:
|
| 260 |
+
valid &= (delta <= self.past_context)
|
| 261 |
+
return torch.where(
|
| 262 |
+
valid,
|
| 263 |
+
torch.zeros([], device=device, dtype=dtype),
|
| 264 |
+
torch.full([], float('-inf'), device=device, dtype=dtype))
|
| 265 |
+
|
| 266 |
+
def _complete_kv(self, k, v):
|
| 267 |
+
time_dim = _get_attention_time_dimension(self.memory_efficient)
|
| 268 |
+
if self.cross_attention:
|
| 269 |
+
# With cross attention we assume all keys and values
|
| 270 |
+
# are already available, and streaming is with respect
|
| 271 |
+
# to the queries only.
|
| 272 |
+
return k, v
|
| 273 |
+
# Complete the key/value pair using the streaming state.
|
| 274 |
+
if self._streaming_state:
|
| 275 |
+
pk = self._streaming_state['past_keys']
|
| 276 |
+
nk = torch.cat([pk, k], dim=time_dim)
|
| 277 |
+
if v is k:
|
| 278 |
+
nv = nk
|
| 279 |
+
else:
|
| 280 |
+
pv = self._streaming_state['past_values']
|
| 281 |
+
nv = torch.cat([pv, v], dim=time_dim)
|
| 282 |
+
else:
|
| 283 |
+
nk = k
|
| 284 |
+
nv = v
|
| 285 |
+
|
| 286 |
+
assert nk.shape[time_dim] == nv.shape[time_dim]
|
| 287 |
+
offset = 0
|
| 288 |
+
if self.past_context is not None:
|
| 289 |
+
offset = max(0, nk.shape[time_dim] - self.past_context)
|
| 290 |
+
if self._is_streaming:
|
| 291 |
+
self._streaming_state['past_keys'] = nk[:, offset:]
|
| 292 |
+
if v is not k:
|
| 293 |
+
self._streaming_state['past_values'] = nv[:, offset:]
|
| 294 |
+
if 'offset' in self._streaming_state:
|
| 295 |
+
self._streaming_state['offset'] += offset
|
| 296 |
+
else:
|
| 297 |
+
self._streaming_state['offset'] = torch.tensor(0)
|
| 298 |
+
return nk, nv
|
| 299 |
+
|
| 300 |
+
def _apply_rope(self, query: torch.Tensor, key: torch.Tensor):
|
| 301 |
+
time_dim = _get_attention_time_dimension(self.memory_efficient)
|
| 302 |
+
# Apply rope embeddings to query and key tensors.
|
| 303 |
+
assert self.rope is not None
|
| 304 |
+
if 'past_keys' in self._streaming_state:
|
| 305 |
+
past_keys_offset = self._streaming_state['past_keys'].shape[1]
|
| 306 |
+
else:
|
| 307 |
+
past_keys_offset = 0
|
| 308 |
+
if 'offset' in self._streaming_state:
|
| 309 |
+
past_context_offset = int(self._streaming_state['offset'].item())
|
| 310 |
+
else:
|
| 311 |
+
past_context_offset = 0
|
| 312 |
+
streaming_offset = past_context_offset + past_keys_offset
|
| 313 |
+
return self.rope.rotate_qk(query, key, start=streaming_offset, time_dim=time_dim)
|
| 314 |
+
|
| 315 |
+
def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
|
| 316 |
+
key_padding_mask=None, need_weights=False, attn_mask=None,
|
| 317 |
+
average_attn_weights=True, is_causal=False):
|
| 318 |
+
assert not is_causal, ("New param added in torch 2.0.1 not supported, "
|
| 319 |
+
"use the causal args in the constructor.")
|
| 320 |
+
|
| 321 |
+
time_dim = _get_attention_time_dimension(self.memory_efficient)
|
| 322 |
+
if time_dim == 2:
|
| 323 |
+
layout = "b h t d"
|
| 324 |
+
else:
|
| 325 |
+
layout = "b t h d"
|
| 326 |
+
dtype = query.dtype
|
| 327 |
+
if self._is_streaming:
|
| 328 |
+
assert self.causal or self.cross_attention, \
|
| 329 |
+
"Streaming only available for causal or cross attention"
|
| 330 |
+
|
| 331 |
+
custom_attn_mask = attn_mask is not None
|
| 332 |
+
|
| 333 |
+
if self.causal:
|
| 334 |
+
assert attn_mask is None
|
| 335 |
+
# At the moment we specialize only for the self-attention case.
|
| 336 |
+
assert query.shape[1] == key.shape[1], "Causal only for same length query / key / value"
|
| 337 |
+
assert value.shape[1] == key.shape[1], "Causal only for same length query / key / value"
|
| 338 |
+
attn_mask = self._get_mask(query.shape[1], query.device, query.dtype)
|
| 339 |
+
|
| 340 |
+
if self.custom:
|
| 341 |
+
# custom implementation
|
| 342 |
+
assert need_weights is False
|
| 343 |
+
assert key_padding_mask is None
|
| 344 |
+
if self.cross_attention:
|
| 345 |
+
# Different queries, keys, values, we have to spit manually the weights
|
| 346 |
+
# before applying the linear.
|
| 347 |
+
dim = self.in_proj_weight.shape[0] // 3
|
| 348 |
+
if self.in_proj_bias is None:
|
| 349 |
+
bias_q, bias_k, bias_v = None, None, None
|
| 350 |
+
else:
|
| 351 |
+
bias_q = self.in_proj_bias[:dim]
|
| 352 |
+
bias_k = self.in_proj_bias[dim: 2 * dim]
|
| 353 |
+
bias_v = self.in_proj_bias[2 * dim:]
|
| 354 |
+
q = nn.functional.linear(query, self.in_proj_weight[:dim], bias_q)
|
| 355 |
+
# todo: when streaming, we could actually save k, v and check the shape actually match.
|
| 356 |
+
k = nn.functional.linear(key, self.in_proj_weight[dim: 2 * dim], bias_k)
|
| 357 |
+
v = nn.functional.linear(value, self.in_proj_weight[2 * dim:], bias_v)
|
| 358 |
+
if self.qk_layer_norm is True:
|
| 359 |
+
q = self.q_layer_norm(q)
|
| 360 |
+
k = self.k_layer_norm(k)
|
| 361 |
+
q, k, v = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k, v]]
|
| 362 |
+
else:
|
| 363 |
+
if not _is_profiled():
|
| 364 |
+
# profiling breaks that propertysomehow.
|
| 365 |
+
assert query is key, "specialized implementation"
|
| 366 |
+
assert value is key, "specialized implementation"
|
| 367 |
+
projected = nn.functional.linear(query, self.in_proj_weight, self.in_proj_bias)
|
| 368 |
+
if self.kv_repeat == 1:
|
| 369 |
+
if time_dim == 2:
|
| 370 |
+
bound_layout = "b h p t d"
|
| 371 |
+
else:
|
| 372 |
+
bound_layout = "b t p h d"
|
| 373 |
+
packed = rearrange(projected, f"b t (p h d) -> {bound_layout}", p=3, h=self.num_heads)
|
| 374 |
+
q, k, v = ops.unbind(packed, dim=2)
|
| 375 |
+
else:
|
| 376 |
+
embed_dim = self.embed_dim
|
| 377 |
+
per_head_dim = (embed_dim // self.num_heads)
|
| 378 |
+
kv_heads = self.num_heads // self.kv_repeat
|
| 379 |
+
q = projected[:, :, :embed_dim]
|
| 380 |
+
start = embed_dim
|
| 381 |
+
end = start + per_head_dim * kv_heads
|
| 382 |
+
k = projected[:, :, start: end]
|
| 383 |
+
v = projected[:, :, end:]
|
| 384 |
+
q = rearrange(q, f"b t (h d) -> {layout}", h=self.num_heads)
|
| 385 |
+
k = rearrange(k, f"b t (h d) -> {layout}", h=kv_heads)
|
| 386 |
+
v = rearrange(v, f"b t (h d) -> {layout}", h=kv_heads)
|
| 387 |
+
|
| 388 |
+
if self.qk_layer_norm is True:
|
| 389 |
+
assert self.kv_repeat == 1
|
| 390 |
+
q, k = [rearrange(x, f"{layout} -> b t (h d)") for x in [q, k]]
|
| 391 |
+
q = self.q_layer_norm(q)
|
| 392 |
+
k = self.k_layer_norm(k)
|
| 393 |
+
q, k = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k]]
|
| 394 |
+
if self.rope:
|
| 395 |
+
q, k = self._apply_rope(q, k)
|
| 396 |
+
k, v = self._complete_kv(k, v)
|
| 397 |
+
if self.kv_repeat > 1:
|
| 398 |
+
k = expand_repeated_kv(k, self.kv_repeat, self.memory_efficient)
|
| 399 |
+
v = expand_repeated_kv(v, self.kv_repeat, self.memory_efficient)
|
| 400 |
+
if self.attention_as_float32:
|
| 401 |
+
q, k, v = [x.float() for x in [q, k, v]]
|
| 402 |
+
if self.memory_efficient:
|
| 403 |
+
if custom_attn_mask:
|
| 404 |
+
# When using a custom attn mask:
|
| 405 |
+
# Move to query's device, repeat for each sample, remove align8 padding
|
| 406 |
+
seq_len = query.shape[1]
|
| 407 |
+
attn_mask = attn_mask.to(q.dtype)
|
| 408 |
+
attn_mask = attn_mask.repeat((q.shape[0], 1, 1, 1))
|
| 409 |
+
attn_mask = attn_mask[..., :seq_len, :seq_len]
|
| 410 |
+
|
| 411 |
+
p = self.dropout if self.training else 0
|
| 412 |
+
if _efficient_attention_backend == 'torch':
|
| 413 |
+
x = torch.nn.functional.scaled_dot_product_attention(
|
| 414 |
+
q, k, v, is_causal=attn_mask is not None, dropout_p=p)
|
| 415 |
+
else:
|
| 416 |
+
x = ops.memory_efficient_attention(q, k, v, attn_mask, p=p)
|
| 417 |
+
else:
|
| 418 |
+
# We include the dot product as float32, for consistency
|
| 419 |
+
# with the other implementations that include that step
|
| 420 |
+
# as part of the attention. Note that when using `autocast`,
|
| 421 |
+
# the einsums would be done as bfloat16, but the softmax
|
| 422 |
+
# would be done as bfloat16, so `attention_as_float32` will
|
| 423 |
+
# extend a bit the range of operations done in float32,
|
| 424 |
+
# although this should make no difference.
|
| 425 |
+
q = q / q.shape[-1] ** 0.5
|
| 426 |
+
key_layout = layout.replace('t', 'k')
|
| 427 |
+
query_layout = layout
|
| 428 |
+
if self._is_streaming and self.safe_streaming and q.device.type == 'cuda':
|
| 429 |
+
with torch.autocast(device_type=q.device.type, dtype=torch.float32):
|
| 430 |
+
pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
|
| 431 |
+
else:
|
| 432 |
+
pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
|
| 433 |
+
if attn_mask is not None:
|
| 434 |
+
pre_w = pre_w + attn_mask
|
| 435 |
+
w = torch.softmax(pre_w, dim=-1)
|
| 436 |
+
w = F.dropout(w, self.dropout, training=self.training).to(v)
|
| 437 |
+
# Key and value have the same format.
|
| 438 |
+
x = torch.einsum(f"b h t k, {key_layout} -> {layout}", w, v)
|
| 439 |
+
x = x.to(dtype)
|
| 440 |
+
x = rearrange(x, f"{layout} -> b t (h d)", h=self.num_heads)
|
| 441 |
+
x = self.out_proj(x)
|
| 442 |
+
else:
|
| 443 |
+
key, value = self._complete_kv(key, value)
|
| 444 |
+
if self.attention_as_float32:
|
| 445 |
+
query, key, value = [x.float() for x in [query, key, value]]
|
| 446 |
+
x, _ = self.mha(
|
| 447 |
+
query, key, value, key_padding_mask,
|
| 448 |
+
need_weights, attn_mask, average_attn_weights)
|
| 449 |
+
x = x.to(dtype)
|
| 450 |
+
|
| 451 |
+
return x, None
|
| 452 |
+
|
| 453 |
+
|
| 454 |
+
class StreamingTransformerLayer(nn.TransformerEncoderLayer):
|
| 455 |
+
"""TransformerLayer with Streaming / Causal support.
|
| 456 |
+
This also integrates cross_attention, when passing `cross_attention=True`,
|
| 457 |
+
rather than having two separate classes like in PyTorch.
|
| 458 |
+
|
| 459 |
+
Args:
|
| 460 |
+
d_model (int): Dimension of the data.
|
| 461 |
+
num_heads (int): Number of heads.
|
| 462 |
+
dim_feedforward (int): Intermediate dimension of FF module.
|
| 463 |
+
dropout (float): Dropout both for MHA and FF.
|
| 464 |
+
bias_ff (bool): Use bias for FF.
|
| 465 |
+
bias_attn (bool): Use bias for MHA.
|
| 466 |
+
causal (bool): Causal mask applied automatically.
|
| 467 |
+
past_context (int, optional): Receptive field for the causal mask, infinite if None.
|
| 468 |
+
custom (bool): Use custom MHA implementation, for testing / benchmarking.
|
| 469 |
+
memory_efficient (bool): Use xformers based memory efficient attention.
|
| 470 |
+
attention_as_float32 (bool): Perform the attention as float32
|
| 471 |
+
(especially important with memory_efficient as autocast won't do this automatically).
|
| 472 |
+
qk_layer_norm (bool): Layer normalization applied to queries and keys before dot product in attention.
|
| 473 |
+
qk_layer_norm_cross (bool): Same for the cross attention.
|
| 474 |
+
cross_attention (bool): If True, expect to get secondary input for cross-attention.
|
| 475 |
+
Cross attention will use the default MHA, as it typically won't require
|
| 476 |
+
special treatment.
|
| 477 |
+
layer_scale (float, optional): If not None, LayerScale will be used with
|
| 478 |
+
the given value as initial scale.
|
| 479 |
+
rope (`RotaryEmbedding`, optional): Rope embedding to use.
|
| 480 |
+
attention_dropout (float, optional): If not None, separate the value of the dimension dropout
|
| 481 |
+
in FFN and of the attention dropout.
|
| 482 |
+
kv_repeat (int): If > 1, will repeat keys and queries multiple times (need to divide num_heads).
|
| 483 |
+
This will lead to faster decoding time on A100 or other GPUs with tensorcore.
|
| 484 |
+
device (torch.device, optional): Device on which to initialize.
|
| 485 |
+
dtype (torch.dtype, optional): dtype to use.
|
| 486 |
+
**kwargs: See `nn.TransformerEncoderLayer`.
|
| 487 |
+
"""
|
| 488 |
+
def __init__(self, d_model: int, num_heads: int, dim_feedforward: int = 2048, dropout: float = 0.1,
|
| 489 |
+
bias_ff: bool = True, bias_attn: bool = True, causal: bool = False,
|
| 490 |
+
past_context: tp.Optional[int] = None, custom: bool = False,
|
| 491 |
+
memory_efficient: bool = False, attention_as_float32: bool = False,
|
| 492 |
+
qk_layer_norm: bool = False, qk_layer_norm_cross: bool = False,
|
| 493 |
+
cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
|
| 494 |
+
rope: tp.Optional[RotaryEmbedding] = None, attention_dropout: tp.Optional[float] = None,
|
| 495 |
+
kv_repeat: int = 1, norm: str = 'layer_norm', device=None, dtype=None, **kwargs):
|
| 496 |
+
super().__init__(d_model, num_heads, dim_feedforward, dropout,
|
| 497 |
+
device=device, dtype=dtype, batch_first=True, **kwargs)
|
| 498 |
+
factory_kwargs = {'device': device, 'dtype': dtype}
|
| 499 |
+
# Redefine self_attn to our streaming multi-head attention
|
| 500 |
+
attn_kwargs: tp.Dict[str, tp.Any] = {
|
| 501 |
+
'embed_dim': d_model,
|
| 502 |
+
'num_heads': num_heads,
|
| 503 |
+
'dropout': dropout if attention_dropout is None else attention_dropout,
|
| 504 |
+
'bias': bias_attn,
|
| 505 |
+
'custom': custom,
|
| 506 |
+
'memory_efficient': memory_efficient,
|
| 507 |
+
'attention_as_float32': attention_as_float32,
|
| 508 |
+
}
|
| 509 |
+
self.self_attn: StreamingMultiheadAttention = StreamingMultiheadAttention(
|
| 510 |
+
causal=causal, past_context=past_context, rope=rope, qk_layer_norm=qk_layer_norm,
|
| 511 |
+
kv_repeat=kv_repeat, **attn_kwargs, **factory_kwargs) # type: ignore
|
| 512 |
+
# Redefine feedforward layers to expose bias parameter
|
| 513 |
+
self.linear1 = nn.Linear(d_model, dim_feedforward, bias=bias_ff, **factory_kwargs)
|
| 514 |
+
self.linear2 = nn.Linear(dim_feedforward, d_model, bias=bias_ff, **factory_kwargs)
|
| 515 |
+
|
| 516 |
+
self.layer_scale_1: nn.Module
|
| 517 |
+
self.layer_scale_2: nn.Module
|
| 518 |
+
if layer_scale is None:
|
| 519 |
+
self.layer_scale_1 = nn.Identity()
|
| 520 |
+
self.layer_scale_2 = nn.Identity()
|
| 521 |
+
else:
|
| 522 |
+
self.layer_scale_1 = LayerScale(d_model, layer_scale, **factory_kwargs)
|
| 523 |
+
self.layer_scale_2 = LayerScale(d_model, layer_scale, **factory_kwargs)
|
| 524 |
+
|
| 525 |
+
self.cross_attention: tp.Optional[nn.Module] = None
|
| 526 |
+
if cross_attention:
|
| 527 |
+
self.cross_attention = StreamingMultiheadAttention(
|
| 528 |
+
cross_attention=True, qk_layer_norm=qk_layer_norm_cross,
|
| 529 |
+
**attn_kwargs, **factory_kwargs)
|
| 530 |
+
# Norm and dropout
|
| 531 |
+
self.dropout_cross = nn.Dropout(dropout)
|
| 532 |
+
# eps value matching that used in PyTorch reference implementation.
|
| 533 |
+
self.norm_cross = nn.LayerNorm(d_model, eps=1e-5, **factory_kwargs)
|
| 534 |
+
self.layer_scale_cross: nn.Module
|
| 535 |
+
if layer_scale is None:
|
| 536 |
+
self.layer_scale_cross = nn.Identity()
|
| 537 |
+
else:
|
| 538 |
+
self.layer_scale_cross = LayerScale(d_model, layer_scale, **factory_kwargs)
|
| 539 |
+
self.norm1 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
|
| 540 |
+
self.norm2 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
|
| 541 |
+
|
| 542 |
+
def _cross_attention_block(self, src: torch.Tensor,
|
| 543 |
+
cross_attention_src: torch.Tensor) -> torch.Tensor:
|
| 544 |
+
assert self.cross_attention is not None
|
| 545 |
+
# queries are from src, keys and values from cross_attention_src.
|
| 546 |
+
x = self.cross_attention(
|
| 547 |
+
src, cross_attention_src, cross_attention_src, need_weights=False)[0]
|
| 548 |
+
return self.dropout_cross(x) # type: ignore
|
| 549 |
+
|
| 550 |
+
def forward(self, src: torch.Tensor, src_mask: tp.Optional[torch.Tensor] = None, # type: ignore
|
| 551 |
+
src_key_padding_mask: tp.Optional[torch.Tensor] = None,
|
| 552 |
+
cross_attention_src: tp.Optional[torch.Tensor] = None):
|
| 553 |
+
if self.cross_attention is None:
|
| 554 |
+
assert cross_attention_src is None
|
| 555 |
+
else:
|
| 556 |
+
assert cross_attention_src is not None
|
| 557 |
+
x = src
|
| 558 |
+
if self.norm_first:
|
| 559 |
+
x = x + self.layer_scale_1(
|
| 560 |
+
self._sa_block(self.norm1(x), src_mask, src_key_padding_mask))
|
| 561 |
+
if cross_attention_src is not None:
|
| 562 |
+
x = x + self.layer_scale_cross(
|
| 563 |
+
self._cross_attention_block(
|
| 564 |
+
self.norm_cross(x), cross_attention_src))
|
| 565 |
+
x = x + self.layer_scale_2(self._ff_block(self.norm2(x)))
|
| 566 |
+
else:
|
| 567 |
+
x = self.norm1(x + self.layer_scale_1(
|
| 568 |
+
self._sa_block(x, src_mask, src_key_padding_mask)))
|
| 569 |
+
if cross_attention_src is not None:
|
| 570 |
+
x = self.norm_cross(
|
| 571 |
+
x + self.layer_scale_cross(
|
| 572 |
+
self._cross_attention_block(src, cross_attention_src)))
|
| 573 |
+
x = self.norm2(x + self.layer_scale_2(self._ff_block(x)))
|
| 574 |
+
return x
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
class StreamingTransformer(StreamingModule):
|
| 578 |
+
"""Transformer with Streaming / Causal support.
|
| 579 |
+
|
| 580 |
+
Args:
|
| 581 |
+
d_model (int): Dimension of the data.
|
| 582 |
+
num_heads (int): Number of heads.
|
| 583 |
+
dim_feedforward (int): Intermediate dimension of FF module.
|
| 584 |
+
dropout (float): Dropout both for MHA and FF.
|
| 585 |
+
bias_ff (bool): Use bias for FF.
|
| 586 |
+
bias_attn (bool): Use bias for MHA.
|
| 587 |
+
causal (bool): Causal mask applied automatically.
|
| 588 |
+
past_context (int, optional): Receptive field for the causal mask, infinite if None.
|
| 589 |
+
custom (bool): Use custom MHA implementation, for testing / benchmarking.
|
| 590 |
+
memory_efficient (bool): Use xformers based memory efficient attention.
|
| 591 |
+
attention_as_float32 (bool): Perform the attention as float32
|
| 592 |
+
(especially important with memory_efficient as autocast won't do this automatically).
|
| 593 |
+
cross_attention (bool): If True, expect to get secondary input for cross-attention.
|
| 594 |
+
layer_scale (float, optional): If not None, LayerScale will be used
|
| 595 |
+
with the given value as initial scale.
|
| 596 |
+
positional_embedding (str): Positional embedding strategy (sin, rope, or sin_rope).
|
| 597 |
+
max_period (float): Maximum period of the time embedding.
|
| 598 |
+
positional_scale (float): Scale of positional embedding, set to 0 to deactivate.
|
| 599 |
+
xpos (bool): Apply xpos exponential decay to positional embedding (rope only).
|
| 600 |
+
lr (float, optional): learning rate override through the `make_optim_group` API.
|
| 601 |
+
weight_decay (float, optional): Weight_decay override through the `make_optim_group` API.
|
| 602 |
+
layer_class: (subclass of `StreamingTransformerLayer): class to use
|
| 603 |
+
to initialize the layers, allowing further customization outside of AudioCraft.
|
| 604 |
+
checkpointing (str): Checkpointing strategy to reduce memory usage.
|
| 605 |
+
No checkpointing if set to 'none'. Per layer checkpointing using PyTorch
|
| 606 |
+
if set to 'torch' (entire layer checkpointed, i.e. linears are evaluated twice,
|
| 607 |
+
minimal memory usage, but maximal runtime). Finally, `xformers_default` provide
|
| 608 |
+
a policy for opting-out some operations of the checkpointing like
|
| 609 |
+
linear layers and attention, providing a middle ground between speed and memory.
|
| 610 |
+
device (torch.device, optional): Device on which to initialize.
|
| 611 |
+
dtype (torch.dtype, optional): dtype to use.
|
| 612 |
+
**kwargs: See `nn.TransformerEncoderLayer`.
|
| 613 |
+
"""
|
| 614 |
+
def __init__(self, d_model: int, num_heads: int, num_layers: int, dim_feedforward: int = 2048,
|
| 615 |
+
dropout: float = 0.1, bias_ff: bool = True, bias_attn: bool = True,
|
| 616 |
+
causal: bool = False, past_context: tp.Optional[int] = None,
|
| 617 |
+
custom: bool = False, memory_efficient: bool = False, attention_as_float32: bool = False,
|
| 618 |
+
cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
|
| 619 |
+
positional_embedding: str = 'sin', max_period: float = 10_000, positional_scale: float = 1.,
|
| 620 |
+
xpos: bool = False, lr: tp.Optional[float] = None, weight_decay: tp.Optional[float] = None,
|
| 621 |
+
layer_class: tp.Type[StreamingTransformerLayer] = StreamingTransformerLayer,
|
| 622 |
+
checkpointing: str = 'none', device=None, dtype=None, **kwargs):
|
| 623 |
+
super().__init__()
|
| 624 |
+
assert d_model % num_heads == 0
|
| 625 |
+
|
| 626 |
+
self.positional_embedding = positional_embedding
|
| 627 |
+
self.max_period = max_period
|
| 628 |
+
self.positional_scale = positional_scale
|
| 629 |
+
self.weight_decay = weight_decay
|
| 630 |
+
self.lr = lr
|
| 631 |
+
|
| 632 |
+
assert positional_embedding in ['sin', 'rope', 'sin_rope']
|
| 633 |
+
self.rope: tp.Optional[RotaryEmbedding] = None
|
| 634 |
+
if self.positional_embedding in ['rope', 'sin_rope']:
|
| 635 |
+
assert _is_custom(custom, memory_efficient)
|
| 636 |
+
self.rope = RotaryEmbedding(d_model // num_heads, max_period=max_period,
|
| 637 |
+
xpos=xpos, scale=positional_scale, device=device)
|
| 638 |
+
|
| 639 |
+
self.checkpointing = checkpointing
|
| 640 |
+
|
| 641 |
+
assert checkpointing in ['none', 'torch', 'xformers_default', 'xformers_mm']
|
| 642 |
+
if self.checkpointing.startswith('xformers'):
|
| 643 |
+
_verify_xformers_internal_compat()
|
| 644 |
+
|
| 645 |
+
self.layers = nn.ModuleList()
|
| 646 |
+
for idx in range(num_layers):
|
| 647 |
+
self.layers.append(
|
| 648 |
+
layer_class(
|
| 649 |
+
d_model=d_model, num_heads=num_heads, dim_feedforward=dim_feedforward,
|
| 650 |
+
dropout=dropout, bias_ff=bias_ff, bias_attn=bias_attn,
|
| 651 |
+
causal=causal, past_context=past_context, custom=custom,
|
| 652 |
+
memory_efficient=memory_efficient, attention_as_float32=attention_as_float32,
|
| 653 |
+
cross_attention=cross_attention, layer_scale=layer_scale, rope=self.rope,
|
| 654 |
+
device=device, dtype=dtype, **kwargs))
|
| 655 |
+
|
| 656 |
+
if self.checkpointing != 'none':
|
| 657 |
+
for layer in self.layers:
|
| 658 |
+
# see audiocraft/optim/fsdp.py, magic signal to indicate this requires fixing the
|
| 659 |
+
# backward hook inside of FSDP...
|
| 660 |
+
layer._magma_checkpointed = True # type: ignore
|
| 661 |
+
|
| 662 |
+
def _apply_layer(self, layer, *args, **kwargs):
|
| 663 |
+
method = self.checkpointing
|
| 664 |
+
if method == 'none':
|
| 665 |
+
return layer(*args, **kwargs)
|
| 666 |
+
elif method == 'torch':
|
| 667 |
+
return torch_checkpoint(layer, *args, use_reentrant=False, **kwargs)
|
| 668 |
+
elif method.startswith('xformers'):
|
| 669 |
+
from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy
|
| 670 |
+
if method == 'xformers_default':
|
| 671 |
+
# those operations will be saved, and not recomputed.
|
| 672 |
+
# According to Francisco we can get smarter policies but this is a good start.
|
| 673 |
+
allow_list = [
|
| 674 |
+
"xformers.efficient_attention_forward_cutlass.default",
|
| 675 |
+
"xformers_flash.flash_fwd.default",
|
| 676 |
+
"aten.addmm.default",
|
| 677 |
+
"aten.mm.default",
|
| 678 |
+
]
|
| 679 |
+
elif method == 'xformers_mm':
|
| 680 |
+
# those operations will be saved, and not recomputed.
|
| 681 |
+
# According to Francisco we can get smarter policies but this is a good start.
|
| 682 |
+
allow_list = [
|
| 683 |
+
"aten.addmm.default",
|
| 684 |
+
"aten.mm.default",
|
| 685 |
+
]
|
| 686 |
+
else:
|
| 687 |
+
raise ValueError(f"xformers checkpointing xformers policy {method} is not known.")
|
| 688 |
+
policy_fn = _get_default_policy(allow_list)
|
| 689 |
+
return checkpoint(layer, *args, policy_fn=policy_fn, **kwargs)
|
| 690 |
+
else:
|
| 691 |
+
raise ValueError(f"Checkpointing method {method} is unknown.")
|
| 692 |
+
|
| 693 |
+
def forward(self, x: torch.Tensor, *args, **kwargs):
|
| 694 |
+
B, T, C = x.shape
|
| 695 |
+
|
| 696 |
+
if 'offsets' in self._streaming_state:
|
| 697 |
+
offsets = self._streaming_state['offsets']
|
| 698 |
+
else:
|
| 699 |
+
offsets = torch.zeros(B, dtype=torch.long, device=x.device)
|
| 700 |
+
|
| 701 |
+
if self.positional_embedding in ['sin', 'sin_rope']:
|
| 702 |
+
positions = torch.arange(T, device=x.device).view(1, -1, 1)
|
| 703 |
+
positions = positions + offsets.view(-1, 1, 1)
|
| 704 |
+
pos_emb = create_sin_embedding(positions, C, max_period=self.max_period, dtype=x.dtype)
|
| 705 |
+
x = x + self.positional_scale * pos_emb
|
| 706 |
+
|
| 707 |
+
for layer in self.layers:
|
| 708 |
+
x = self._apply_layer(layer, x, *args, **kwargs)
|
| 709 |
+
|
| 710 |
+
if self._is_streaming:
|
| 711 |
+
self._streaming_state['offsets'] = offsets + T
|
| 712 |
+
|
| 713 |
+
return x
|
| 714 |
+
|
| 715 |
+
def make_optim_group(self):
|
| 716 |
+
group = {"params": list(self.parameters())}
|
| 717 |
+
if self.lr is not None:
|
| 718 |
+
group["lr"] = self.lr
|
| 719 |
+
if self.weight_decay is not None:
|
| 720 |
+
group["weight_decay"] = self.weight_decay
|
| 721 |
+
return group
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
# special attention related function
|
| 725 |
+
|
| 726 |
+
def _verify_xformers_memory_efficient_compat():
|
| 727 |
+
try:
|
| 728 |
+
from xformers.ops import memory_efficient_attention, LowerTriangularMask # noqa
|
| 729 |
+
except ImportError:
|
| 730 |
+
raise ImportError(
|
| 731 |
+
"xformers is not installed. Please install it and try again.\n"
|
| 732 |
+
"To install on AWS and Azure, run \n"
|
| 733 |
+
"FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
|
| 734 |
+
"pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
|
| 735 |
+
"To install on FAIR Cluster, run \n"
|
| 736 |
+
"FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
|
| 737 |
+
"pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
|
| 738 |
+
|
| 739 |
+
|
| 740 |
+
def _verify_xformers_internal_compat():
|
| 741 |
+
try:
|
| 742 |
+
from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy # noqa
|
| 743 |
+
except ImportError:
|
| 744 |
+
raise ImportError(
|
| 745 |
+
"Francisco's fairinternal xformers is not installed. Please install it and try again.\n"
|
| 746 |
+
"To install on AWS and Azure, run \n"
|
| 747 |
+
"FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
|
| 748 |
+
"pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
|
| 749 |
+
"To install on FAIR Cluster, run \n"
|
| 750 |
+
"FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
|
| 751 |
+
"pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
|
| 752 |
+
|
| 753 |
+
|
| 754 |
+
def _is_custom(custom: bool, memory_efficient: bool):
|
| 755 |
+
return custom or memory_efficient
|
src/audiocraft/py.typed
ADDED
|
File without changes
|
src/audiocraft/quantization/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""RVQ."""
|
| 7 |
+
# flake8: noqa
|
| 8 |
+
from .vq import ResidualVectorQuantizer
|
| 9 |
+
from .base import BaseQuantizer, DummyQuantizer, QuantizedResult
|
src/audiocraft/quantization/base.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
"""
|
| 8 |
+
Base class for all quantizers.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from dataclasses import dataclass, field
|
| 12 |
+
import typing as tp
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
from torch import nn
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class QuantizedResult:
|
| 20 |
+
x: torch.Tensor
|
| 21 |
+
codes: torch.Tensor
|
| 22 |
+
bandwidth: torch.Tensor # bandwidth in kb/s used, per batch item.
|
| 23 |
+
penalty: tp.Optional[torch.Tensor] = None
|
| 24 |
+
metrics: dict = field(default_factory=dict)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class BaseQuantizer(nn.Module):
|
| 28 |
+
"""Base class for quantizers.
|
| 29 |
+
"""
|
| 30 |
+
|
| 31 |
+
def forward(self, x: torch.Tensor, frame_rate: int) -> QuantizedResult:
|
| 32 |
+
"""
|
| 33 |
+
Given input tensor x, returns first the quantized (or approximately quantized)
|
| 34 |
+
representation along with quantized codes, bandwidth, and any penalty term for the loss.
|
| 35 |
+
Finally, this returns a dict of metrics to update logging etc.
|
| 36 |
+
Frame rate must be passed so that the bandwidth is properly computed.
|
| 37 |
+
"""
|
| 38 |
+
raise NotImplementedError()
|
| 39 |
+
|
| 40 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 41 |
+
"""Encode a given input tensor with the specified sample rate at the given bandwidth."""
|
| 42 |
+
raise NotImplementedError()
|
| 43 |
+
|
| 44 |
+
def decode(self, codes: torch.Tensor) -> torch.Tensor:
|
| 45 |
+
"""Decode the given codes to the quantized representation."""
|
| 46 |
+
raise NotImplementedError()
|
| 47 |
+
|
| 48 |
+
@property
|
| 49 |
+
def total_codebooks(self):
|
| 50 |
+
"""Total number of codebooks."""
|
| 51 |
+
raise NotImplementedError()
|
| 52 |
+
|
| 53 |
+
@property
|
| 54 |
+
def num_codebooks(self):
|
| 55 |
+
"""Number of active codebooks."""
|
| 56 |
+
raise NotImplementedError()
|
| 57 |
+
|
| 58 |
+
def set_num_codebooks(self, n: int):
|
| 59 |
+
"""Set the number of active codebooks."""
|
| 60 |
+
raise NotImplementedError()
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class DummyQuantizer(BaseQuantizer):
|
| 64 |
+
"""Fake quantizer that actually does not perform any quantization.
|
| 65 |
+
"""
|
| 66 |
+
def __init__(self):
|
| 67 |
+
super().__init__()
|
| 68 |
+
|
| 69 |
+
def forward(self, x: torch.Tensor, frame_rate: int):
|
| 70 |
+
q = x.unsqueeze(1)
|
| 71 |
+
return QuantizedResult(x, q, torch.tensor(q.numel() * 32 * frame_rate / 1000 / len(x)).to(x))
|
| 72 |
+
|
| 73 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 74 |
+
"""Encode a given input tensor with the specified sample rate at the given bandwidth.
|
| 75 |
+
In the case of the DummyQuantizer, the codes are actually identical
|
| 76 |
+
to the input and resulting quantized representation as no quantization is done.
|
| 77 |
+
"""
|
| 78 |
+
return x.unsqueeze(1)
|
| 79 |
+
|
| 80 |
+
def decode(self, codes: torch.Tensor) -> torch.Tensor:
|
| 81 |
+
"""Decode the given codes to the quantized representation.
|
| 82 |
+
In the case of the DummyQuantizer, the codes are actually identical
|
| 83 |
+
to the input and resulting quantized representation as no quantization is done.
|
| 84 |
+
"""
|
| 85 |
+
return codes.squeeze(1)
|
| 86 |
+
|
| 87 |
+
@property
|
| 88 |
+
def total_codebooks(self):
|
| 89 |
+
"""Total number of codebooks."""
|
| 90 |
+
return 1
|
| 91 |
+
|
| 92 |
+
@property
|
| 93 |
+
def num_codebooks(self):
|
| 94 |
+
"""Total number of codebooks."""
|
| 95 |
+
return self.total_codebooks
|
| 96 |
+
|
| 97 |
+
def set_num_codebooks(self, n: int):
|
| 98 |
+
"""Set the number of active codebooks."""
|
| 99 |
+
raise AttributeError("Cannot override the number of codebooks for the dummy quantizer")
|
src/audiocraft/quantization/core_vq.py
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import typing as tp
|
| 8 |
+
|
| 9 |
+
from einops import rearrange, repeat
|
| 10 |
+
import flashy
|
| 11 |
+
import torch
|
| 12 |
+
from torch import nn, einsum
|
| 13 |
+
import torch.nn.functional as F
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def exists(val: tp.Optional[tp.Any]) -> bool:
|
| 17 |
+
return val is not None
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def default(val: tp.Any, d: tp.Any) -> tp.Any:
|
| 21 |
+
return val if exists(val) else d
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def l2norm(t):
|
| 25 |
+
return F.normalize(t, p=2, dim=-1)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def ema_inplace(moving_avg, new, decay: float):
|
| 29 |
+
moving_avg.data.mul_(decay).add_(new, alpha=(1 - decay))
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def laplace_smoothing(x, n_categories: int, epsilon: float = 1e-5):
|
| 33 |
+
return (x + epsilon) / (x.sum() + n_categories * epsilon)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def uniform_init(*shape: int):
|
| 37 |
+
t = torch.empty(shape)
|
| 38 |
+
nn.init.kaiming_uniform_(t)
|
| 39 |
+
return t
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def sample_vectors(samples, num: int):
|
| 43 |
+
num_samples, device = samples.shape[0], samples.device
|
| 44 |
+
|
| 45 |
+
if num_samples >= num:
|
| 46 |
+
indices = torch.randperm(num_samples, device=device)[:num]
|
| 47 |
+
else:
|
| 48 |
+
indices = torch.randint(0, num_samples, (num,), device=device)
|
| 49 |
+
|
| 50 |
+
return samples[indices]
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def kmeans(samples, num_clusters: int, num_iters: int = 10):
|
| 54 |
+
dim, dtype = samples.shape[-1], samples.dtype
|
| 55 |
+
|
| 56 |
+
means = sample_vectors(samples, num_clusters)
|
| 57 |
+
|
| 58 |
+
for _ in range(num_iters):
|
| 59 |
+
diffs = rearrange(samples, "n d -> n () d") - rearrange(
|
| 60 |
+
means, "c d -> () c d"
|
| 61 |
+
)
|
| 62 |
+
dists = -(diffs ** 2).sum(dim=-1)
|
| 63 |
+
|
| 64 |
+
buckets = dists.max(dim=-1).indices
|
| 65 |
+
bins = torch.bincount(buckets, minlength=num_clusters)
|
| 66 |
+
zero_mask = bins == 0
|
| 67 |
+
bins_min_clamped = bins.masked_fill(zero_mask, 1)
|
| 68 |
+
|
| 69 |
+
new_means = buckets.new_zeros(num_clusters, dim, dtype=dtype)
|
| 70 |
+
new_means.scatter_add_(0, repeat(buckets, "n -> n d", d=dim), samples)
|
| 71 |
+
new_means = new_means / bins_min_clamped[..., None]
|
| 72 |
+
|
| 73 |
+
means = torch.where(zero_mask[..., None], means, new_means)
|
| 74 |
+
|
| 75 |
+
return means, bins
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def orthogonal_loss_fn(t):
|
| 79 |
+
# eq (2) from https://arxiv.org/abs/2112.00384
|
| 80 |
+
n = t.shape[0]
|
| 81 |
+
normed_codes = l2norm(t)
|
| 82 |
+
identity = torch.eye(n, device=t.device)
|
| 83 |
+
cosine_sim = einsum("i d, j d -> i j", normed_codes, normed_codes)
|
| 84 |
+
return ((cosine_sim - identity) ** 2).sum() / (n ** 2)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
class EuclideanCodebook(nn.Module):
|
| 88 |
+
"""Codebook with Euclidean distance.
|
| 89 |
+
|
| 90 |
+
Args:
|
| 91 |
+
dim (int): Dimension.
|
| 92 |
+
codebook_size (int): Codebook size.
|
| 93 |
+
kmeans_init (bool): Whether to use k-means to initialize the codebooks.
|
| 94 |
+
If set to true, run the k-means algorithm on the first training batch and use
|
| 95 |
+
the learned centroids as initialization.
|
| 96 |
+
kmeans_iters (int): Number of iterations used for k-means algorithm at initialization.
|
| 97 |
+
decay (float): Decay for exponential moving average over the codebooks.
|
| 98 |
+
epsilon (float): Epsilon value for numerical stability.
|
| 99 |
+
threshold_ema_dead_code (int): Threshold for dead code expiration. Replace any codes
|
| 100 |
+
that have an exponential moving average cluster size less than the specified threshold with
|
| 101 |
+
randomly selected vector from the current batch.
|
| 102 |
+
"""
|
| 103 |
+
def __init__(
|
| 104 |
+
self,
|
| 105 |
+
dim: int,
|
| 106 |
+
codebook_size: int,
|
| 107 |
+
kmeans_init: int = False,
|
| 108 |
+
kmeans_iters: int = 10,
|
| 109 |
+
decay: float = 0.8,
|
| 110 |
+
epsilon: float = 1e-5,
|
| 111 |
+
threshold_ema_dead_code: int = 2,
|
| 112 |
+
):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.decay = decay
|
| 115 |
+
init_fn: tp.Union[tp.Callable[..., torch.Tensor], tp.Any] = uniform_init if not kmeans_init else torch.zeros
|
| 116 |
+
embed = init_fn(codebook_size, dim)
|
| 117 |
+
|
| 118 |
+
self.codebook_size = codebook_size
|
| 119 |
+
|
| 120 |
+
self.kmeans_iters = kmeans_iters
|
| 121 |
+
self.epsilon = epsilon
|
| 122 |
+
self.threshold_ema_dead_code = threshold_ema_dead_code
|
| 123 |
+
|
| 124 |
+
self.register_buffer("inited", torch.Tensor([not kmeans_init]))
|
| 125 |
+
self.register_buffer("cluster_size", torch.zeros(codebook_size))
|
| 126 |
+
self.register_buffer("embed", embed)
|
| 127 |
+
self.register_buffer("embed_avg", embed.clone())
|
| 128 |
+
|
| 129 |
+
@torch.jit.ignore
|
| 130 |
+
def init_embed_(self, data):
|
| 131 |
+
if self.inited:
|
| 132 |
+
return
|
| 133 |
+
|
| 134 |
+
embed, cluster_size = kmeans(data, self.codebook_size, self.kmeans_iters)
|
| 135 |
+
self.embed.data.copy_(embed)
|
| 136 |
+
self.embed_avg.data.copy_(embed.clone())
|
| 137 |
+
self.cluster_size.data.copy_(cluster_size)
|
| 138 |
+
self.inited.data.copy_(torch.Tensor([True]))
|
| 139 |
+
# Make sure all buffers across workers are in sync after initialization
|
| 140 |
+
flashy.distrib.broadcast_tensors(self.buffers())
|
| 141 |
+
|
| 142 |
+
def replace_(self, samples, mask):
|
| 143 |
+
modified_codebook = torch.where(
|
| 144 |
+
mask[..., None], sample_vectors(samples, self.codebook_size), self.embed
|
| 145 |
+
)
|
| 146 |
+
self.embed.data.copy_(modified_codebook)
|
| 147 |
+
|
| 148 |
+
def expire_codes_(self, batch_samples):
|
| 149 |
+
if self.threshold_ema_dead_code == 0:
|
| 150 |
+
return
|
| 151 |
+
|
| 152 |
+
expired_codes = self.cluster_size < self.threshold_ema_dead_code
|
| 153 |
+
if not torch.any(expired_codes):
|
| 154 |
+
return
|
| 155 |
+
|
| 156 |
+
batch_samples = rearrange(batch_samples, "... d -> (...) d")
|
| 157 |
+
self.replace_(batch_samples, mask=expired_codes)
|
| 158 |
+
flashy.distrib.broadcast_tensors(self.buffers())
|
| 159 |
+
|
| 160 |
+
def preprocess(self, x):
|
| 161 |
+
x = rearrange(x, "... d -> (...) d")
|
| 162 |
+
return x
|
| 163 |
+
|
| 164 |
+
def quantize(self, x):
|
| 165 |
+
embed = self.embed.t()
|
| 166 |
+
dist = -(
|
| 167 |
+
x.pow(2).sum(1, keepdim=True)
|
| 168 |
+
- 2 * x @ embed
|
| 169 |
+
+ embed.pow(2).sum(0, keepdim=True)
|
| 170 |
+
)
|
| 171 |
+
embed_ind = dist.max(dim=-1).indices
|
| 172 |
+
return embed_ind
|
| 173 |
+
|
| 174 |
+
def postprocess_emb(self, embed_ind, shape):
|
| 175 |
+
return embed_ind.view(*shape[:-1])
|
| 176 |
+
|
| 177 |
+
def dequantize(self, embed_ind):
|
| 178 |
+
quantize = F.embedding(embed_ind, self.embed)
|
| 179 |
+
return quantize
|
| 180 |
+
|
| 181 |
+
def encode(self, x):
|
| 182 |
+
shape = x.shape
|
| 183 |
+
# pre-process
|
| 184 |
+
x = self.preprocess(x)
|
| 185 |
+
# quantize
|
| 186 |
+
embed_ind = self.quantize(x)
|
| 187 |
+
# post-process
|
| 188 |
+
embed_ind = self.postprocess_emb(embed_ind, shape)
|
| 189 |
+
return embed_ind
|
| 190 |
+
|
| 191 |
+
def decode(self, embed_ind):
|
| 192 |
+
quantize = self.dequantize(embed_ind)
|
| 193 |
+
return quantize
|
| 194 |
+
|
| 195 |
+
def forward(self, x):
|
| 196 |
+
shape, dtype = x.shape, x.dtype
|
| 197 |
+
x = self.preprocess(x)
|
| 198 |
+
self.init_embed_(x)
|
| 199 |
+
|
| 200 |
+
embed_ind = self.quantize(x)
|
| 201 |
+
embed_onehot = F.one_hot(embed_ind, self.codebook_size).type(dtype)
|
| 202 |
+
embed_ind = self.postprocess_emb(embed_ind, shape)
|
| 203 |
+
quantize = self.dequantize(embed_ind)
|
| 204 |
+
|
| 205 |
+
if self.training:
|
| 206 |
+
# We do the expiry of code at that point as buffers are in sync
|
| 207 |
+
# and all the workers will take the same decision.
|
| 208 |
+
self.expire_codes_(x)
|
| 209 |
+
ema_inplace(self.cluster_size, embed_onehot.sum(0), self.decay)
|
| 210 |
+
embed_sum = x.t() @ embed_onehot
|
| 211 |
+
ema_inplace(self.embed_avg, embed_sum.t(), self.decay)
|
| 212 |
+
cluster_size = (
|
| 213 |
+
laplace_smoothing(self.cluster_size, self.codebook_size, self.epsilon)
|
| 214 |
+
* self.cluster_size.sum()
|
| 215 |
+
)
|
| 216 |
+
embed_normalized = self.embed_avg / cluster_size.unsqueeze(1)
|
| 217 |
+
self.embed.data.copy_(embed_normalized)
|
| 218 |
+
|
| 219 |
+
return quantize, embed_ind
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
class VectorQuantization(nn.Module):
|
| 223 |
+
"""Vector quantization implementation.
|
| 224 |
+
Currently supports only euclidean distance.
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
dim (int): Dimension
|
| 228 |
+
codebook_size (int): Codebook size
|
| 229 |
+
codebook_dim (int): Codebook dimension. If not defined, uses the specified dimension in dim.
|
| 230 |
+
decay (float): Decay for exponential moving average over the codebooks.
|
| 231 |
+
epsilon (float): Epsilon value for numerical stability.
|
| 232 |
+
kmeans_init (bool): Whether to use kmeans to initialize the codebooks.
|
| 233 |
+
kmeans_iters (int): Number of iterations used for kmeans initialization.
|
| 234 |
+
threshold_ema_dead_code (int):
|
| 235 |
+
channels_last (bool): Channels are the last dimension in the input tensors.
|
| 236 |
+
commitment_weight (float): Weight for commitment loss.
|
| 237 |
+
orthogonal_reg_weight (float): Orthogonal regularization weights.
|
| 238 |
+
orthogonal_reg_active_codes_only (bool): Apply orthogonal regularization only on active codes.
|
| 239 |
+
orthogonal_reg_max_codes (optional int): Maximum number of codes to consider
|
| 240 |
+
for orthogonal regularization.
|
| 241 |
+
threshold_ema_dead_code (int): Threshold for dead code expiration. Replace any codes
|
| 242 |
+
that have an exponential moving average cluster size less than the specified threshold with
|
| 243 |
+
randomly selected vector from the current batch.
|
| 244 |
+
"""
|
| 245 |
+
def __init__(
|
| 246 |
+
self,
|
| 247 |
+
dim: int,
|
| 248 |
+
codebook_size: int,
|
| 249 |
+
codebook_dim: tp.Optional[int] = None,
|
| 250 |
+
decay: float = 0.8,
|
| 251 |
+
epsilon: float = 1e-5,
|
| 252 |
+
kmeans_init: bool = False,
|
| 253 |
+
kmeans_iters: int = 10,
|
| 254 |
+
threshold_ema_dead_code: int = 2,
|
| 255 |
+
channels_last: bool = False,
|
| 256 |
+
commitment_weight: float = 1.,
|
| 257 |
+
orthogonal_reg_weight: float = 0.0,
|
| 258 |
+
orthogonal_reg_active_codes_only: bool = False,
|
| 259 |
+
orthogonal_reg_max_codes: tp.Optional[int] = None,
|
| 260 |
+
):
|
| 261 |
+
super().__init__()
|
| 262 |
+
_codebook_dim: int = default(codebook_dim, dim)
|
| 263 |
+
|
| 264 |
+
requires_projection = _codebook_dim != dim
|
| 265 |
+
self.project_in = (nn.Linear(dim, _codebook_dim) if requires_projection else nn.Identity())
|
| 266 |
+
self.project_out = (nn.Linear(_codebook_dim, dim) if requires_projection else nn.Identity())
|
| 267 |
+
|
| 268 |
+
self.epsilon = epsilon
|
| 269 |
+
self.commitment_weight = commitment_weight
|
| 270 |
+
|
| 271 |
+
self.orthogonal_reg_weight = orthogonal_reg_weight
|
| 272 |
+
self.orthogonal_reg_active_codes_only = orthogonal_reg_active_codes_only
|
| 273 |
+
self.orthogonal_reg_max_codes = orthogonal_reg_max_codes
|
| 274 |
+
|
| 275 |
+
self._codebook = EuclideanCodebook(dim=_codebook_dim, codebook_size=codebook_size,
|
| 276 |
+
kmeans_init=kmeans_init, kmeans_iters=kmeans_iters,
|
| 277 |
+
decay=decay, epsilon=epsilon,
|
| 278 |
+
threshold_ema_dead_code=threshold_ema_dead_code)
|
| 279 |
+
self.codebook_size = codebook_size
|
| 280 |
+
|
| 281 |
+
self.channels_last = channels_last
|
| 282 |
+
|
| 283 |
+
@property
|
| 284 |
+
def codebook(self):
|
| 285 |
+
return self._codebook.embed
|
| 286 |
+
|
| 287 |
+
@property
|
| 288 |
+
def inited(self):
|
| 289 |
+
return self._codebook.inited
|
| 290 |
+
|
| 291 |
+
def _preprocess(self, x):
|
| 292 |
+
if not self.channels_last:
|
| 293 |
+
x = rearrange(x, "b d n -> b n d")
|
| 294 |
+
return x
|
| 295 |
+
|
| 296 |
+
def _postprocess(self, quantize):
|
| 297 |
+
if not self.channels_last:
|
| 298 |
+
quantize = rearrange(quantize, "b n d -> b d n")
|
| 299 |
+
return quantize
|
| 300 |
+
|
| 301 |
+
def encode(self, x):
|
| 302 |
+
x = self._preprocess(x)
|
| 303 |
+
x = self.project_in(x)
|
| 304 |
+
embed_in = self._codebook.encode(x)
|
| 305 |
+
return embed_in
|
| 306 |
+
|
| 307 |
+
def decode(self, embed_ind):
|
| 308 |
+
quantize = self._codebook.decode(embed_ind)
|
| 309 |
+
quantize = self.project_out(quantize)
|
| 310 |
+
quantize = self._postprocess(quantize)
|
| 311 |
+
return quantize
|
| 312 |
+
|
| 313 |
+
def forward(self, x):
|
| 314 |
+
device = x.device
|
| 315 |
+
x = self._preprocess(x)
|
| 316 |
+
|
| 317 |
+
x = self.project_in(x)
|
| 318 |
+
quantize, embed_ind = self._codebook(x)
|
| 319 |
+
|
| 320 |
+
if self.training:
|
| 321 |
+
quantize = x + (quantize - x).detach()
|
| 322 |
+
|
| 323 |
+
loss = torch.tensor([0.0], device=device, requires_grad=self.training)
|
| 324 |
+
|
| 325 |
+
if self.training:
|
| 326 |
+
if self.commitment_weight > 0:
|
| 327 |
+
commit_loss = F.mse_loss(quantize.detach(), x)
|
| 328 |
+
loss = loss + commit_loss * self.commitment_weight
|
| 329 |
+
|
| 330 |
+
if self.orthogonal_reg_weight > 0:
|
| 331 |
+
codebook = self.codebook
|
| 332 |
+
|
| 333 |
+
if self.orthogonal_reg_active_codes_only:
|
| 334 |
+
# only calculate orthogonal loss for the activated codes for this batch
|
| 335 |
+
unique_code_ids = torch.unique(embed_ind)
|
| 336 |
+
codebook = codebook[unique_code_ids]
|
| 337 |
+
|
| 338 |
+
num_codes = codebook.shape[0]
|
| 339 |
+
if exists(self.orthogonal_reg_max_codes) and num_codes > self.orthogonal_reg_max_codes:
|
| 340 |
+
rand_ids = torch.randperm(num_codes, device=device)[:self.orthogonal_reg_max_codes]
|
| 341 |
+
codebook = codebook[rand_ids]
|
| 342 |
+
|
| 343 |
+
orthogonal_reg_loss = orthogonal_loss_fn(codebook)
|
| 344 |
+
loss = loss + orthogonal_reg_loss * self.orthogonal_reg_weight
|
| 345 |
+
|
| 346 |
+
quantize = self.project_out(quantize)
|
| 347 |
+
quantize = self._postprocess(quantize)
|
| 348 |
+
|
| 349 |
+
return quantize, embed_ind, loss
|
| 350 |
+
|
| 351 |
+
|
| 352 |
+
class ResidualVectorQuantization(nn.Module):
|
| 353 |
+
"""Residual vector quantization implementation.
|
| 354 |
+
|
| 355 |
+
Follows Algorithm 1. in https://arxiv.org/pdf/2107.03312.pdf
|
| 356 |
+
"""
|
| 357 |
+
def __init__(self, *, num_quantizers, **kwargs):
|
| 358 |
+
super().__init__()
|
| 359 |
+
self.layers = nn.ModuleList(
|
| 360 |
+
[VectorQuantization(**kwargs) for _ in range(num_quantizers)]
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
def forward(self, x, n_q: tp.Optional[int] = None):
|
| 364 |
+
quantized_out = 0.0
|
| 365 |
+
residual = x
|
| 366 |
+
|
| 367 |
+
all_losses = []
|
| 368 |
+
all_indices = []
|
| 369 |
+
|
| 370 |
+
n_q = n_q or len(self.layers)
|
| 371 |
+
|
| 372 |
+
for i, layer in enumerate(self.layers[:n_q]):
|
| 373 |
+
quantized, indices, loss = layer(residual)
|
| 374 |
+
quantized = quantized.detach()
|
| 375 |
+
residual = residual - quantized
|
| 376 |
+
quantized_out = quantized_out + quantized
|
| 377 |
+
all_indices.append(indices)
|
| 378 |
+
all_losses.append(loss)
|
| 379 |
+
|
| 380 |
+
if self.training:
|
| 381 |
+
# Solving subtle bug with STE and RVQ: https://github.com/facebookresearch/encodec/issues/25
|
| 382 |
+
quantized_out = x + (quantized_out - x).detach()
|
| 383 |
+
|
| 384 |
+
out_losses, out_indices = map(torch.stack, (all_losses, all_indices))
|
| 385 |
+
return quantized_out, out_indices, out_losses
|
| 386 |
+
|
| 387 |
+
def encode(self, x: torch.Tensor, n_q: tp.Optional[int] = None) -> torch.Tensor:
|
| 388 |
+
residual = x
|
| 389 |
+
all_indices = []
|
| 390 |
+
n_q = n_q or len(self.layers)
|
| 391 |
+
for layer in self.layers[:n_q]:
|
| 392 |
+
indices = layer.encode(residual)
|
| 393 |
+
quantized = layer.decode(indices)
|
| 394 |
+
residual = residual - quantized
|
| 395 |
+
all_indices.append(indices)
|
| 396 |
+
out_indices = torch.stack(all_indices)
|
| 397 |
+
return out_indices
|
| 398 |
+
|
| 399 |
+
def decode(self, q_indices: torch.Tensor) -> torch.Tensor:
|
| 400 |
+
quantized_out = torch.tensor(0.0, device=q_indices.device)
|
| 401 |
+
for i, indices in enumerate(q_indices):
|
| 402 |
+
layer = self.layers[i]
|
| 403 |
+
quantized = layer.decode(indices)
|
| 404 |
+
quantized_out = quantized_out + quantized
|
| 405 |
+
return quantized_out
|
src/audiocraft/quantization/vq.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
|
| 7 |
+
import math
|
| 8 |
+
import typing as tp
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from .base import BaseQuantizer, QuantizedResult
|
| 13 |
+
from .core_vq import ResidualVectorQuantization
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class ResidualVectorQuantizer(BaseQuantizer):
|
| 17 |
+
"""Residual Vector Quantizer.
|
| 18 |
+
|
| 19 |
+
Args:
|
| 20 |
+
dimension (int): Dimension of the codebooks.
|
| 21 |
+
n_q (int): Number of residual vector quantizers used.
|
| 22 |
+
q_dropout (bool): Random quantizer drop out at train time.
|
| 23 |
+
bins (int): Codebook size.
|
| 24 |
+
decay (float): Decay for exponential moving average over the codebooks.
|
| 25 |
+
kmeans_init (bool): Whether to use kmeans to initialize the codebooks.
|
| 26 |
+
kmeans_iters (int): Number of iterations used for kmeans initialization.
|
| 27 |
+
threshold_ema_dead_code (int): Threshold for dead code expiration. Replace any codes
|
| 28 |
+
that have an exponential moving average cluster size less than the specified threshold with
|
| 29 |
+
randomly selected vector from the current batch.
|
| 30 |
+
orthogonal_reg_weight (float): Orthogonal regularization weights.
|
| 31 |
+
orthogonal_reg_active_codes_only (bool): Apply orthogonal regularization only on active codes.
|
| 32 |
+
orthogonal_reg_max_codes (optional int): Maximum number of codes to consider.
|
| 33 |
+
for orthogonal regularization.
|
| 34 |
+
"""
|
| 35 |
+
def __init__(
|
| 36 |
+
self,
|
| 37 |
+
dimension: int = 256,
|
| 38 |
+
n_q: int = 8,
|
| 39 |
+
q_dropout: bool = False,
|
| 40 |
+
bins: int = 1024,
|
| 41 |
+
decay: float = 0.99,
|
| 42 |
+
kmeans_init: bool = True,
|
| 43 |
+
kmeans_iters: int = 10,
|
| 44 |
+
threshold_ema_dead_code: int = 2,
|
| 45 |
+
orthogonal_reg_weight: float = 0.0,
|
| 46 |
+
orthogonal_reg_active_codes_only: bool = False,
|
| 47 |
+
orthogonal_reg_max_codes: tp.Optional[int] = None,
|
| 48 |
+
):
|
| 49 |
+
super().__init__()
|
| 50 |
+
self.max_n_q = n_q
|
| 51 |
+
self.n_q = n_q
|
| 52 |
+
self.q_dropout = q_dropout
|
| 53 |
+
self.dimension = dimension
|
| 54 |
+
self.bins = bins
|
| 55 |
+
self.decay = decay
|
| 56 |
+
self.kmeans_init = kmeans_init
|
| 57 |
+
self.kmeans_iters = kmeans_iters
|
| 58 |
+
self.threshold_ema_dead_code = threshold_ema_dead_code
|
| 59 |
+
self.orthogonal_reg_weight = orthogonal_reg_weight
|
| 60 |
+
self.orthogonal_reg_active_codes_only = orthogonal_reg_active_codes_only
|
| 61 |
+
self.orthogonal_reg_max_codes = orthogonal_reg_max_codes
|
| 62 |
+
self.vq = ResidualVectorQuantization(
|
| 63 |
+
dim=self.dimension,
|
| 64 |
+
codebook_size=self.bins,
|
| 65 |
+
num_quantizers=self.n_q,
|
| 66 |
+
decay=self.decay,
|
| 67 |
+
kmeans_init=self.kmeans_init,
|
| 68 |
+
kmeans_iters=self.kmeans_iters,
|
| 69 |
+
threshold_ema_dead_code=self.threshold_ema_dead_code,
|
| 70 |
+
orthogonal_reg_weight=self.orthogonal_reg_weight,
|
| 71 |
+
orthogonal_reg_active_codes_only=self.orthogonal_reg_active_codes_only,
|
| 72 |
+
orthogonal_reg_max_codes=self.orthogonal_reg_max_codes,
|
| 73 |
+
channels_last=False
|
| 74 |
+
)
|
| 75 |
+
|
| 76 |
+
def forward(self, x: torch.Tensor, frame_rate: int):
|
| 77 |
+
n_q = self.n_q
|
| 78 |
+
if self.training and self.q_dropout:
|
| 79 |
+
n_q = int(torch.randint(1, self.n_q + 1, (1,)).item())
|
| 80 |
+
bw_per_q = math.log2(self.bins) * frame_rate / 1000
|
| 81 |
+
quantized, codes, commit_loss = self.vq(x, n_q=n_q)
|
| 82 |
+
codes = codes.transpose(0, 1)
|
| 83 |
+
# codes is [B, K, T], with T frames, K nb of codebooks.
|
| 84 |
+
bw = torch.tensor(n_q * bw_per_q).to(x)
|
| 85 |
+
return QuantizedResult(quantized, codes, bw, penalty=torch.mean(commit_loss))
|
| 86 |
+
|
| 87 |
+
def encode(self, x: torch.Tensor) -> torch.Tensor:
|
| 88 |
+
"""Encode a given input tensor with the specified frame rate at the given bandwidth.
|
| 89 |
+
The RVQ encode method sets the appropriate number of quantizer to use
|
| 90 |
+
and returns indices for each quantizer.
|
| 91 |
+
"""
|
| 92 |
+
n_q = self.n_q
|
| 93 |
+
codes = self.vq.encode(x, n_q=n_q)
|
| 94 |
+
codes = codes.transpose(0, 1)
|
| 95 |
+
# codes is [B, K, T], with T frames, K nb of codebooks.
|
| 96 |
+
return codes
|
| 97 |
+
|
| 98 |
+
def decode(self, codes: torch.Tensor) -> torch.Tensor:
|
| 99 |
+
"""Decode the given codes to the quantized representation."""
|
| 100 |
+
# codes is [B, K, T], with T frames, K nb of codebooks, vq.decode expects [K, B, T].
|
| 101 |
+
codes = codes.transpose(0, 1)
|
| 102 |
+
quantized = self.vq.decode(codes)
|
| 103 |
+
return quantized
|
| 104 |
+
|
| 105 |
+
@property
|
| 106 |
+
def total_codebooks(self):
|
| 107 |
+
return self.max_n_q
|
| 108 |
+
|
| 109 |
+
@property
|
| 110 |
+
def num_codebooks(self):
|
| 111 |
+
return self.n_q
|
| 112 |
+
|
| 113 |
+
def set_num_codebooks(self, n: int):
|
| 114 |
+
assert n > 0 and n <= self.max_n_q
|
| 115 |
+
self.n_q = n
|
src/audiocraft/utils/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
#
|
| 4 |
+
# This source code is licensed under the license found in the
|
| 5 |
+
# LICENSE file in the root directory of this source tree.
|
| 6 |
+
"""Utilities."""
|