Vansh Chugh commited on
Commit
83e59db
·
1 Parent(s): 0b9c252

initial deploy

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +4 -0
  2. README.md +8 -7
  3. SOURCES.md +4 -0
  4. app.py +257 -0
  5. beat_this/__init__.py +0 -0
  6. beat_this/inference.py +315 -0
  7. beat_this/model/__init__.py +0 -0
  8. beat_this/model/beat_tracker.py +346 -0
  9. beat_this/model/postprocessor.py +197 -0
  10. beat_this/model/roformer.py +181 -0
  11. beat_this/preprocessing.py +59 -0
  12. beat_this/utils.py +111 -0
  13. model.json +18 -0
  14. requirements.txt +22 -0
  15. src/__init__.py +0 -0
  16. src/audiocraft/__init__.py +26 -0
  17. src/audiocraft/data/__init__.py +12 -0
  18. src/audiocraft/data/audio.py +231 -0
  19. src/audiocraft/data/audio_dataset.py +587 -0
  20. src/audiocraft/data/audio_utils.py +176 -0
  21. src/audiocraft/data/zip.py +76 -0
  22. src/audiocraft/environment.py +176 -0
  23. src/audiocraft/models/__init__.py +19 -0
  24. src/audiocraft/models/builders.py +261 -0
  25. src/audiocraft/models/encodec.py +506 -0
  26. src/audiocraft/models/genmodel.py +267 -0
  27. src/audiocraft/models/lm.py +547 -0
  28. src/audiocraft/models/lm_magnet.py +498 -0
  29. src/audiocraft/models/loaders.py +178 -0
  30. src/audiocraft/models/magnet.py +88 -0
  31. src/audiocraft/models/musicgen.py +308 -0
  32. src/audiocraft/models/unet.py +214 -0
  33. src/audiocraft/modules/__init__.py +22 -0
  34. src/audiocraft/modules/activations.py +96 -0
  35. src/audiocraft/modules/chroma.py +66 -0
  36. src/audiocraft/modules/codebooks_patterns.py +548 -0
  37. src/audiocraft/modules/conditioners.py +1416 -0
  38. src/audiocraft/modules/conv.py +243 -0
  39. src/audiocraft/modules/diffusion_schedule.py +272 -0
  40. src/audiocraft/modules/lstm.py +25 -0
  41. src/audiocraft/modules/rope.py +125 -0
  42. src/audiocraft/modules/seanet.py +258 -0
  43. src/audiocraft/modules/streaming.py +131 -0
  44. src/audiocraft/modules/transformer.py +755 -0
  45. src/audiocraft/py.typed +0 -0
  46. src/audiocraft/quantization/__init__.py +9 -0
  47. src/audiocraft/quantization/base.py +99 -0
  48. src/audiocraft/quantization/core_vq.py +405 -0
  49. src/audiocraft/quantization/vq.py +115 -0
  50. 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: Loopgen
3
- emoji: 🏃
4
- colorFrom: red
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
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."""