Vansh Chugh commited on
Commit
fa3196f
·
1 Parent(s): 6b214a6

initial deploy

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
4
+ .venv/
5
+ MusiConGen-repo/
README.md CHANGED
@@ -1,15 +1,15 @@
1
  ---
2
  title: MusiConGen
3
- emoji: 🔥
4
  colorFrom: gray
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
  license: mit
12
- short_description: TODO
13
  ---
14
 
15
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: MusiConGen
3
+ emoji: 🎸
4
  colorFrom: gray
5
  colorTo: indigo
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
+ short_description: Text-to-music generation with chord and rhythm control
13
  ---
14
 
15
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+
3
+ sys.stdout.reconfigure(line_buffering=True)
4
+
5
+ try:
6
+ import spaces
7
+ except ImportError:
8
+ # keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
9
+ class spaces:
10
+ class GPU:
11
+ def __init__(self, func=None, duration=60):
12
+ self.func = func
13
+
14
+ def __call__(self, *args, **kwargs):
15
+ if self.func is not None:
16
+ return self.func(*args, **kwargs)
17
+ func = args[0]
18
+ return func
19
+
20
+ import threading
21
+
22
+ import audiotools
23
+ import gradio as gr
24
+ import torch
25
+ from pyharp import ModelCard, build_endpoint, save_audio
26
+
27
+ from audiocraft.models import MusicGen
28
+ from audiocraft.utils.autocast import TorchAutocast
29
+
30
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
31
+ CHECKPOINT_REPO = "Cyan0731/MusiConGen"
32
+
33
+ model = None
34
+ model_ready = False # has model been moved onto the GPU yet?
35
+ model_loading = True
36
+ model_error = None
37
+
38
+
39
+ def load_model():
40
+ """Download + construct on CPU only. ZeroGPU only intercepts CUDA calls
41
+ made inside @spaces.GPU decorated call."""
42
+ global model, model_loading, model_error
43
+ try:
44
+ model = MusicGen.get_pretrained(CHECKPOINT_REPO, device="cpu")
45
+ print("Model loaded (CPU).")
46
+ except Exception as e:
47
+ model_error = str(e)
48
+ print(f"Load error: {e}")
49
+ finally:
50
+ model_loading = False
51
+
52
+
53
+ threading.Thread(target=load_model, daemon=True).start()
54
+
55
+
56
+ model_card = ModelCard(
57
+ name="MusiConGen",
58
+ description=(
59
+ "Text-to-music generation with rhythm and chord control. Generates a "
60
+ "music clip from a text description, a chord progression, and a "
61
+ "tempo/time signature."
62
+ ),
63
+ author="Yun-Han Lan, Wen-Yi Hsiao, Hao-Chung Cheng, Yi-Hsuan Yang",
64
+ tags=["music generation", "text-to-music"],
65
+ )
66
+
67
+
68
+ @spaces.GPU(duration=120)
69
+ @torch.inference_mode()
70
+ def process_fn(description, chords, bpm, meter, duration, conditioning_strength):
71
+ """Generate a music clip conditioned on text, chords, and rhythm."""
72
+ global model, model_ready
73
+ if model_loading:
74
+ raise gr.Error("Model is still loading, please wait a moment and try again.")
75
+ if model is None:
76
+ raise gr.Error(f"Model failed to load: {model_error}")
77
+ if not model_ready:
78
+ model.lm.to(DEVICE)
79
+ model.compression_model.to(DEVICE)
80
+ model.device = torch.device(DEVICE) # only safe here, inside @spaces.GPU
81
+ model.autocast = TorchAutocast(enabled=(DEVICE == "cuda"), device_type=DEVICE, dtype=torch.float16)
82
+ model_ready = True
83
+
84
+ model.set_generation_params(
85
+ duration=duration,
86
+ extend_stride=duration / 2,
87
+ cfg_coef=conditioning_strength,
88
+ )
89
+ wav = model.generate_with_chords_and_beats([description], [chords], [bpm], [meter])
90
+
91
+ signal = audiotools.AudioSignal(wav[0].cpu(), sample_rate=model.sample_rate)
92
+ return save_audio(signal)
93
+
94
+
95
+ with gr.Blocks() as demo:
96
+ input_components = [
97
+ gr.Textbox(
98
+ label="Description",
99
+ value="A laid-back blues shuffle with a relaxed tempo, warm guitar tones, and a comfortable groove. Instruments: electric guitar, bass, drums.",
100
+ info="Text description of the music to generate.",
101
+ ),
102
+ gr.Textbox(
103
+ label="Chord Progression",
104
+ value="C G A:min F",
105
+ info="Space-separated chord symbols, one per bar, repeating to fill the duration (e.g. 'C G A:min F'). Syntax: root note plus optional ':quality', e.g. C, A:min, D:min7.",
106
+ ),
107
+ gr.Number(
108
+ label="Tempo (BPM)", value=120, minimum=40, maximum=240,
109
+ info="Tempo in beats per minute (default: 120, per repo demo script).",
110
+ ),
111
+ gr.Number(
112
+ label="Time Signature (beats per bar)", value=4, minimum=2, maximum=12,
113
+ info="Numerator of the time signature (default: 4, per repo demo script).",
114
+ ),
115
+ gr.Slider(
116
+ minimum=5, maximum=30, step=1, value=30, label="Duration (seconds)",
117
+ info="Length of the generated clip (default/max: 30s, the model's trained segment length, per repo config segment_duration=30).",
118
+ ),
119
+ gr.Slider(
120
+ minimum=0.0, maximum=10.0, step=0.5, value=3.0, label="Conditioning Strength",
121
+ info="How strongly generation follows the description/chords/rhythm vs. sounding more free (default: 3.0, per paper guidance scale γ).",
122
+ ),
123
+ ]
124
+ output_components = [
125
+ gr.Audio(type="filepath", label="Generated Music").set_info("Generated music, 32kHz."),
126
+ ]
127
+
128
+ build_endpoint(
129
+ model_card=model_card,
130
+ input_components=input_components,
131
+ output_components=output_components,
132
+ process_fn=process_fn,
133
+ )
134
+
135
+ demo.queue().launch(pwa=True)
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.0.0'
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 info_audio_dataset, music_dataset, sound_dataset, and btc_chords
11
+ # (training data loaders + unused chord parser)
12
+ from . import audio, audio_dataset
audiocraft/data/audio.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import torchaudio as ta
22
+
23
+ import av
24
+
25
+ from .audio_utils import f32_pcm, i16_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
+ elif (
140
+ fp.suffix in ['.wav', '.mp3'] and fp.suffix[1:] in ta.utils.sox_utils.list_read_formats()
141
+ and duration <= 0 and seek_time == 0
142
+ ):
143
+ # Torchaudio is faster if we load an entire file at once.
144
+ wav, sr = ta.load(fp)
145
+ else:
146
+ wav, sr = _av_read(filepath, seek_time, duration)
147
+ if pad and duration > 0:
148
+ expected_frames = int(duration * sr)
149
+ wav = F.pad(wav, (0, expected_frames - wav.shape[-1]))
150
+ return wav, sr
151
+
152
+
153
+ def audio_write(stem_name: tp.Union[str, Path],
154
+ wav: torch.Tensor, sample_rate: int,
155
+ format: str = 'wav', mp3_rate: int = 320, normalize: bool = True,
156
+ strategy: str = 'peak', peak_clip_headroom_db: float = 1,
157
+ rms_headroom_db: float = 18, loudness_headroom_db: float = 14,
158
+ loudness_compressor: bool = False,
159
+ log_clipping: bool = True, make_parent_dir: bool = True,
160
+ add_suffix: bool = True) -> Path:
161
+ """Convenience function for saving audio to disk. Returns the filename the audio was written to.
162
+
163
+ Args:
164
+ stem_name (str or Path): Filename without extension which will be added automatically.
165
+ format (str): Either "wav" or "mp3".
166
+ mp3_rate (int): kbps when using mp3s.
167
+ normalize (bool): if `True` (default), normalizes according to the prescribed
168
+ strategy (see after). If `False`, the strategy is only used in case clipping
169
+ would happen.
170
+ strategy (str): Can be either 'clip', 'peak', or 'rms'. Default is 'peak',
171
+ i.e. audio is normalized by its largest value. RMS normalizes by root-mean-square
172
+ with extra headroom to avoid clipping. 'clip' just clips.
173
+ peak_clip_headroom_db (float): Headroom in dB when doing 'peak' or 'clip' strategy.
174
+ rms_headroom_db (float): Headroom in dB when doing 'rms' strategy. This must be much larger
175
+ than the `peak_clip` one to avoid further clipping.
176
+ loudness_headroom_db (float): Target loudness for loudness normalization.
177
+ loudness_compressor (bool): Uses tanh for soft clipping when strategy is 'loudness'.
178
+ when strategy is 'loudness' log_clipping (bool): If True, basic logging on stderr when clipping still
179
+ occurs despite strategy (only for 'rms').
180
+ make_parent_dir (bool): Make parent directory if it doesn't exist.
181
+ Returns:
182
+ Path: Path of the saved audio.
183
+ """
184
+ assert wav.dtype.is_floating_point, "wav is not floating point"
185
+ if wav.dim() == 1:
186
+ wav = wav[None]
187
+ elif wav.dim() > 2:
188
+ raise ValueError("Input wav should be at most 2 dimension.")
189
+ assert wav.isfinite().all()
190
+ wav = normalize_audio(wav, normalize, strategy, peak_clip_headroom_db,
191
+ rms_headroom_db, loudness_headroom_db, loudness_compressor,
192
+ log_clipping=log_clipping, sample_rate=sample_rate,
193
+ stem_name=str(stem_name))
194
+ kwargs: dict = {}
195
+ if format == 'mp3':
196
+ suffix = '.mp3'
197
+ kwargs.update({"compression": mp3_rate})
198
+ elif format == 'wav':
199
+ wav = i16_pcm(wav)
200
+ suffix = '.wav'
201
+ kwargs.update({"encoding": "PCM_S", "bits_per_sample": 16})
202
+ else:
203
+ raise RuntimeError(f"Invalid format {format}. Only wav or mp3 are supported.")
204
+ if not add_suffix:
205
+ suffix = ''
206
+ path = Path(str(stem_name) + suffix)
207
+ if make_parent_dir:
208
+ path.parent.mkdir(exist_ok=True, parents=True)
209
+ try:
210
+ ta.save(path, wav, sample_rate, **kwargs)
211
+ except Exception:
212
+ if path.exists():
213
+ # we do not want to leave half written files around.
214
+ path.unlink()
215
+ raise
216
+ return path
217
+
218
+ def audio_postproc(wav: torch.Tensor, sample_rate: int, normalize: bool = True,
219
+ strategy: str = 'peak', peak_clip_headroom_db: float = 1,
220
+ rms_headroom_db: float = 18, loudness_headroom_db: float = 14,
221
+ loudness_compressor: bool = False, log_clipping: bool = True) -> Path:
222
+ """Convenience function for saving audio to disk. Returns the filename the audio was written to.
223
+
224
+ Args:
225
+ wav (torch.Tensor): Audio data to save.
226
+ sample_rate (int): Sample rate of audio data.
227
+ format (str): Either "wav" or "mp3".
228
+ mp3_rate (int): kbps when using mp3s.
229
+ normalize (bool): if `True` (default), normalizes according to the prescribed
230
+ strategy (see after). If `False`, the strategy is only used in case clipping
231
+ would happen.
232
+ strategy (str): Can be either 'clip', 'peak', or 'rms'. Default is 'peak',
233
+ i.e. audio is normalized by its largest value. RMS normalizes by root-mean-square
234
+ with extra headroom to avoid clipping. 'clip' just clips.
235
+ peak_clip_headroom_db (float): Headroom in dB when doing 'peak' or 'clip' strategy.
236
+ rms_headroom_db (float): Headroom in dB when doing 'rms' strategy. This must be much larger
237
+ than the `peak_clip` one to avoid further clipping.
238
+ loudness_headroom_db (float): Target loudness for loudness normalization.
239
+ loudness_compressor (bool): Uses tanh for soft clipping when strategy is 'loudness'.
240
+ when strategy is 'loudness' log_clipping (bool): If True, basic logging on stderr when clipping still
241
+ occurs despite strategy (only for 'rms').
242
+ make_parent_dir (bool): Make parent directory if it doesn't exist.
243
+ Returns:
244
+ Path: Path of the saved audio.
245
+ """
246
+ assert wav.dtype.is_floating_point, "wav is not floating point"
247
+ if wav.dim() == 1:
248
+ wav = wav[None]
249
+ elif wav.dim() > 2:
250
+ raise ValueError("Input wav should be at most 2 dimension.")
251
+ assert wav.isfinite().all()
252
+ wav = normalize_audio(wav, normalize, strategy, peak_clip_headroom_db,
253
+ rms_headroom_db, loudness_headroom_db, loudness_compressor,
254
+ log_clipping=log_clipping, sample_rate=sample_rate,
255
+ stem_name=None)
256
+
257
+ return wav
audiocraft/data/audio_dataset.py ADDED
@@ -0,0 +1,614 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ bpm: float
66
+ # meter: int
67
+ amplitude: tp.Optional[float] = None
68
+ weight: tp.Optional[float] = None
69
+ phr_start: tp.List[tp.Optional[float]] = None
70
+ # info_path is used to load additional information about the audio file that is stored in zip files.
71
+ info_path: tp.Optional[PathInZip] = None
72
+
73
+ @classmethod
74
+ def from_dict(cls, dictionary: dict):
75
+ base = cls._dict2fields(dictionary)
76
+ if 'info_path' in base and base['info_path'] is not None:
77
+ base['info_path'] = PathInZip(base['info_path'])
78
+ return cls(**base)
79
+
80
+ def to_dict(self):
81
+ d = super().to_dict()
82
+ if d['info_path'] is not None:
83
+ d['info_path'] = str(d['info_path'])
84
+ return d
85
+
86
+
87
+ @dataclass(order=True)
88
+ class SegmentInfo(BaseInfo):
89
+ meta: AudioMeta
90
+ seek_time: float
91
+ # The following values are given once the audio is processed, e.g.
92
+ # at the target sample rate and target number of channels.
93
+ n_frames: int # actual number of frames without padding
94
+ total_frames: int # total number of frames, padding included
95
+ sample_rate: int # actual sample rate
96
+ channels: int # number of audio channels.
97
+
98
+
99
+ DEFAULT_EXTS = ['.wav', '.mp3', '.flac', '.ogg', '.m4a']
100
+
101
+ logger = logging.getLogger(__name__)
102
+
103
+
104
+ def _get_audio_meta(file_path: str, minimal: bool = True) -> AudioMeta:
105
+ """AudioMeta from a path to an audio file.
106
+
107
+ Args:
108
+ file_path (str): Resolved path of valid audio file.
109
+ minimal (bool): Whether to only load the minimal set of metadata (takes longer if not).
110
+ Returns:
111
+ AudioMeta: Audio file path and its metadata.
112
+ """
113
+ info = audio_info(file_path)
114
+ amplitude: tp.Optional[float] = None
115
+ if not minimal:
116
+ wav, sr = audio_read(file_path)
117
+ amplitude = wav.abs().max().item()
118
+
119
+ # load json info
120
+ json_file = file_path.replace('.wav', '.json')
121
+ with open(json_file ,'r') as f:
122
+ json_str = f.read()
123
+ info_json = json.loads(json_str)
124
+
125
+ if "phr_start" not in info_json.keys():
126
+ info_json["phr_start"] = None
127
+
128
+ # return AudioMeta(file_path, info.duration, info.sample_rate, info_json["bpm"], info_json["meter"], amplitude, None, info_json["phr_start"])
129
+ return AudioMeta(file_path, info.duration, info.sample_rate, info_json["bpm"], amplitude, None, info_json["phr_start"])
130
+
131
+ def _resolve_audio_meta(m: AudioMeta, fast: bool = True) -> AudioMeta:
132
+ """If Dora is available as a dependency, try to resolve potential relative paths
133
+ in list of AudioMeta. This method is expected to be used when loading meta from file.
134
+
135
+ Args:
136
+ m (AudioMeta): Audio meta to resolve.
137
+ fast (bool): If True, uses a really fast check for determining if a file
138
+ is already absolute or not. Only valid on Linux/Mac.
139
+ Returns:
140
+ AudioMeta: Audio meta with resolved path.
141
+ """
142
+ def is_abs(m):
143
+ if fast:
144
+ return str(m)[0] == '/'
145
+ else:
146
+ os.path.isabs(str(m))
147
+
148
+ if not dora:
149
+ return m
150
+
151
+ if not is_abs(m.path):
152
+ m.path = dora.git_save.to_absolute_path(m.path)
153
+ if m.info_path is not None and not is_abs(m.info_path.zip_path):
154
+ m.info_path.zip_path = dora.git_save.to_absolute_path(m.path)
155
+ return m
156
+
157
+
158
+ def find_audio_files(path: tp.Union[Path, str],
159
+ exts: tp.List[str] = DEFAULT_EXTS,
160
+ resolve: bool = True,
161
+ minimal: bool = True,
162
+ progress: bool = False,
163
+ workers: int = 0) -> tp.List[AudioMeta]:
164
+ """Build a list of AudioMeta from a given path,
165
+ collecting relevant audio files and fetching meta info.
166
+
167
+ Args:
168
+ path (str or Path): Path to folder containing audio files.
169
+ exts (list of str): List of file extensions to consider for audio files.
170
+ minimal (bool): Whether to only load the minimal set of metadata (takes longer if not).
171
+ progress (bool): Whether to log progress on audio files collection.
172
+ workers (int): number of parallel workers, if 0, use only the current thread.
173
+ Returns:
174
+ list of AudioMeta: List of audio file path and its metadata.
175
+ """
176
+ audio_files = []
177
+ futures: tp.List[Future] = []
178
+ pool: tp.Optional[ThreadPoolExecutor] = None
179
+ with ExitStack() as stack:
180
+ if workers > 0:
181
+ pool = ThreadPoolExecutor(workers)
182
+ stack.enter_context(pool)
183
+
184
+ if progress:
185
+ print("Finding audio files...")
186
+ for root, folders, files in os.walk(path, followlinks=True):
187
+ for file in files:
188
+ full_path = Path(root) / file
189
+ if full_path.suffix.lower() in exts:
190
+ audio_files.append(full_path)
191
+ if pool is not None:
192
+ futures.append(pool.submit(_get_audio_meta, str(audio_files[-1]), minimal))
193
+ if progress:
194
+ print(format(len(audio_files), " 8d"), end='\r', file=sys.stderr)
195
+
196
+ if progress:
197
+ print("Getting audio metadata...")
198
+ meta: tp.List[AudioMeta] = []
199
+ for idx, file_path in enumerate(audio_files):
200
+ try:
201
+ if pool is None:
202
+ m = _get_audio_meta(str(file_path), minimal)
203
+ else:
204
+ m = futures[idx].result()
205
+ if resolve:
206
+ m = _resolve_audio_meta(m)
207
+ except Exception as err:
208
+ print("Error with", str(file_path), err, file=sys.stderr)
209
+ continue
210
+ meta.append(m)
211
+ if progress:
212
+ print(format((1 + idx) / len(audio_files), " 3.1%"), end='\r', file=sys.stderr)
213
+ meta.sort()
214
+ return meta
215
+
216
+
217
+ def load_audio_meta(path: tp.Union[str, Path],
218
+ resolve: bool = True, fast: bool = True) -> tp.List[AudioMeta]:
219
+ """Load list of AudioMeta from an optionally compressed json file.
220
+
221
+ Args:
222
+ path (str or Path): Path to JSON file.
223
+ resolve (bool): Whether to resolve the path from AudioMeta (default=True).
224
+ fast (bool): activates some tricks to make things faster.
225
+ Returns:
226
+ list of AudioMeta: List of audio file path and its total duration.
227
+ """
228
+ open_fn = gzip.open if str(path).lower().endswith('.gz') else open
229
+ with open_fn(path, 'rb') as fp: # type: ignore
230
+ lines = fp.readlines()
231
+ meta = []
232
+ for line in lines:
233
+ d = json.loads(line)
234
+ m = AudioMeta.from_dict(d)
235
+ if resolve:
236
+ m = _resolve_audio_meta(m, fast=fast)
237
+ meta.append(m)
238
+ return meta
239
+
240
+
241
+ def save_audio_meta(path: tp.Union[str, Path], meta: tp.List[AudioMeta]):
242
+ """Save the audio metadata to the file pointer as json.
243
+
244
+ Args:
245
+ path (str or Path): Path to JSON file.
246
+ metadata (list of BaseAudioMeta): List of audio meta to save.
247
+ """
248
+ Path(path).parent.mkdir(exist_ok=True, parents=True)
249
+ open_fn = gzip.open if str(path).lower().endswith('.gz') else open
250
+ with open_fn(path, 'wb') as fp: # type: ignore
251
+ for m in meta:
252
+ json_str = json.dumps(m.to_dict()) + '\n'
253
+ json_bytes = json_str.encode('utf-8')
254
+ fp.write(json_bytes)
255
+
256
+
257
+ class AudioDataset:
258
+ """Base audio dataset.
259
+
260
+ The dataset takes a list of AudioMeta and create a dataset composed of segments of audio
261
+ and potentially additional information, by creating random segments from the list of audio
262
+ files referenced in the metadata and applying minimal data pre-processing such as resampling,
263
+ mixing of channels, padding, etc.
264
+
265
+ If no segment_duration value is provided, the AudioDataset will return the full wav for each
266
+ audio file. Otherwise, it will randomly sample audio files and create a segment of the specified
267
+ duration, applying padding if required.
268
+
269
+ By default, only the torch Tensor corresponding to the waveform is returned. Setting return_info=True
270
+ allows to return a tuple containing the torch Tensor and additional metadata on the segment and the
271
+ original audio meta.
272
+
273
+ Note that you can call `start_epoch(epoch)` in order to get
274
+ a deterministic "randomization" for `shuffle=True`.
275
+ For a given epoch and dataset index, this will always return the same extract.
276
+ You can get back some diversity by setting the `shuffle_seed` param.
277
+
278
+ Args:
279
+ meta (list of AudioMeta): List of audio files metadata.
280
+ segment_duration (float, optional): Optional segment duration of audio to load.
281
+ If not specified, the dataset will load the full audio segment from the file.
282
+ shuffle (bool): Set to `True` to have the data reshuffled at every epoch.
283
+ sample_rate (int): Target sample rate of the loaded audio samples.
284
+ channels (int): Target number of channels of the loaded audio samples.
285
+ sample_on_duration (bool): Set to `True` to sample segments with probability
286
+ dependent on audio file duration. This is only used if `segment_duration` is provided.
287
+ sample_on_weight (bool): Set to `True` to sample segments using the `weight` entry of
288
+ `AudioMeta`. If `sample_on_duration` is also True, the actual weight will be the product
289
+ of the file duration and file weight. This is only used if `segment_duration` is provided.
290
+ min_segment_ratio (float): Minimum segment ratio to use when the audio file
291
+ is shorter than the desired segment.
292
+ max_read_retry (int): Maximum number of retries to sample an audio segment from the dataset.
293
+ return_info (bool): Whether to return the wav only or return wav along with segment info and metadata.
294
+ min_audio_duration (float, optional): Minimum audio file duration, in seconds, if provided
295
+ audio shorter than this will be filtered out.
296
+ max_audio_duration (float, optional): Maximal audio file duration in seconds, if provided
297
+ audio longer than this will be filtered out.
298
+ shuffle_seed (int): can be used to further randomize
299
+ load_wav (bool): if False, skip loading the wav but returns a tensor of 0
300
+ with the expected segment_duration (which must be provided if load_wav is False).
301
+ permutation_on_files (bool): only if `sample_on_weight` and `sample_on_duration`
302
+ are False. Will ensure a permutation on files when going through the dataset.
303
+ In that case the epoch number must be provided in order for the model
304
+ to continue the permutation across epochs. In that case, it is assumed
305
+ that `num_samples = total_batch_size * num_updates_per_epoch`, with
306
+ `total_batch_size` the overall batch size accounting for all gpus.
307
+ """
308
+ def __init__(self,
309
+ meta: tp.List[AudioMeta],
310
+ segment_duration: tp.Optional[float] = None,
311
+ shuffle: bool = True,
312
+ num_samples: int = 10_000,
313
+ sample_rate: int = 48_000,
314
+ channels: int = 2,
315
+ pad: bool = True,
316
+ sample_on_duration: bool = True,
317
+ sample_on_weight: bool = True,
318
+ min_segment_ratio: float = 1,
319
+ max_read_retry: int = 10,
320
+ return_info: bool = False,
321
+ min_audio_duration: tp.Optional[float] = None,
322
+ max_audio_duration: tp.Optional[float] = None,
323
+ shuffle_seed: int = 0,
324
+ load_wav: bool = True,
325
+ permutation_on_files: bool = False,
326
+ ):
327
+ assert len(meta) > 0, "No audio meta provided to AudioDataset. Please check loading of audio meta."
328
+ assert segment_duration is None or segment_duration > 0
329
+ assert segment_duration is None or min_segment_ratio >= 0
330
+ self.segment_duration = segment_duration
331
+ self.min_segment_ratio = min_segment_ratio
332
+ self.max_audio_duration = max_audio_duration
333
+ self.min_audio_duration = min_audio_duration
334
+ if self.min_audio_duration is not None and self.max_audio_duration is not None:
335
+ assert self.min_audio_duration <= self.max_audio_duration
336
+ self.meta: tp.List[AudioMeta] = self._filter_duration(meta)
337
+ assert len(self.meta) # Fail fast if all data has been filtered.
338
+ self.total_duration = sum(d.duration for d in self.meta)
339
+
340
+ if segment_duration is None:
341
+ num_samples = len(self.meta)
342
+ self.num_samples = num_samples
343
+ self.shuffle = shuffle
344
+ self.sample_rate = sample_rate
345
+ self.channels = channels
346
+ self.pad = pad
347
+ self.sample_on_weight = sample_on_weight
348
+ self.sample_on_duration = sample_on_duration
349
+ self.sampling_probabilities = self._get_sampling_probabilities()
350
+ self.max_read_retry = max_read_retry
351
+ self.return_info = return_info
352
+ self.shuffle_seed = shuffle_seed
353
+ self.current_epoch: tp.Optional[int] = None
354
+ self.load_wav = load_wav
355
+ if not load_wav:
356
+ assert segment_duration is not None
357
+ self.permutation_on_files = permutation_on_files
358
+ if permutation_on_files:
359
+ assert not self.sample_on_duration
360
+ assert not self.sample_on_weight
361
+ assert self.shuffle
362
+
363
+ def start_epoch(self, epoch: int):
364
+ self.current_epoch = epoch
365
+
366
+ def __len__(self):
367
+ return self.num_samples
368
+
369
+ def _get_sampling_probabilities(self, normalized: bool = True):
370
+ """Return the sampling probabilities for each file inside `self.meta`."""
371
+ scores: tp.List[float] = []
372
+ for file_meta in self.meta:
373
+ score = 1.
374
+ if self.sample_on_weight and file_meta.weight is not None:
375
+ score *= file_meta.weight
376
+ if self.sample_on_duration:
377
+ score *= file_meta.duration
378
+ scores.append(score)
379
+ probabilities = torch.tensor(scores)
380
+ if normalized:
381
+ probabilities /= probabilities.sum()
382
+ return probabilities
383
+
384
+ @staticmethod
385
+ @lru_cache(16)
386
+ def _get_file_permutation(num_files: int, permutation_index: int, base_seed: int):
387
+ # Used to keep the most recent files permutation in memory implicitely.
388
+ # will work unless someone is using a lot of Datasets in parallel.
389
+ rng = torch.Generator()
390
+ rng.manual_seed(base_seed + permutation_index)
391
+ return torch.randperm(num_files, generator=rng)
392
+
393
+ def sample_file(self, index: int, rng: torch.Generator) -> AudioMeta:
394
+ """Sample a given file from `self.meta`. Can be overridden in subclasses.
395
+ This is only called if `segment_duration` is not None.
396
+
397
+ You must use the provided random number generator `rng` for reproducibility.
398
+ You can further make use of the index accessed.
399
+ """
400
+ if self.permutation_on_files:
401
+ assert self.current_epoch is not None
402
+ total_index = self.current_epoch * len(self) + index
403
+ permutation_index = total_index // len(self.meta)
404
+ relative_index = total_index % len(self.meta)
405
+ permutation = AudioDataset._get_file_permutation(
406
+ len(self.meta), permutation_index, self.shuffle_seed)
407
+ file_index = permutation[relative_index]
408
+ return self.meta[file_index]
409
+
410
+ if not self.sample_on_weight and not self.sample_on_duration:
411
+ file_index = int(torch.randint(len(self.sampling_probabilities), (1,), generator=rng).item())
412
+ else:
413
+ file_index = int(torch.multinomial(self.sampling_probabilities, 1, generator=rng).item())
414
+
415
+ return self.meta[file_index]
416
+
417
+ def _audio_read(self, path: str, seek_time: float = 0, duration: float = -1):
418
+ # Override this method in subclass if needed.
419
+ if self.load_wav:
420
+ return audio_read(path, seek_time, duration, pad=False)
421
+ else:
422
+ assert self.segment_duration is not None
423
+ n_frames = int(self.sample_rate * self.segment_duration)
424
+ return torch.zeros(self.channels, n_frames), self.sample_rate
425
+
426
+ def __getitem__(self, index: int) -> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, SegmentInfo]]:
427
+ if self.segment_duration is None:
428
+ file_meta = self.meta[index]
429
+ out, sr = audio_read(file_meta.path)
430
+ out = convert_audio(out, sr, self.sample_rate, self.channels)
431
+ n_frames = out.shape[-1]
432
+ segment_info = SegmentInfo(file_meta, seek_time=0., n_frames=n_frames, total_frames=n_frames,
433
+ sample_rate=self.sample_rate, channels=out.shape[0])
434
+ else:
435
+ rng = torch.Generator()
436
+ if self.shuffle:
437
+ # We use index, plus extra randomness, either totally random if we don't know the epoch.
438
+ # otherwise we make use of the epoch number and optional shuffle_seed.
439
+ if self.current_epoch is None:
440
+ rng.manual_seed(index + self.num_samples * random.randint(0, 2**24))
441
+ else:
442
+ rng.manual_seed(index + self.num_samples * (self.current_epoch + self.shuffle_seed))
443
+ else:
444
+ # We only use index
445
+ rng.manual_seed(index)
446
+
447
+ for retry in range(self.max_read_retry):
448
+ file_meta = self.sample_file(index, rng)
449
+ # We add some variance in the file position even if audio file is smaller than segment
450
+ # without ending up with empty segments
451
+
452
+ # sample with phrase
453
+ if file_meta.phr_start is not None:
454
+ # max_seek = max(0, len(file_meta.phr_start[:-1]))
455
+ max_seek = max(0, len([start for start in file_meta.phr_start if start + self.segment_duration <= file_meta.duration])) # sample with time
456
+ seek_time = file_meta.phr_start[int(torch.rand(1, generator=rng).item() * max_seek)] # choose from phrase
457
+
458
+ else:
459
+ max_seek = max(0, file_meta.duration - self.segment_duration * self.min_segment_ratio)
460
+ seek_time = torch.rand(1, generator=rng).item() * max_seek # can be change to choose phrase start
461
+
462
+ if file_meta.duration == self.segment_duration:
463
+ seek_time = 0
464
+
465
+ # phr_dur = 60./file_meta.bpm * (file_meta.meter * 4.) # if meter=4 then 16 beats per phrase
466
+ try:
467
+ out, sr = audio_read(file_meta.path, seek_time, self.segment_duration, pad=False)
468
+ # out, sr = audio_read(file_meta.path, seek_time, phr_dur, pad=False) # use phrase trunk as input
469
+ out = convert_audio(out, sr, self.sample_rate, self.channels)
470
+ n_frames = out.shape[-1]
471
+ target_frames = int(self.segment_duration * self.sample_rate)
472
+ if self.pad:
473
+ out = F.pad(out, (0, target_frames - n_frames))
474
+ segment_info = SegmentInfo(file_meta, seek_time, n_frames=n_frames, total_frames=target_frames,
475
+ sample_rate=self.sample_rate, channels=out.shape[0])
476
+ except Exception as exc:
477
+ logger.warning("Error opening file %s: %r", file_meta.path, exc)
478
+ if retry == self.max_read_retry - 1:
479
+ raise
480
+ else:
481
+ break
482
+
483
+ if self.return_info:
484
+ # Returns the wav and additional information on the wave segment
485
+ return out, segment_info
486
+ else:
487
+ return out
488
+
489
+ def collater(self, samples):
490
+ """The collater function has to be provided to the dataloader
491
+ if AudioDataset has return_info=True in order to properly collate
492
+ the samples of a batch.
493
+ """
494
+ if self.segment_duration is None and len(samples) > 1:
495
+ assert self.pad, "Must allow padding when batching examples of different durations."
496
+
497
+ # In this case the audio reaching the collater is of variable length as segment_duration=None.
498
+ to_pad = self.segment_duration is None and self.pad
499
+ if to_pad:
500
+ max_len = max([wav.shape[-1] for wav, _ in samples])
501
+
502
+ def _pad_wav(wav):
503
+ return F.pad(wav, (0, max_len - wav.shape[-1]))
504
+
505
+ if self.return_info:
506
+ if len(samples) > 0:
507
+ assert len(samples[0]) == 2
508
+ assert isinstance(samples[0][0], torch.Tensor)
509
+ assert isinstance(samples[0][1], SegmentInfo)
510
+
511
+ wavs = [wav for wav, _ in samples]
512
+ segment_infos = [copy.deepcopy(info) for _, info in samples]
513
+
514
+ if to_pad:
515
+ # Each wav could be of a different duration as they are not segmented.
516
+ for i in range(len(samples)):
517
+ # Determines the total length of the signal with padding, so we update here as we pad.
518
+ segment_infos[i].total_frames = max_len
519
+ wavs[i] = _pad_wav(wavs[i])
520
+
521
+ wav = torch.stack(wavs)
522
+ return wav, segment_infos
523
+ else:
524
+ assert isinstance(samples[0], torch.Tensor)
525
+ if to_pad:
526
+ samples = [_pad_wav(s) for s in samples]
527
+ return torch.stack(samples)
528
+
529
+ def _filter_duration(self, meta: tp.List[AudioMeta]) -> tp.List[AudioMeta]:
530
+ """Filters out audio files with audio durations that will not allow to sample examples from them."""
531
+ orig_len = len(meta)
532
+
533
+ # Filter data that is too short.
534
+ if self.min_audio_duration is not None:
535
+ meta = [m for m in meta if m.duration >= self.min_audio_duration]
536
+
537
+ # Filter data that is too long.
538
+ if self.max_audio_duration is not None:
539
+ meta = [m for m in meta if m.duration <= self.max_audio_duration]
540
+
541
+ filtered_len = len(meta)
542
+ removed_percentage = 100*(1-float(filtered_len)/orig_len)
543
+ msg = 'Removed %.2f percent of the data because it was too short or too long.' % removed_percentage
544
+ if removed_percentage < 10:
545
+ logging.debug(msg)
546
+ else:
547
+ logging.warning(msg)
548
+ return meta
549
+
550
+ @classmethod
551
+ def from_meta(cls, root: tp.Union[str, Path], **kwargs):
552
+ """Instantiate AudioDataset from a path to a directory containing a manifest as a jsonl file.
553
+
554
+ Args:
555
+ root (str or Path): Path to root folder containing audio files.
556
+ kwargs: Additional keyword arguments for the AudioDataset.
557
+ """
558
+ root = Path(root)
559
+ if root.is_dir():
560
+ if (root / 'data.jsonl').exists():
561
+ root = root / 'data.jsonl'
562
+ elif (root / 'data.jsonl.gz').exists():
563
+ root = root / 'data.jsonl.gz'
564
+ else:
565
+ raise ValueError("Don't know where to read metadata from in the dir. "
566
+ "Expecting either a data.jsonl or data.jsonl.gz file but none found.")
567
+ meta = load_audio_meta(root)
568
+ return cls(meta, **kwargs)
569
+
570
+ @classmethod
571
+ def from_path(cls, root: tp.Union[str, Path], minimal_meta: bool = True,
572
+ exts: tp.List[str] = DEFAULT_EXTS, **kwargs):
573
+ """Instantiate AudioDataset from a path containing (possibly nested) audio files.
574
+
575
+ Args:
576
+ root (str or Path): Path to root folder containing audio files.
577
+ minimal_meta (bool): Whether to only load minimal metadata or not.
578
+ exts (list of str): Extensions for audio files.
579
+ kwargs: Additional keyword arguments for the AudioDataset.
580
+ """
581
+ root = Path(root)
582
+ if root.is_file():
583
+ meta = load_audio_meta(root, resolve=True)
584
+ else:
585
+ meta = find_audio_files(root, exts, minimal=minimal_meta, resolve=True)
586
+ return cls(meta, **kwargs)
587
+
588
+
589
+ def main():
590
+ logging.basicConfig(stream=sys.stderr, level=logging.INFO)
591
+ parser = argparse.ArgumentParser(
592
+ prog='audio_dataset',
593
+ description='Generate .jsonl files by scanning a folder.')
594
+ parser.add_argument('root', help='Root folder with all the audio files')
595
+ parser.add_argument('output_meta_file',
596
+ help='Output file to store the metadata, ')
597
+ parser.add_argument('--complete',
598
+ action='store_false', dest='minimal', default=True,
599
+ help='Retrieve all metadata, even the one that are expansive '
600
+ 'to compute (e.g. normalization).')
601
+ parser.add_argument('--resolve',
602
+ action='store_true', default=False,
603
+ help='Resolve the paths to be absolute and with no symlinks.')
604
+ parser.add_argument('--workers',
605
+ default=10, type=int,
606
+ help='Number of workers.')
607
+ args = parser.parse_args()
608
+ meta = find_audio_files(args.root, DEFAULT_EXTS, progress=True,
609
+ resolve=args.resolve, minimal=args.minimal, workers=args.workers)
610
+ save_audio_meta(args.output_meta_file, meta)
611
+
612
+
613
+ if __name__ == '__main__':
614
+ main()
audiocraft/data/audio_utils.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import numpy as np
15
+
16
+ from .chords import Chords
17
+ chords = Chords() # initiate object
18
+
19
+
20
+ def convert_audio_channels(wav: torch.Tensor, channels: int = 2) -> torch.Tensor:
21
+ """Convert audio to the given number of channels.
22
+
23
+ Args:
24
+ wav (torch.Tensor): Audio wave of shape [B, C, T].
25
+ channels (int): Expected number of channels as output.
26
+ Returns:
27
+ torch.Tensor: Downmixed or unchanged audio wave [B, C, T].
28
+ """
29
+ *shape, src_channels, length = wav.shape
30
+ if src_channels == channels:
31
+ pass
32
+ elif channels == 1:
33
+ # Case 1:
34
+ # The caller asked 1-channel audio, and the stream has multiple
35
+ # channels, downmix all channels.
36
+ wav = wav.mean(dim=-2, keepdim=True)
37
+ elif src_channels == 1:
38
+ # Case 2:
39
+ # The caller asked for multiple channels, but the input file has
40
+ # a single channel, replicate the audio over all channels.
41
+ wav = wav.expand(*shape, channels, length)
42
+ elif src_channels >= channels:
43
+ # Case 3:
44
+ # The caller asked for multiple channels, and the input file has
45
+ # more channels than requested. In that case return the first channels.
46
+ wav = wav[..., :channels, :]
47
+ else:
48
+ # Case 4: What is a reasonable choice here?
49
+ raise ValueError('The audio file has less channels than requested but is not mono.')
50
+ return wav
51
+
52
+
53
+ def convert_audio(wav: torch.Tensor, from_rate: float,
54
+ to_rate: float, to_channels: int) -> torch.Tensor:
55
+ """Convert audio to new sample rate and number of audio channels."""
56
+ wav = julius.resample_frac(wav, int(from_rate), int(to_rate))
57
+ wav = convert_audio_channels(wav, to_channels)
58
+ return wav
59
+
60
+
61
+ def normalize_loudness(wav: torch.Tensor, sample_rate: int, loudness_headroom_db: float = 14,
62
+ loudness_compressor: bool = False, energy_floor: float = 2e-3):
63
+ """Normalize an input signal to a user loudness in dB LKFS.
64
+ Audio loudness is defined according to the ITU-R BS.1770-4 recommendation.
65
+
66
+ Args:
67
+ wav (torch.Tensor): Input multichannel audio data.
68
+ sample_rate (int): Sample rate.
69
+ loudness_headroom_db (float): Target loudness of the output in dB LUFS.
70
+ loudness_compressor (bool): Uses tanh for soft clipping.
71
+ energy_floor (float): anything below that RMS level will not be rescaled.
72
+ Returns:
73
+ torch.Tensor: Loudness normalized output data.
74
+ """
75
+ energy = wav.pow(2).mean().sqrt().item()
76
+ if energy < energy_floor:
77
+ return wav
78
+ transform = torchaudio.transforms.Loudness(sample_rate)
79
+ input_loudness_db = transform(wav).item()
80
+ # calculate the gain needed to scale to the desired loudness level
81
+ delta_loudness = -loudness_headroom_db - input_loudness_db
82
+ gain = 10.0 ** (delta_loudness / 20.0)
83
+ output = gain * wav
84
+ if loudness_compressor:
85
+ output = torch.tanh(output)
86
+ assert output.isfinite().all(), (input_loudness_db, wav.pow(2).mean().sqrt())
87
+ return output
88
+
89
+
90
+ def _clip_wav(wav: torch.Tensor, log_clipping: bool = False, stem_name: tp.Optional[str] = None) -> None:
91
+ """Utility function to clip the audio with logging if specified."""
92
+ max_scale = wav.abs().max()
93
+ if log_clipping and max_scale > 1:
94
+ clamp_prob = (wav.abs() > 1).float().mean().item()
95
+ print(f"CLIPPING {stem_name or ''} happening with proba (a bit of clipping is okay):",
96
+ clamp_prob, "maximum scale: ", max_scale.item(), file=sys.stderr)
97
+ wav.clamp_(-1, 1)
98
+
99
+
100
+ def normalize_audio(wav: torch.Tensor, normalize: bool = True,
101
+ strategy: str = 'peak', peak_clip_headroom_db: float = 1,
102
+ rms_headroom_db: float = 18, loudness_headroom_db: float = 14,
103
+ loudness_compressor: bool = False, log_clipping: bool = False,
104
+ sample_rate: tp.Optional[int] = None,
105
+ stem_name: tp.Optional[str] = None) -> torch.Tensor:
106
+ """Normalize the audio according to the prescribed strategy (see after).
107
+
108
+ Args:
109
+ wav (torch.Tensor): Audio data.
110
+ normalize (bool): if `True` (default), normalizes according to the prescribed
111
+ strategy (see after). If `False`, the strategy is only used in case clipping
112
+ would happen.
113
+ strategy (str): Can be either 'clip', 'peak', or 'rms'. Default is 'peak',
114
+ i.e. audio is normalized by its largest value. RMS normalizes by root-mean-square
115
+ with extra headroom to avoid clipping. 'clip' just clips.
116
+ peak_clip_headroom_db (float): Headroom in dB when doing 'peak' or 'clip' strategy.
117
+ rms_headroom_db (float): Headroom in dB when doing 'rms' strategy. This must be much larger
118
+ than the `peak_clip` one to avoid further clipping.
119
+ loudness_headroom_db (float): Target loudness for loudness normalization.
120
+ loudness_compressor (bool): If True, uses tanh based soft clipping.
121
+ log_clipping (bool): If True, basic logging on stderr when clipping still
122
+ occurs despite strategy (only for 'rms').
123
+ sample_rate (int): Sample rate for the audio data (required for loudness).
124
+ stem_name (str, optional): Stem name for clipping logging.
125
+ Returns:
126
+ torch.Tensor: Normalized audio.
127
+ """
128
+ scale_peak = 10 ** (-peak_clip_headroom_db / 20)
129
+ scale_rms = 10 ** (-rms_headroom_db / 20)
130
+ if strategy == 'peak':
131
+ rescaling = (scale_peak / wav.abs().max())
132
+ if normalize or rescaling < 1:
133
+ wav = wav * rescaling
134
+ elif strategy == 'clip':
135
+ wav = wav.clamp(-scale_peak, scale_peak)
136
+ elif strategy == 'rms':
137
+ mono = wav.mean(dim=0)
138
+ rescaling = scale_rms / mono.pow(2).mean().sqrt()
139
+ if normalize or rescaling < 1:
140
+ wav = wav * rescaling
141
+ _clip_wav(wav, log_clipping=log_clipping, stem_name=stem_name)
142
+ elif strategy == 'loudness':
143
+ assert sample_rate is not None, "Loudness normalization requires sample rate."
144
+ wav = normalize_loudness(wav, sample_rate, loudness_headroom_db, loudness_compressor)
145
+ _clip_wav(wav, log_clipping=log_clipping, stem_name=stem_name)
146
+ else:
147
+ assert wav.abs().max() < 1
148
+ assert strategy == '' or strategy == 'none', f"Unexpected strategy: '{strategy}'"
149
+ return wav
150
+
151
+
152
+ def f32_pcm(wav: torch.Tensor) -> torch.Tensor:
153
+ """Convert audio to float 32 bits PCM format.
154
+ """
155
+ if wav.dtype.is_floating_point:
156
+ return wav
157
+ elif wav.dtype == torch.int16:
158
+ return wav.float() / 2**15
159
+ elif wav.dtype == torch.int32:
160
+ return wav.float() / 2**31
161
+ raise ValueError(f"Unsupported wav dtype: {wav.dtype}")
162
+
163
+
164
+ def i16_pcm(wav: torch.Tensor) -> torch.Tensor:
165
+ """Convert audio to int 16 bits PCM format.
166
+
167
+ ..Warning:: There exist many formula for doing this conversion. None are perfect
168
+ due to the asymmetry of the int16 range. One either have possible clipping, DC offset,
169
+ or inconsistencies with f32_pcm. If the given wav doesn't have enough headroom,
170
+ it is possible that `i16_pcm(f32_pcm)) != Identity`.
171
+ """
172
+ if wav.dtype.is_floating_point:
173
+ assert wav.abs().max() <= 1
174
+ candidate = (wav * 2 ** 15).round()
175
+ if candidate.max() >= 2 ** 15: # clipping would occur
176
+ candidate = (wav * (2 ** 15 - 1)).round()
177
+ return candidate.short()
178
+ else:
179
+ assert wav.dtype == torch.int16
180
+ return wav
181
+
182
+ def convert_txtchord2chroma_orig(text_chords, bpms, meters, gen_sec):
183
+ chromas = []
184
+ # total_len = int(gen_sec * 44100 / 512)
185
+ total_len = int(gen_sec * 32000 / 640)
186
+ for chord, bpm, meter in zip(text_chords, bpms, meters):
187
+ phr_len = int(60. / bpm * (meter * 4) * 32000 / 640)
188
+ # phr_len = int(60. / bpm * (meter * 4) * 44100 / 2048)
189
+ chroma = torch.zeros([total_len, 12])
190
+ count = 0
191
+ offset = 0
192
+
193
+ stext = chord.split(" ")
194
+ timebin = phr_len // 4 # frames per bar
195
+ while count < total_len:
196
+ for tokens in stext:
197
+ if count >= total_len:
198
+ break
199
+ stoken = tokens.split(',')
200
+ for token in stoken:
201
+ off_timebin = timebin + offset
202
+ rounded_timebin = round(off_timebin)
203
+ offset = off_timebin - rounded_timebin
204
+ offset = offset/len(stoken)
205
+ add_step = rounded_timebin//len(stoken)
206
+ mhot = chords.chord(token)
207
+ rolled = np.roll(mhot[2], mhot[0])
208
+ for i in range(count, count + add_step):
209
+ if count >= total_len:
210
+ break
211
+ chroma[i] = torch.Tensor(rolled)
212
+ count += 1
213
+ chromas.append(chroma)
214
+ chroma = torch.stack(chromas)
215
+ return chroma
216
+
217
+ def convert_txtchord2chroma(chord, bpm, meter, gen_sec):
218
+ total_len = int(gen_sec * 32000 / 640)
219
+
220
+ phr_len = int(60. / bpm * (meter * 4) * 32000 / 640)
221
+ # phr_len = int(60. / bpm * (meter * 4) * 44100 / 2048)
222
+ chroma = torch.zeros([total_len, 12])
223
+ count = 0
224
+ offset = 0
225
+
226
+ stext = chord.split(" ")
227
+ timebin = phr_len // 4 # frames per bar
228
+ while count < total_len:
229
+ for tokens in stext:
230
+ if count >= total_len:
231
+ break
232
+ stoken = tokens.split(',')
233
+ for token in stoken:
234
+ off_timebin = timebin + offset
235
+ rounded_timebin = round(off_timebin)
236
+ offset = off_timebin - rounded_timebin
237
+ offset = offset/len(stoken)
238
+ add_step = rounded_timebin//len(stoken)
239
+ mhot = chords.chord(token)
240
+ rolled = np.roll(mhot[2], mhot[0])
241
+ for i in range(count, count + add_step):
242
+ if count >= total_len:
243
+ break
244
+ chroma[i] = torch.Tensor(rolled)
245
+ count += 1
246
+ return chroma
247
+
248
+
249
+
250
+ def convert_txtchord2chroma_24(chord, bpm, meter, gen_sec):
251
+ total_len = int(gen_sec * 32000 / 640)
252
+
253
+ phr_len = int(60. / bpm * (meter * 4) * 32000 / 640)
254
+ # phr_len = int(60. / bpm * (meter * 4) * 44100 / 2048)
255
+ chroma = torch.zeros([total_len, 24])
256
+ count = 0
257
+ offset = 0
258
+
259
+ stext = chord.split(" ")
260
+ timebin = phr_len // 4 # frames per bar
261
+ while count < total_len:
262
+ for tokens in stext:
263
+ if count >= total_len:
264
+ break
265
+ stoken = tokens.split(',')
266
+ for token in stoken:
267
+ off_timebin = timebin + offset
268
+ rounded_timebin = round(off_timebin)
269
+ offset = off_timebin - rounded_timebin
270
+ offset = offset/len(stoken)
271
+ add_step = rounded_timebin//len(stoken)
272
+
273
+ root, bass, ivs_vec, _ = chords.chord(token)
274
+ root_vec = torch.zeros(12)
275
+ root_vec[root] = 1
276
+ final_vec = np.concatenate([root_vec, ivs_vec]) # [C]
277
+ for i in range(count, count + add_step):
278
+ if count >= total_len:
279
+ break
280
+ chroma[i] = torch.Tensor(final_vec)
281
+ count += 1
282
+ return chroma
283
+
284
+ def get_chroma_chord_from_lab(chord_path, gen_sec):
285
+ total_len = int(gen_sec * 32000 / 640)
286
+ feat_hz = 32000/640
287
+ intervals = []
288
+ labels = []
289
+ feat_chord = np.zeros((12, total_len)) # root| ivs
290
+ with open(chord_path, 'r') as f:
291
+ for line in f.readlines():
292
+ splits = line.split()
293
+ if len(splits) == 3:
294
+ st_sec, ed_sec, ctag = splits
295
+ st_sec = float(st_sec)
296
+ ed_sec = float(ed_sec)
297
+
298
+ st_frame = int(st_sec*feat_hz)
299
+ ed_frame = int(ed_sec*feat_hz)
300
+
301
+ mhot = chords.chord(ctag)
302
+ final_vec = np.roll(mhot[2], mhot[0])
303
+
304
+ final_vec = final_vec[..., None] # [C, T]
305
+ feat_chord[:, st_frame:ed_frame] = final_vec
306
+ feat_chord = torch.from_numpy(feat_chord)
307
+ return feat_chord
308
+
309
+
310
+ def get_chroma_chord_from_text(text_chord, bpm, meter, gen_sec):
311
+ total_len = int(gen_sec * 32000 / 640)
312
+
313
+ phr_len = int(60. / bpm * (meter * 4) * 32000 / 640)
314
+ chroma = np.zeros([12, total_len])
315
+ count = 0
316
+ offset = 0
317
+
318
+ stext = chord.split(" ")
319
+ timebin = phr_len // 4 # frames per bar
320
+ while count < total_len:
321
+ for tokens in stext:
322
+ if count >= total_len:
323
+ break
324
+ stoken = tokens.split(',')
325
+ for token in stoken:
326
+ off_timebin = timebin + offset
327
+ rounded_timebin = round(off_timebin)
328
+ offset = off_timebin - rounded_timebin
329
+ offset = offset/len(stoken)
330
+ add_step = rounded_timebin//len(stoken)
331
+ mhot = chords.chord(token)
332
+ final_vec = np.roll(mhot[2], mhot[0])
333
+ final_vec = final_vec[..., None] # [C, T]
334
+
335
+ for i in range(count, count + add_step):
336
+ if count >= total_len:
337
+ break
338
+ chroma[:, i] = final_vec
339
+ count += 1
340
+ feat_chord = torch.from_numpy(feat_chord)
341
+ return feat_chord
342
+
343
+ def get_beat_from_npy(beat_path, gen_sec):
344
+ total_len = int(gen_sec * 32000 / 640)
345
+
346
+ beats_np = np.load(beat_path, allow_pickle=True)
347
+ feat_beats = np.zeros((2, total_len))
348
+ meter = int(max(beats_np.T[1]))
349
+ beat_time = beats_np[:, 0]
350
+ bar_time = beats_np[np.where(beats_np[:, 1] == 1)[0], 0]
351
+
352
+ beat_frame = [int((t)*feat_hz) for t in beat_time if (t >= 0 and t < duration)]
353
+ bar_frame =[int((t)*feat_hz) for t in bar_time if (t >= 0 and t < duration)]
354
+
355
+ feat_beats[0, beat_frame] = 1
356
+ feat_beats[1, bar_frame] = 1
357
+ kernel = np.array([0.05, 0.1, 0.3, 0.9, 0.3, 0.1, 0.05])
358
+ feat_beats[0] = np.convolve(feat_beats[0] , kernel, 'same') # apply soft kernel
359
+ beat_events = feat_beats[0] + feat_beats[1]
360
+ beat_events = torch.tensor(beat_events).unsqueeze(0) # [T] -> [1, T]
361
+
362
+ bpm = 60 // np.mean([j-i for i, j in zip(beat_time[:-1], beat_time[1:])])
363
+ return beat_events, bpm, meter
364
+
365
+ def get_beat_from_bpm(bpm, meter, gen_sec):
366
+ total_len = int(gen_sec * 32000 / 640)
367
+
368
+ feat_beats = np.zeros((2, total_len))
369
+
370
+ beat_time_gap = 60 / bpm
371
+ beat_gap = 60 / bpm * feat_hz
372
+
373
+ beat_time = np.arange(0, duration, beat_time_gap)
374
+ beat_frame = np.round(np.arange(0, n_frames_feat, beat_gap)).astype(int)
375
+ if beat_frame[-1] == n_frames_feat:
376
+ beat_frame = beat_frame[:-1]
377
+ bar_frame = beat_frame[::meter]
378
+
379
+ feat_beats[0, beat_frame] = 1
380
+ feat_beats[1, bar_frame] = 1
381
+ kernel = np.array([0.05, 0.1, 0.3, 0.9, 0.3, 0.1, 0.05])
382
+ feat_beats[0] = np.convolve(feat_beats[0] , kernel, 'same') # apply soft kernel
383
+ beat_events = feat_beats[0] + feat_beats[1]
384
+ beat_events = torch.tensor(beat_events).unsqueeze(0) # [T] -> [1, T]
385
+ return beat_events, beat_time, meter
audiocraft/data/chords.py ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # encoding: utf-8
2
+ """
3
+ This module contains chord evaluation functionality.
4
+
5
+ It provides the evaluation measures used for the MIREX ACE task, and
6
+ tries to follow [1]_ and [2]_ as closely as possible.
7
+
8
+ Notes
9
+ -----
10
+ This implementation tries to follow the references and their implementation
11
+ (e.g., https://github.com/jpauwels/MusOOEvaluator for [2]_). However, there
12
+ are some known (and possibly some unknown) differences. If you find one not
13
+ listed in the following, please file an issue:
14
+
15
+ - Detected chord segments are adjusted to fit the length of the annotations.
16
+ In particular, this means that, if necessary, filler segments of 'no chord'
17
+ are added at beginnings and ends. This can result in different segmentation
18
+ scores compared to the original implementation.
19
+
20
+ References
21
+ ----------
22
+ .. [1] Christopher Harte, "Towards Automatic Extraction of Harmony Information
23
+ from Music Signals." Dissertation,
24
+ Department for Electronic Engineering, Queen Mary University of London,
25
+ 2010.
26
+ .. [2] Johan Pauwels and Geoffroy Peeters.
27
+ "Evaluating Automatically Estimated Chord Sequences."
28
+ In Proceedings of ICASSP 2013, Vancouver, Canada, 2013.
29
+
30
+ """
31
+
32
+ import numpy as np
33
+ import pandas as pd
34
+
35
+
36
+ CHORD_DTYPE = [('root', np.int_),
37
+ ('bass', np.int_),
38
+ ('intervals', np.int_, (12,)),
39
+ ('is_major',np.bool_)]
40
+
41
+ CHORD_ANN_DTYPE = [('start', np.float32),
42
+ ('end', np.float32),
43
+ ('chord', CHORD_DTYPE)]
44
+
45
+ NO_CHORD = (-1, -1, np.zeros(12, dtype=np.int_), False)
46
+ UNKNOWN_CHORD = (-1, -1, np.ones(12, dtype=np.int_) * -1, False)
47
+
48
+ PITCH_CLASS = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
49
+
50
+
51
+ def idx_to_chord(idx):
52
+ if idx == 24:
53
+ return "-"
54
+ elif idx == 25:
55
+ return u"\u03B5"
56
+
57
+ minmaj = idx % 2
58
+ root = idx // 2
59
+
60
+ return PITCH_CLASS[root] + ("M" if minmaj == 0 else "m")
61
+
62
+ class Chords:
63
+
64
+ def __init__(self):
65
+ self._shorthands = {
66
+ 'maj': self.interval_list('(1,3,5)'),
67
+ 'min': self.interval_list('(1,b3,5)'),
68
+ 'dim': self.interval_list('(1,b3,b5)'),
69
+ 'aug': self.interval_list('(1,3,#5)'),
70
+ 'maj7': self.interval_list('(1,3,5,7)'),
71
+ 'min7': self.interval_list('(1,b3,5,b7)'),
72
+ '7': self.interval_list('(1,3,5,b7)'),
73
+ '6': self.interval_list('(1,6)'), # custom
74
+ '5': self.interval_list('(1,5)'),
75
+ '4': self.interval_list('(1,4)'), # custom
76
+ '1': self.interval_list('(1)'),
77
+ 'dim7': self.interval_list('(1,b3,b5,bb7)'),
78
+ 'hdim7': self.interval_list('(1,b3,b5,b7)'),
79
+ 'minmaj7': self.interval_list('(1,b3,5,7)'),
80
+ 'maj6': self.interval_list('(1,3,5,6)'),
81
+ 'min6': self.interval_list('(1,b3,5,6)'),
82
+ '9': self.interval_list('(1,3,5,b7,9)'),
83
+ 'maj9': self.interval_list('(1,3,5,7,9)'),
84
+ 'min9': self.interval_list('(1,b3,5,b7,9)'),
85
+ 'add9': self.interval_list('(1,3,5,9)'), # custom
86
+ 'sus2': self.interval_list('(1,2,5)'),
87
+ 'sus4': self.interval_list('(1,4,5)'),
88
+ '7sus2': self.interval_list('(1,2,5,b7)'), # custom
89
+ '7sus4': self.interval_list('(1,4,5,b7)'), # custom
90
+ '11': self.interval_list('(1,3,5,b7,9,11)'),
91
+ 'min11': self.interval_list('(1,b3,5,b7,9,11)'),
92
+ '13': self.interval_list('(1,3,5,b7,13)'),
93
+ 'maj13': self.interval_list('(1,3,5,7,13)'),
94
+ 'min13': self.interval_list('(1,b3,5,b7,13)')
95
+ }
96
+
97
+ def chords(self, labels):
98
+
99
+ """
100
+ Transform a list of chord labels into an array of internal numeric
101
+ representations.
102
+
103
+ Parameters
104
+ ----------
105
+ labels : list
106
+ List of chord labels (str).
107
+
108
+ Returns
109
+ -------
110
+ chords : numpy.array
111
+ Structured array with columns 'root', 'bass', and 'intervals',
112
+ containing a numeric representation of chords.
113
+
114
+ """
115
+ crds = np.zeros(len(labels), dtype=CHORD_DTYPE)
116
+ cache = {}
117
+ for i, lbl in enumerate(labels):
118
+ cv = cache.get(lbl, None)
119
+ if cv is None:
120
+ cv = self.chord(lbl)
121
+ cache[lbl] = cv
122
+ crds[i] = cv
123
+
124
+ return crds
125
+
126
+ def label_error_modify(self, label):
127
+ if label == 'Emin/4': label = 'E:min/4'
128
+ elif label == 'A7/3': label = 'A:7/3'
129
+ elif label == 'Bb7/3': label = 'Bb:7/3'
130
+ elif label == 'Bb7/5': label = 'Bb:7/5'
131
+ elif label.find(':') == -1:
132
+ if label.find('min') != -1:
133
+ label = label[:label.find('min')] + ':' + label[label.find('min'):]
134
+ return label
135
+
136
+ def chord(self, label):
137
+ """
138
+ Transform a chord label into the internal numeric represenation of
139
+ (root, bass, intervals array).
140
+
141
+ Parameters
142
+ ----------
143
+ label : str
144
+ Chord label.
145
+
146
+ Returns
147
+ -------
148
+ chord : tuple
149
+ Numeric representation of the chord: (root, bass, intervals array).
150
+
151
+ """
152
+
153
+
154
+ is_major = False
155
+
156
+ if label == 'N':
157
+ return NO_CHORD
158
+ if label == 'X':
159
+ return UNKNOWN_CHORD
160
+
161
+ label = self.label_error_modify(label)
162
+
163
+ c_idx = label.find(':')
164
+ s_idx = label.find('/')
165
+
166
+ if c_idx == -1:
167
+ quality_str = 'maj'
168
+ if s_idx == -1:
169
+ root_str = label
170
+ bass_str = ''
171
+ else:
172
+ root_str = label[:s_idx]
173
+ bass_str = label[s_idx + 1:]
174
+ else:
175
+ root_str = label[:c_idx]
176
+ if s_idx == -1:
177
+ quality_str = label[c_idx + 1:]
178
+ bass_str = ''
179
+ else:
180
+ quality_str = label[c_idx + 1:s_idx]
181
+ bass_str = label[s_idx + 1:]
182
+
183
+ root = self.pitch(root_str)
184
+ bass = self.interval(bass_str) if bass_str else 0
185
+ ivs = self.chord_intervals(quality_str)
186
+ ivs[bass] = 1
187
+
188
+ if 'min' in quality_str:
189
+ is_major = False
190
+ else:
191
+ is_major = True
192
+
193
+
194
+ return root, bass, ivs, is_major
195
+
196
+ _l = [0, 1, 1, 0, 1, 1, 1]
197
+ _chroma_id = (np.arange(len(_l) * 2) + 1) + np.array(_l + _l).cumsum() - 1
198
+
199
+ def modify(self, base_pitch, modifier):
200
+ """
201
+ Modify a pitch class in integer representation by a given modifier string.
202
+
203
+ A modifier string can be any sequence of 'b' (one semitone down)
204
+ and '#' (one semitone up).
205
+
206
+ Parameters
207
+ ----------
208
+ base_pitch : int
209
+ Pitch class as integer.
210
+ modifier : str
211
+ String of modifiers ('b' or '#').
212
+
213
+ Returns
214
+ -------
215
+ modified_pitch : int
216
+ Modified root note.
217
+
218
+ """
219
+ for m in modifier:
220
+ if m == 'b':
221
+ base_pitch -= 1
222
+ elif m == '#':
223
+ base_pitch += 1
224
+ else:
225
+ raise ValueError('Unknown modifier: {}'.format(m))
226
+ return base_pitch
227
+
228
+ def pitch(self, pitch_str):
229
+ """
230
+ Convert a string representation of a pitch class (consisting of root
231
+ note and modifiers) to an integer representation.
232
+
233
+ Parameters
234
+ ----------
235
+ pitch_str : str
236
+ String representation of a pitch class.
237
+
238
+ Returns
239
+ -------
240
+ pitch : int
241
+ Integer representation of a pitch class.
242
+
243
+ """
244
+ return self.modify(self._chroma_id[(ord(pitch_str[0]) - ord('C')) % 7],
245
+ pitch_str[1:]) % 12
246
+
247
+ def interval(self, interval_str):
248
+ """
249
+ Convert a string representation of a musical interval into a pitch class
250
+ (e.g. a minor seventh 'b7' into 10, because it is 10 semitones above its
251
+ base note).
252
+
253
+ Parameters
254
+ ----------
255
+ interval_str : str
256
+ Musical interval.
257
+
258
+ Returns
259
+ -------
260
+ pitch_class : int
261
+ Number of semitones to base note of interval.
262
+
263
+ """
264
+ for i, c in enumerate(interval_str):
265
+ if c.isdigit():
266
+ return self.modify(self._chroma_id[int(interval_str[i:]) - 1],
267
+ interval_str[:i]) % 12
268
+
269
+ def interval_list(self, intervals_str, given_pitch_classes=None):
270
+ """
271
+ Convert a list of intervals given as string to a binary pitch class
272
+ representation. For example, 'b3, 5' would become
273
+ [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0].
274
+
275
+ Parameters
276
+ ----------
277
+ intervals_str : str
278
+ List of intervals as comma-separated string (e.g. 'b3, 5').
279
+ given_pitch_classes : None or numpy array
280
+ If None, start with empty pitch class array, if numpy array of length
281
+ 12, this array will be modified.
282
+
283
+ Returns
284
+ -------
285
+ pitch_classes : numpy array
286
+ Binary pitch class representation of intervals.
287
+
288
+ """
289
+ if given_pitch_classes is None:
290
+ given_pitch_classes = np.zeros(12, dtype=np.int_)
291
+ for int_def in intervals_str[1:-1].split(','):
292
+ int_def = int_def.strip()
293
+ if int_def[0] == '*':
294
+ given_pitch_classes[self.interval(int_def[1:])] = 0
295
+ else:
296
+ given_pitch_classes[self.interval(int_def)] = 1
297
+ return given_pitch_classes
298
+
299
+ # mapping of shorthand interval notations to the actual interval representation
300
+
301
+ def chord_intervals(self, quality_str):
302
+ """
303
+ Convert a chord quality string to a pitch class representation. For
304
+ example, 'maj' becomes [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0].
305
+
306
+ Parameters
307
+ ----------
308
+ quality_str : str
309
+ String defining the chord quality.
310
+
311
+ Returns
312
+ -------
313
+ pitch_classes : numpy array
314
+ Binary pitch class representation of chord quality.
315
+
316
+ """
317
+ list_idx = quality_str.find('(')
318
+ if list_idx == -1:
319
+ return self._shorthands[quality_str].copy()
320
+ if list_idx != 0:
321
+ ivs = self._shorthands[quality_str[:list_idx]].copy()
322
+ else:
323
+ ivs = np.zeros(12, dtype=np.int_)
324
+
325
+
326
+ return self.interval_list(quality_str[list_idx:], ivs)
327
+
328
+ def load_chords(self, filename):
329
+ """
330
+ Load chords from a text file.
331
+
332
+ The chord must follow the syntax defined in [1]_.
333
+
334
+ Parameters
335
+ ----------
336
+ filename : str
337
+ File containing chord segments.
338
+
339
+ Returns
340
+ -------
341
+ crds : numpy structured array
342
+ Structured array with columns "start", "end", and "chord",
343
+ containing the beginning, end, and chord definition of chord
344
+ segments.
345
+
346
+ References
347
+ ----------
348
+ .. [1] Christopher Harte, "Towards Automatic Extraction of Harmony
349
+ Information from Music Signals." Dissertation,
350
+ Department for Electronic Engineering, Queen Mary University of
351
+ London, 2010.
352
+
353
+ """
354
+ start, end, chord_labels = [], [], []
355
+ with open(filename, 'r') as f:
356
+ for line in f:
357
+ if line:
358
+
359
+ splits = line.split()
360
+ if len(splits) == 3:
361
+
362
+ s = splits[0]
363
+ e = splits[1]
364
+ l = splits[2]
365
+
366
+ start.append(float(s))
367
+ end.append(float(e))
368
+ chord_labels.append(l)
369
+
370
+ crds = np.zeros(len(start), dtype=CHORD_ANN_DTYPE)
371
+ crds['start'] = start
372
+ crds['end'] = end
373
+ crds['chord'] = self.chords(chord_labels)
374
+
375
+ return crds
376
+
377
+ def reduce_to_triads(self, chords, keep_bass=False):
378
+ """
379
+ Reduce chords to triads.
380
+
381
+ The function follows the reduction rules implemented in [1]_. If a chord
382
+ chord does not contain a third, major second or fourth, it is reduced to
383
+ a power chord. If it does not contain neither a third nor a fifth, it is
384
+ reduced to a single note "chord".
385
+
386
+ Parameters
387
+ ----------
388
+ chords : numpy structured array
389
+ Chords to be reduced.
390
+ keep_bass : bool
391
+ Indicates whether to keep the bass note or set it to 0.
392
+
393
+ Returns
394
+ -------
395
+ reduced_chords : numpy structured array
396
+ Chords reduced to triads.
397
+
398
+ References
399
+ ----------
400
+ .. [1] Johan Pauwels and Geoffroy Peeters.
401
+ "Evaluating Automatically Estimated Chord Sequences."
402
+ In Proceedings of ICASSP 2013, Vancouver, Canada, 2013.
403
+
404
+ """
405
+ unison = chords['intervals'][:, 0].astype(bool)
406
+ maj_sec = chords['intervals'][:, 2].astype(bool)
407
+ min_third = chords['intervals'][:, 3].astype(bool)
408
+ maj_third = chords['intervals'][:, 4].astype(bool)
409
+ perf_fourth = chords['intervals'][:, 5].astype(bool)
410
+ dim_fifth = chords['intervals'][:, 6].astype(bool)
411
+ perf_fifth = chords['intervals'][:, 7].astype(bool)
412
+ aug_fifth = chords['intervals'][:, 8].astype(bool)
413
+ no_chord = (chords['intervals'] == NO_CHORD[-1]).all(axis=1)
414
+
415
+ reduced_chords = chords.copy()
416
+ ivs = reduced_chords['intervals']
417
+
418
+ ivs[~no_chord] = self.interval_list('(1)')
419
+ ivs[unison & perf_fifth] = self.interval_list('(1,5)')
420
+ ivs[~perf_fourth & maj_sec] = self._shorthands['sus2']
421
+ ivs[perf_fourth & ~maj_sec] = self._shorthands['sus4']
422
+
423
+ ivs[min_third] = self._shorthands['min']
424
+ ivs[min_third & aug_fifth & ~perf_fifth] = self.interval_list('(1,b3,#5)')
425
+ ivs[min_third & dim_fifth & ~perf_fifth] = self._shorthands['dim']
426
+
427
+ ivs[maj_third] = self._shorthands['maj']
428
+ ivs[maj_third & dim_fifth & ~perf_fifth] = self.interval_list('(1,3,b5)')
429
+ ivs[maj_third & aug_fifth & ~perf_fifth] = self._shorthands['aug']
430
+
431
+ if not keep_bass:
432
+ reduced_chords['bass'] = 0
433
+ else:
434
+ # remove bass notes if they are not part of the intervals anymore
435
+ reduced_chords['bass'] *= ivs[range(len(reduced_chords)),
436
+ reduced_chords['bass']]
437
+ # keep -1 in bass for no chords
438
+ reduced_chords['bass'][no_chord] = -1
439
+
440
+ return reduced_chords
441
+
442
+ def convert_to_id(self, root, is_major):
443
+ if root == -1:
444
+ return 24
445
+ else:
446
+ if is_major:
447
+ return root * 2
448
+ else:
449
+ return root * 2 + 1
450
+
451
+ def get_converted_chord(self, filename):
452
+ loaded_chord = self.load_chords(filename)
453
+ triads = self.reduce_to_triads(loaded_chord['chord'])
454
+
455
+ df = self.assign_chord_id(triads)
456
+ df['start'] = loaded_chord['start']
457
+ df['end'] = loaded_chord['end']
458
+
459
+ return df
460
+
461
+ def assign_chord_id(self, entry):
462
+ # maj, min chord only
463
+ # if you want to add other chord, change this part and get_converted_chord(reduce_to_triads)
464
+ df = pd.DataFrame(data=entry[['root', 'is_major']])
465
+ df['chord_id'] = df.apply(lambda row: self.convert_to_id(row['root'], row['is_major']), axis=1)
466
+ return df
467
+
468
+ def convert_to_id_voca(self, root, quality):
469
+ if root == -1:
470
+ return 169
471
+ else:
472
+ if quality == 'min':
473
+ return root * 14
474
+ elif quality == 'maj':
475
+ return root * 14 + 1
476
+ elif quality == 'dim':
477
+ return root * 14 + 2
478
+ elif quality == 'aug':
479
+ return root * 14 + 3
480
+ elif quality == 'min6':
481
+ return root * 14 + 4
482
+ elif quality == 'maj6':
483
+ return root * 14 + 5
484
+ elif quality == 'min7':
485
+ return root * 14 + 6
486
+ elif quality == 'minmaj7':
487
+ return root * 14 + 7
488
+ elif quality == 'maj7':
489
+ return root * 14 + 8
490
+ elif quality == '7':
491
+ return root * 14 + 9
492
+ elif quality == 'dim7':
493
+ return root * 14 + 10
494
+ elif quality == 'hdim7':
495
+ return root * 14 + 11
496
+ elif quality == 'sus2':
497
+ return root * 14 + 12
498
+ elif quality == 'sus4':
499
+ return root * 14 + 13
500
+ else:
501
+ return 168
502
+
503
+
504
+ def lab_file_error_modify(self, ref_labels):
505
+ for i in range(len(ref_labels)):
506
+ if ref_labels[i][-2:] == ':4':
507
+ ref_labels[i] = ref_labels[i].replace(':4', ':sus4')
508
+ elif ref_labels[i][-2:] == ':6':
509
+ ref_labels[i] = ref_labels[i].replace(':6', ':maj6')
510
+ elif ref_labels[i][-4:] == ':6/2':
511
+ ref_labels[i] = ref_labels[i].replace(':6/2', ':maj6/2')
512
+ elif ref_labels[i] == 'Emin/4':
513
+ ref_labels[i] = 'E:min/4'
514
+ elif ref_labels[i] == 'A7/3':
515
+ ref_labels[i] = 'A:7/3'
516
+ elif ref_labels[i] == 'Bb7/3':
517
+ ref_labels[i] = 'Bb:7/3'
518
+ elif ref_labels[i] == 'Bb7/5':
519
+ ref_labels[i] = 'Bb:7/5'
520
+ elif ref_labels[i].find(':') == -1:
521
+ if ref_labels[i].find('min') != -1:
522
+ ref_labels[i] = ref_labels[i][:ref_labels[i].find('min')] + ':' + ref_labels[i][ref_labels[i].find('min'):]
523
+ return ref_labels
524
+
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)
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
audiocraft/models/__init__.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ # AudioGen and MultiBandDiffusion dropped: unused by MusicGen inference, and MultiBandDiffusion
11
+ # pulls in audiocraft.solvers, which isn't ported here bcs not needed in inf
12
+ from . import builders, loaders
13
+ from .encodec import (
14
+ CompressionModel, EncodecModel, DAC,
15
+ HFEncodecModel, HFEncodecCompressionModel)
16
+ from .lm import LMModel
17
+ from .musicgen import MusicGen
18
+ from .unet import DiffusionUnet
audiocraft/models/builders.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
+ 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 audiocraft
15
+ import omegaconf
16
+ import torch
17
+
18
+ from .encodec import CompressionModel, EncodecModel
19
+ from .lm import LMModel
20
+ from ..modules.codebooks_patterns import (
21
+ CodebooksPatternProvider,
22
+ DelayedPatternProvider,
23
+ MusicLMPattern,
24
+ ParallelPatternProvider,
25
+ UnrolledPatternProvider,
26
+ VALLEPattern,
27
+ )
28
+ from ..modules.conditioners import (
29
+ BaseConditioner,
30
+ ChromaStemConditioner,
31
+ CLAPEmbeddingConditioner,
32
+ ConditionFuser,
33
+ ConditioningProvider,
34
+ LUTConditioner,
35
+ T5Conditioner,
36
+ ChordProgressionConditioner,
37
+ BeatConditioner
38
+ )
39
+ from .unet import DiffusionUnet
40
+ from .. import quantization as qt
41
+ from ..utils.utils import dict_from_config
42
+ from ..modules.diffusion_schedule import MultiBandProcessor, SampleProcessor
43
+
44
+
45
+ def get_quantizer(quantizer: str, cfg: omegaconf.DictConfig, dimension: int) -> qt.BaseQuantizer:
46
+ klass = {
47
+ 'no_quant': qt.DummyQuantizer,
48
+ 'rvq': qt.ResidualVectorQuantizer
49
+ }[quantizer]
50
+ kwargs = dict_from_config(getattr(cfg, quantizer))
51
+ if quantizer != 'no_quant':
52
+ kwargs['dimension'] = dimension
53
+ return klass(**kwargs)
54
+
55
+
56
+ def get_encodec_autoencoder(encoder_name: str, cfg: omegaconf.DictConfig):
57
+ if encoder_name == 'seanet':
58
+ kwargs = dict_from_config(getattr(cfg, 'seanet'))
59
+ encoder_override_kwargs = kwargs.pop('encoder')
60
+ decoder_override_kwargs = kwargs.pop('decoder')
61
+ encoder_kwargs = {**kwargs, **encoder_override_kwargs}
62
+ decoder_kwargs = {**kwargs, **decoder_override_kwargs}
63
+ encoder = audiocraft.modules.SEANetEncoder(**encoder_kwargs)
64
+ decoder = audiocraft.modules.SEANetDecoder(**decoder_kwargs)
65
+ return encoder, decoder
66
+ else:
67
+ raise KeyError(f"Unexpected compression model {cfg.compression_model}")
68
+
69
+
70
+ def get_compression_model(cfg: omegaconf.DictConfig) -> CompressionModel:
71
+ """Instantiate a compression model."""
72
+ if cfg.compression_model == 'encodec':
73
+ kwargs = dict_from_config(getattr(cfg, 'encodec'))
74
+ encoder_name = kwargs.pop('autoencoder')
75
+ quantizer_name = kwargs.pop('quantizer')
76
+ encoder, decoder = get_encodec_autoencoder(encoder_name, cfg)
77
+ quantizer = get_quantizer(quantizer_name, cfg, encoder.dimension)
78
+ frame_rate = kwargs['sample_rate'] // encoder.hop_length
79
+ renormalize = kwargs.pop('renormalize', False)
80
+ # deprecated params
81
+ kwargs.pop('renorm', None)
82
+ return EncodecModel(encoder, decoder, quantizer,
83
+ frame_rate=frame_rate, renormalize=renormalize, **kwargs).to(cfg.device)
84
+ else:
85
+ raise KeyError(f"Unexpected compression model {cfg.compression_model}")
86
+
87
+
88
+ def get_lm_model(cfg: omegaconf.DictConfig) -> LMModel:
89
+ """Instantiate a transformer LM."""
90
+ if cfg.lm_model == 'transformer_lm':
91
+ kwargs = dict_from_config(getattr(cfg, 'transformer_lm'))
92
+ n_q = kwargs['n_q']
93
+ q_modeling = kwargs.pop('q_modeling', None)
94
+ codebooks_pattern_cfg = getattr(cfg, 'codebooks_pattern')
95
+ attribute_dropout = dict_from_config(getattr(cfg, 'attribute_dropout'))
96
+ cls_free_guidance = dict_from_config(getattr(cfg, 'classifier_free_guidance'))
97
+ cfg_prob, cfg_coef = cls_free_guidance['training_dropout'], cls_free_guidance['inference_coef']
98
+ fuser = get_condition_fuser(cfg)
99
+ condition_provider = get_conditioner_provider(kwargs["dim"], cfg).to(cfg.device)
100
+ if len(fuser.fuse2cond['cross']) > 0: # enforce cross-att programmatically
101
+ kwargs['cross_attention'] = True
102
+ if codebooks_pattern_cfg.modeling is None:
103
+ assert q_modeling is not None, \
104
+ "LM model should either have a codebook pattern defined or transformer_lm.q_modeling"
105
+ codebooks_pattern_cfg = omegaconf.OmegaConf.create(
106
+ {'modeling': q_modeling, 'delay': {'delays': list(range(n_q))}}
107
+ )
108
+ pattern_provider = get_codebooks_pattern_provider(n_q, codebooks_pattern_cfg)
109
+ return LMModel(
110
+ pattern_provider=pattern_provider,
111
+ condition_provider=condition_provider,
112
+ fuser=fuser,
113
+ cfg_dropout=cfg_prob,
114
+ cfg_coef=cfg_coef,
115
+ attribute_dropout=attribute_dropout,
116
+ dtype=getattr(torch, cfg.dtype),
117
+ device=cfg.device,
118
+ **kwargs
119
+ ).to(cfg.device)
120
+ else:
121
+ raise KeyError(f"Unexpected LM model {cfg.lm_model}")
122
+
123
+
124
+ def get_conditioner_provider(output_dim: int, cfg: omegaconf.DictConfig) -> ConditioningProvider:
125
+ """Instantiate a conditioning model."""
126
+ device = cfg.device
127
+ duration = cfg.dataset.segment_duration
128
+ cfg = getattr(cfg, 'conditioners')
129
+ dict_cfg = {} if cfg is None else dict_from_config(cfg)
130
+ conditioners: tp.Dict[str, BaseConditioner] = {}
131
+ condition_provider_args = dict_cfg.pop('args', {})
132
+ condition_provider_args.pop('merge_text_conditions_p', None)
133
+ condition_provider_args.pop('drop_desc_p', None)
134
+
135
+ for cond, cond_cfg in dict_cfg.items():
136
+ model_type = cond_cfg['model']
137
+ model_args = cond_cfg[model_type]
138
+ if model_type == 't5':
139
+ conditioners[str(cond)] = T5Conditioner(output_dim=output_dim, device=device, **model_args)
140
+ elif model_type == 'lut':
141
+ conditioners[str(cond)] = LUTConditioner(output_dim=output_dim, **model_args)
142
+ elif model_type == 'chroma_stem':
143
+ conditioners[str(cond)] = ChromaStemConditioner(
144
+ output_dim=output_dim,
145
+ duration=duration,
146
+ device=device,
147
+ **model_args
148
+ )
149
+ elif model_type == 'beat':
150
+ conditioners[str(cond)] = BeatConditioner(
151
+ output_dim=output_dim,
152
+ device=device,
153
+ **model_args
154
+ )
155
+ elif model_type == 'chord':
156
+ conditioners[str(cond)] = ChordProgressionConditioner(
157
+ output_dim=output_dim,
158
+ device=device,
159
+ **model_args
160
+ )
161
+ elif model_type == 'clap':
162
+ conditioners[str(cond)] = CLAPEmbeddingConditioner(
163
+ output_dim=output_dim,
164
+ device=device,
165
+ **model_args
166
+ )
167
+ else:
168
+ raise ValueError(f"Unrecognized conditioning model: {model_type}")
169
+ conditioner = ConditioningProvider(conditioners, device=device, **condition_provider_args)
170
+ return conditioner
171
+
172
+
173
+ def get_condition_fuser(cfg: omegaconf.DictConfig) -> ConditionFuser:
174
+ """Instantiate a condition fuser object."""
175
+ fuser_cfg = getattr(cfg, 'fuser')
176
+ fuser_methods = ['sum', 'cross', 'prepend', 'input_interpolate']
177
+ fuse2cond = {k: fuser_cfg[k] for k in fuser_methods}
178
+ kwargs = {k: v for k, v in fuser_cfg.items() if k not in fuser_methods}
179
+ print(f"==== use in-attention: {fuser_cfg['in_attn']} ====")
180
+ fuser = ConditionFuser(fuse2cond=fuse2cond, **kwargs)
181
+ return fuser
182
+
183
+
184
+ def get_codebooks_pattern_provider(n_q: int, cfg: omegaconf.DictConfig) -> CodebooksPatternProvider:
185
+ """Instantiate a codebooks pattern provider object."""
186
+ pattern_providers = {
187
+ 'parallel': ParallelPatternProvider,
188
+ 'delay': DelayedPatternProvider,
189
+ 'unroll': UnrolledPatternProvider,
190
+ 'valle': VALLEPattern,
191
+ 'musiclm': MusicLMPattern,
192
+ }
193
+ name = cfg.modeling
194
+ kwargs = dict_from_config(cfg.get(name)) if hasattr(cfg, name) else {}
195
+ klass = pattern_providers[name]
196
+ return klass(n_q, **kwargs)
197
+
198
+
199
+ def get_debug_compression_model(device='cpu', sample_rate: int = 32000):
200
+ """Instantiate a debug compression model to be used for unit tests."""
201
+ assert sample_rate in [16000, 32000], "unsupported sample rate for debug compression model"
202
+ model_ratios = {
203
+ 16000: [10, 8, 8], # 25 Hz at 16kHz
204
+ 32000: [10, 8, 16] # 25 Hz at 32kHz
205
+ }
206
+ ratios: tp.List[int] = model_ratios[sample_rate]
207
+ frame_rate = 25
208
+ seanet_kwargs: dict = {
209
+ 'n_filters': 4,
210
+ 'n_residual_layers': 1,
211
+ 'dimension': 32,
212
+ 'ratios': ratios,
213
+ }
214
+ print(seanet_kwargs)
215
+ encoder = audiocraft.modules.SEANetEncoder(**seanet_kwargs)
216
+ decoder = audiocraft.modules.SEANetDecoder(**seanet_kwargs)
217
+ quantizer = qt.ResidualVectorQuantizer(dimension=32, bins=400, n_q=4)
218
+ init_x = torch.randn(8, 32, 128)
219
+ quantizer(init_x, 1) # initialize kmeans etc.
220
+ compression_model = EncodecModel(
221
+ encoder, decoder, quantizer,
222
+ frame_rate=frame_rate, sample_rate=sample_rate, channels=1).to(device)
223
+ return compression_model.eval()
224
+
225
+
226
+ def get_diffusion_model(cfg: omegaconf.DictConfig):
227
+ # TODO Find a way to infer the channels from dset
228
+ channels = cfg.channels
229
+ num_steps = cfg.schedule.num_steps
230
+ return DiffusionUnet(
231
+ chin=channels, num_steps=num_steps, **cfg.diffusion_unet)
232
+
233
+
234
+ def get_processor(cfg, sample_rate: int = 24000):
235
+ sample_processor = SampleProcessor()
236
+ if cfg.use:
237
+ kw = dict(cfg)
238
+ kw.pop('use')
239
+ kw.pop('name')
240
+ if cfg.name == "multi_band_processor":
241
+ sample_processor = MultiBandProcessor(sample_rate=sample_rate, **kw)
242
+ return sample_processor
243
+
244
+
245
+ def get_debug_lm_model(device='cpu'):
246
+ """Instantiate a debug LM to be used for unit tests."""
247
+ pattern = DelayedPatternProvider(n_q=4)
248
+ dim = 16
249
+ providers = {
250
+ 'description': LUTConditioner(n_bins=128, dim=dim, output_dim=dim, tokenizer="whitespace"),
251
+ }
252
+ condition_provider = ConditioningProvider(providers)
253
+ fuser = ConditionFuser(
254
+ {'cross': ['description'], 'prepend': [],
255
+ 'sum': [], 'input_interpolate': []})
256
+ lm = LMModel(
257
+ pattern, condition_provider, fuser,
258
+ n_q=4, card=400, dim=dim, num_heads=4, custom=True, num_layers=2,
259
+ cross_attention=True, causal=True)
260
+ return lm.to(device).eval()
261
+
262
+
263
+ def get_wrapped_compression_model(
264
+ compression_model: CompressionModel,
265
+ cfg: omegaconf.DictConfig) -> CompressionModel:
266
+ # more to come.
267
+ return compression_model
audiocraft/models/encodec.py ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import numpy as np
17
+ import torch
18
+ from torch import nn
19
+ from transformers import EncodecModel as HFEncodecModel
20
+
21
+ from .. import quantization as qt
22
+
23
+
24
+ logger = logging.getLogger()
25
+
26
+
27
+ class CompressionModel(ABC, nn.Module):
28
+ """Base API for all compression model that aim at being used as audio tokenizers
29
+ with a language model.
30
+ """
31
+
32
+ @abstractmethod
33
+ def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
34
+ ...
35
+
36
+ @abstractmethod
37
+ def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
38
+ """See `EncodecModel.encode`."""
39
+ ...
40
+
41
+ @abstractmethod
42
+ def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
43
+ """See `EncodecModel.decode`."""
44
+ ...
45
+
46
+ @abstractmethod
47
+ def decode_latent(self, codes: torch.Tensor):
48
+ """Decode from the discrete codes to continuous latent space."""
49
+ ...
50
+
51
+ @property
52
+ @abstractmethod
53
+ def channels(self) -> int:
54
+ ...
55
+
56
+ @property
57
+ @abstractmethod
58
+ def frame_rate(self) -> float:
59
+ ...
60
+
61
+ @property
62
+ @abstractmethod
63
+ def sample_rate(self) -> int:
64
+ ...
65
+
66
+ @property
67
+ @abstractmethod
68
+ def cardinality(self) -> int:
69
+ ...
70
+
71
+ @property
72
+ @abstractmethod
73
+ def num_codebooks(self) -> int:
74
+ ...
75
+
76
+ @property
77
+ @abstractmethod
78
+ def total_codebooks(self) -> int:
79
+ ...
80
+
81
+ @abstractmethod
82
+ def set_num_codebooks(self, n: int):
83
+ """Set the active number of codebooks used by the quantizer."""
84
+ ...
85
+
86
+ @staticmethod
87
+ def get_pretrained(
88
+ name: str, device: tp.Union[torch.device, str] = 'cpu'
89
+ ) -> 'CompressionModel':
90
+ """Instantiate a CompressionModel from a given pretrained model.
91
+
92
+ Args:
93
+ name (Path or str): name of the pretrained model. See after.
94
+ device (torch.device or str): Device on which the model is loaded.
95
+
96
+ Pretrained models:
97
+ - dac_44khz (https://github.com/descriptinc/descript-audio-codec)
98
+ - dac_24khz (same)
99
+ - facebook/encodec_24khz (https://huggingface.co/facebook/encodec_24khz)
100
+ - facebook/encodec_32khz (https://huggingface.co/facebook/encodec_32khz)
101
+ - your own model on HugginFace. Export instructions to come...
102
+ """
103
+
104
+ from . import builders, loaders
105
+ model: CompressionModel
106
+ if name in ['dac_44khz', 'dac_24khz']:
107
+ model_type = name.split('_')[1]
108
+ logger.info("Getting pretrained compression model from DAC %s", model_type)
109
+ model = DAC(model_type)
110
+ elif name in ['debug_compression_model']:
111
+ logger.info("Getting pretrained compression model for debug")
112
+ model = builders.get_debug_compression_model()
113
+ elif Path(name).exists():
114
+ # We assume here if the paths exist that it is in fact an AC checkpoint
115
+ # that was exported using `audiocraft.utils.export` functions.
116
+ model = loaders.load_compression_model(name, device=device)
117
+ else:
118
+ logger.info("Getting pretrained compression model from HF %s", name)
119
+ hf_model = HFEncodecModel.from_pretrained(name)
120
+ model = HFEncodecCompressionModel(hf_model).to(device)
121
+ return model.to(device).eval()
122
+
123
+
124
+ class EncodecModel(CompressionModel):
125
+ """Encodec model operating on the raw waveform.
126
+
127
+ Args:
128
+ encoder (nn.Module): Encoder network.
129
+ decoder (nn.Module): Decoder network.
130
+ quantizer (qt.BaseQuantizer): Quantizer network.
131
+ frame_rate (int): Frame rate for the latent representation.
132
+ sample_rate (int): Audio sample rate.
133
+ channels (int): Number of audio channels.
134
+ causal (bool): Whether to use a causal version of the model.
135
+ renormalize (bool): Whether to renormalize the audio before running the model.
136
+ """
137
+ # we need assignment to override the property in the abstract class,
138
+ # I couldn't find a better way...
139
+ frame_rate: float = 0
140
+ sample_rate: int = 0
141
+ channels: int = 0
142
+
143
+ def __init__(self,
144
+ encoder: nn.Module,
145
+ decoder: nn.Module,
146
+ quantizer: qt.BaseQuantizer,
147
+ frame_rate: int,
148
+ sample_rate: int,
149
+ channels: int,
150
+ causal: bool = False,
151
+ renormalize: bool = False):
152
+ super().__init__()
153
+ self.encoder = encoder
154
+ self.decoder = decoder
155
+ self.quantizer = quantizer
156
+ self.frame_rate = frame_rate
157
+ self.sample_rate = sample_rate
158
+ self.channels = channels
159
+ self.renormalize = renormalize
160
+ self.causal = causal
161
+ if self.causal:
162
+ # we force disabling here to avoid handling linear overlap of segments
163
+ # as supported in original EnCodec codebase.
164
+ assert not self.renormalize, 'Causal model does not support renormalize'
165
+
166
+ @property
167
+ def total_codebooks(self):
168
+ """Total number of quantizer codebooks available."""
169
+ return self.quantizer.total_codebooks
170
+
171
+ @property
172
+ def num_codebooks(self):
173
+ """Active number of codebooks used by the quantizer."""
174
+ return self.quantizer.num_codebooks
175
+
176
+ def set_num_codebooks(self, n: int):
177
+ """Set the active number of codebooks used by the quantizer."""
178
+ self.quantizer.set_num_codebooks(n)
179
+
180
+ @property
181
+ def cardinality(self):
182
+ """Cardinality of each codebook."""
183
+ return self.quantizer.bins
184
+
185
+ def preprocess(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
186
+ scale: tp.Optional[torch.Tensor]
187
+ if self.renormalize:
188
+ mono = x.mean(dim=1, keepdim=True)
189
+ volume = mono.pow(2).mean(dim=2, keepdim=True).sqrt()
190
+ scale = 1e-8 + volume
191
+ x = x / scale
192
+ scale = scale.view(-1, 1)
193
+ else:
194
+ scale = None
195
+ return x, scale
196
+
197
+ def postprocess(self,
198
+ x: torch.Tensor,
199
+ scale: tp.Optional[torch.Tensor] = None) -> torch.Tensor:
200
+ if scale is not None:
201
+ assert self.renormalize
202
+ x = x * scale.view(-1, 1, 1)
203
+ return x
204
+
205
+ def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
206
+ assert x.dim() == 3
207
+ length = x.shape[-1]
208
+ x, scale = self.preprocess(x)
209
+
210
+ emb = self.encoder(x)
211
+ q_res = self.quantizer(emb, self.frame_rate)
212
+ out = self.decoder(q_res.x)
213
+
214
+ # remove extra padding added by the encoder and decoder
215
+ assert out.shape[-1] >= length, (out.shape[-1], length)
216
+ out = out[..., :length]
217
+
218
+ q_res.x = self.postprocess(out, scale)
219
+
220
+ return q_res
221
+
222
+ def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
223
+ """Encode the given input tensor to quantized representation along with scale parameter.
224
+
225
+ Args:
226
+ x (torch.Tensor): Float tensor of shape [B, C, T]
227
+
228
+ Returns:
229
+ codes, scale (tuple of torch.Tensor, torch.Tensor): Tuple composed of:
230
+ codes a float tensor of shape [B, K, T] with K the number of codebooks used and T the timestep.
231
+ scale a float tensor containing the scale for audio renormalizealization.
232
+ """
233
+ assert x.dim() == 3
234
+ x, scale = self.preprocess(x)
235
+ emb = self.encoder(x)
236
+ codes = self.quantizer.encode(emb)
237
+ return codes, scale
238
+
239
+ def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
240
+ """Decode the given codes to a reconstructed representation, using the scale to perform
241
+ audio denormalization if needed.
242
+
243
+ Args:
244
+ codes (torch.Tensor): Int tensor of shape [B, K, T]
245
+ scale (torch.Tensor, optional): Float tensor containing the scale value.
246
+
247
+ Returns:
248
+ out (torch.Tensor): Float tensor of shape [B, C, T], the reconstructed audio.
249
+ """
250
+ emb = self.decode_latent(codes)
251
+ out = self.decoder(emb)
252
+ out = self.postprocess(out, scale)
253
+ # out contains extra padding added by the encoder and decoder
254
+ return out
255
+
256
+ def decode_latent(self, codes: torch.Tensor):
257
+ """Decode from the discrete codes to continuous latent space."""
258
+ return self.quantizer.decode(codes)
259
+
260
+
261
+ class DAC(CompressionModel):
262
+ def __init__(self, model_type: str = "44khz"):
263
+ super().__init__()
264
+ try:
265
+ import dac.utils
266
+ except ImportError:
267
+ raise RuntimeError("Could not import dac, make sure it is installed, "
268
+ "please run `pip install descript-audio-codec`")
269
+ self.model = dac.utils.load_model(model_type=model_type)
270
+ self.n_quantizers = self.total_codebooks
271
+ self.model.eval()
272
+
273
+ def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
274
+ # We don't support training with this.
275
+ raise NotImplementedError("Forward and training with DAC not supported.")
276
+
277
+ def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
278
+ codes = self.model.encode(x, self.n_quantizers)[1]
279
+ return codes, None
280
+
281
+ def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
282
+ assert scale is None
283
+ z_q = self.decode_latent(codes)
284
+ return self.model.decode(z_q)
285
+
286
+ def decode_latent(self, codes: torch.Tensor):
287
+ """Decode from the discrete codes to continuous latent space."""
288
+ return self.model.quantizer.from_codes(codes)[0]
289
+
290
+ @property
291
+ def channels(self) -> int:
292
+ return 1
293
+
294
+ @property
295
+ def frame_rate(self) -> float:
296
+ return self.model.sample_rate / self.model.hop_length
297
+
298
+ @property
299
+ def sample_rate(self) -> int:
300
+ return self.model.sample_rate
301
+
302
+ @property
303
+ def cardinality(self) -> int:
304
+ return self.model.codebook_size
305
+
306
+ @property
307
+ def num_codebooks(self) -> int:
308
+ return self.n_quantizers
309
+
310
+ @property
311
+ def total_codebooks(self) -> int:
312
+ return self.model.n_codebooks
313
+
314
+ def set_num_codebooks(self, n: int):
315
+ """Set the active number of codebooks used by the quantizer.
316
+ """
317
+ assert n >= 1
318
+ assert n <= self.total_codebooks
319
+ self.n_quantizers = n
320
+
321
+
322
+ class HFEncodecCompressionModel(CompressionModel):
323
+ """Wrapper around HuggingFace Encodec.
324
+ """
325
+ def __init__(self, model: HFEncodecModel):
326
+ super().__init__()
327
+ self.model = model
328
+ bws = self.model.config.target_bandwidths
329
+ num_codebooks = [
330
+ bw * 1000 / (self.frame_rate * math.log2(self.cardinality))
331
+ for bw in bws
332
+ ]
333
+ deltas = [nc - int(nc) for nc in num_codebooks]
334
+ # Checking we didn't do some bad maths and we indeed have integers!
335
+ assert all(deltas) <= 1e-3, deltas
336
+ self.possible_num_codebooks = [int(nc) for nc in num_codebooks]
337
+ self.set_num_codebooks(max(self.possible_num_codebooks))
338
+
339
+ def forward(self, x: torch.Tensor) -> qt.QuantizedResult:
340
+ # We don't support training with this.
341
+ raise NotImplementedError("Forward and training with HF EncodecModel not supported.")
342
+
343
+ def encode(self, x: torch.Tensor) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
344
+ bandwidth_index = self.possible_num_codebooks.index(self.num_codebooks)
345
+ bandwidth = self.model.config.target_bandwidths[bandwidth_index]
346
+ res = self.model.encode(x, None, bandwidth)
347
+ assert len(res[0]) == 1
348
+ assert len(res[1]) == 1
349
+ return res[0][0], res[1][0]
350
+
351
+ def decode(self, codes: torch.Tensor, scale: tp.Optional[torch.Tensor] = None):
352
+ if scale is None:
353
+ scales = [None] # type: ignore
354
+ else:
355
+ scales = scale # type: ignore
356
+ res = self.model.decode(codes[None], scales)
357
+ return res[0]
358
+
359
+ def decode_latent(self, codes: torch.Tensor):
360
+ """Decode from the discrete codes to continuous latent space."""
361
+ return self.model.quantizer.decode(codes.transpose(0, 1))
362
+
363
+ @property
364
+ def channels(self) -> int:
365
+ return self.model.config.audio_channels
366
+
367
+ @property
368
+ def frame_rate(self) -> float:
369
+ hop_length = int(np.prod(self.model.config.upsampling_ratios))
370
+ return self.sample_rate / hop_length
371
+
372
+ @property
373
+ def sample_rate(self) -> int:
374
+ return self.model.config.sampling_rate
375
+
376
+ @property
377
+ def cardinality(self) -> int:
378
+ return self.model.config.codebook_size
379
+
380
+ @property
381
+ def num_codebooks(self) -> int:
382
+ return self._num_codebooks
383
+
384
+ @property
385
+ def total_codebooks(self) -> int:
386
+ return max(self.possible_num_codebooks)
387
+
388
+ def set_num_codebooks(self, n: int):
389
+ """Set the active number of codebooks used by the quantizer.
390
+ """
391
+ if n not in self.possible_num_codebooks:
392
+ raise ValueError(f"Allowed values for num codebooks: {self.possible_num_codebooks}")
393
+ self._num_codebooks = n
audiocraft/models/lm.py ADDED
@@ -0,0 +1,533 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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) -> torch.Tensor:
223
+ """Apply language model on sequence and conditions.
224
+ Given a tensor of sequence of shape [B, K, S] with K the number of codebooks and
225
+ S the sequence steps, return the logits with shape [B, card, K, S].
226
+
227
+ Args:
228
+ indices (torch.Tensor): Indices of the codes to model.
229
+ conditions (list of ConditioningAttributes): Conditions to use when modeling
230
+ the given codes. Note that when evaluating multiple time with the same conditioning
231
+ you should pre-compute those and pass them as `condition_tensors`.
232
+ condition_tensors (dict[str, ConditionType], optional): Pre-computed conditioning
233
+ tensors, see `conditions`.
234
+ Returns:
235
+ torch.Tensor: Logits.
236
+ """
237
+ B, K, S = sequence.shape
238
+ #assert K == self.num_codebooks, "Sequence shape must match the specified number of codebooks"
239
+ input_ = sum([self.emb[k](sequence[:, k]) for k in range(K)]) # [B, K, S] -> [B, K, S, dim] -(sum)> [B, S, dim]
240
+ if condition_tensors is None:
241
+ assert not self._is_streaming, "Conditions tensors should be precomputed when streaming."
242
+ # apply dropout modules
243
+ conditions = self.cfg_dropout(conditions)
244
+ conditions = self.att_dropout(conditions)
245
+ tokenized = self.condition_provider.tokenize(conditions)
246
+ # encode conditions and fuse, both have a streaming cache to not recompute when generating.
247
+ condition_tensors = self.condition_provider(tokenized)
248
+ else:
249
+ assert not conditions, "Shouldn't pass both conditions and condition_tensors."
250
+
251
+ # input_, cross_attention_input = self.fuser(input_, condition_tensors)
252
+ input_, in_attn_input, cross_attention_input = self.fuser(input_, condition_tensors)
253
+
254
+ # out = self.transformer(input_, cross_attention_src=cross_attention_input)
255
+ out = self.transformer(input_, in_attn_src=in_attn_input, cross_attention_src=cross_attention_input)
256
+ if self.out_norm:
257
+ out = self.out_norm(out)
258
+ logits = torch.stack([self.linears[k](out) for k in range(K)], dim=1) # [B, K, S, card]
259
+
260
+ # remove the prefix from the model outputs
261
+ if len(self.fuser.fuse2cond['prepend']) > 0:
262
+ logits = logits[:, :, -S:]
263
+
264
+ return logits # [B, K, S, card]
265
+
266
+ def compute_predictions(
267
+ self, codes: torch.Tensor,
268
+ conditions: tp.List[ConditioningAttributes],
269
+ condition_tensors: tp.Optional[ConditionTensors] = None) -> LMOutput:
270
+ """Given an input tensor of codes [B, K, T] and list of conditions, runs the model
271
+ forward using the specified codes interleaving pattern.
272
+
273
+ Args:
274
+ codes (torch.Tensor): Input codes of shape [B, K, T] with B the batch size,
275
+ K the number of codebooks and T the number of timesteps.
276
+ conditions (list of ConditioningAttributes): conditionings to use when modeling
277
+ the given codes. Note that when evaluating multiple time with the same conditioning
278
+ you should pre-compute those and pass them as `condition_tensors`.
279
+ condition_tensors (dict[str, ConditionType], optional): pre-computed conditioning
280
+ tensors, see `conditions`.
281
+ Returns:
282
+ LMOutput: Language model outputs
283
+ logits (torch.Tensor) of shape [B, K, T, card] corresponding to the provided codes,
284
+ i.e. the first item corresponds to logits to predict the first code, meaning that
285
+ no additional shifting of codes and logits is required.
286
+ mask (torch.Tensor) of shape [B, K, T], mask over valid and invalid positions.
287
+ Given the specified interleaving strategies, parts of the logits and codes should
288
+ not be considered as valid predictions because of invalid context.
289
+ """
290
+ B, K, T = codes.shape
291
+ codes = codes.contiguous()
292
+ # map codes [B, K, T] into pattern sequence [B, K, S] using special_token_id for masked tokens
293
+ pattern = self.pattern_provider.get_pattern(T)
294
+ sequence_codes, sequence_indexes, sequence_mask = pattern.build_pattern_sequence(
295
+ codes, self.special_token_id, keep_only_valid_steps=True
296
+ )
297
+ # apply model on pattern sequence
298
+ model = self if self._fsdp is None else self._fsdp
299
+ logits = model(sequence_codes, conditions, condition_tensors) # [B, K, S, card]
300
+ # map back the logits on pattern sequence to logits on original codes: [B, K, S, card] -> [B, K, T, card]
301
+ # and provide the corresponding mask over invalid positions of tokens
302
+ logits = logits.permute(0, 3, 1, 2) # [B, card, K, S]
303
+ # note: we use nans as special token to make it obvious if we feed unexpected logits
304
+ logits, logits_indexes, logits_mask = pattern.revert_pattern_logits(
305
+ logits, float('nan'), keep_only_valid_steps=True
306
+ )
307
+ logits = logits.permute(0, 2, 3, 1) # [B, K, T, card]
308
+ logits_mask = logits_mask[None, :, :].expand(B, -1, -1) # [K, T] -> [B, K, T]
309
+ return LMOutput(logits, logits_mask)
310
+
311
+ def _sample_next_token(self,
312
+ sequence: torch.Tensor,
313
+ cfg_conditions: CFGConditions,
314
+ unconditional_state: State,
315
+ use_sampling: bool = False,
316
+ temp: float = 1.0,
317
+ top_k: int = 0,
318
+ top_p: float = 0.0,
319
+ cfg_coef: tp.Optional[float] = None) -> torch.Tensor:
320
+ """Sample next token from the model given a sequence and a set of conditions. The model supports
321
+ multiple sampling strategies (greedy sampling, softmax, top-k, top-p...).
322
+
323
+ Args:
324
+ sequence (torch.Tensor): Current sequence of shape [B, K, S]
325
+ with K corresponding to the number of codebooks and S the number of sequence steps.
326
+ S = 1 in streaming mode, except for the first step that contains a bigger prompt.
327
+ condition_tensors (dict[str, ConditionType): Set of conditions. If CFG is used,
328
+ should be twice the batch size, being the concatenation of the conditions + null conditions.
329
+ use_sampling (bool): Whether to use a sampling strategy or not.
330
+ temp (float): Sampling temperature.
331
+ top_k (int): K for "top-k" sampling.
332
+ top_p (float): P for "top-p" sampling.
333
+ cfg_coef (float, optional): classifier free guidance coefficient
334
+ Returns:
335
+ next_token (torch.Tensor): Next token tensor of shape [B, K, 1].
336
+ """
337
+ B = sequence.shape[0]
338
+ cfg_coef = self.cfg_coef if cfg_coef is None else cfg_coef
339
+ model = self if self._fsdp is None else self._fsdp
340
+ if self.two_step_cfg and cfg_conditions != {}:
341
+ assert isinstance(cfg_conditions, tuple), type(cfg_conditions)
342
+ condition_tensors, null_condition_tensors = cfg_conditions
343
+ cond_logits = model(sequence, conditions=[], condition_tensors=condition_tensors)
344
+ state = self.get_streaming_state()
345
+ self.set_streaming_state(unconditional_state)
346
+ uncond_logits = model(sequence, conditions=[], condition_tensors=null_condition_tensors)
347
+ unconditional_state.update(self.get_streaming_state())
348
+ self.set_streaming_state(state)
349
+ logits = uncond_logits + (cond_logits - uncond_logits) * self.cfg_coef
350
+ else:
351
+ assert isinstance(cfg_conditions, dict)
352
+ condition_tensors = cfg_conditions
353
+ if condition_tensors:
354
+ # Preparing for CFG, predicting both conditional and unconditional logits.
355
+ sequence = torch.cat([sequence, sequence], dim=0)
356
+ all_logits = model(
357
+ sequence,
358
+ conditions=[], condition_tensors=condition_tensors)
359
+ if condition_tensors:
360
+ cond_logits, uncond_logits = all_logits.split(B, dim=0) # [B, K, T, card]
361
+ logits = uncond_logits + (cond_logits - uncond_logits) * cfg_coef
362
+ else:
363
+ logits = all_logits
364
+
365
+ logits = logits.permute(0, 1, 3, 2) # [B, K, card, T]
366
+ logits = logits[..., -1] # [B x K x card]
367
+
368
+ # Apply softmax for sampling if temp > 0. Else, do greedy sampling to avoid zero division error.
369
+ if use_sampling and temp > 0.0:
370
+ probs = torch.softmax(logits / temp, dim=-1)
371
+ if top_p > 0.0:
372
+ next_token = utils.sample_top_p(probs, p=top_p)
373
+ elif top_k > 0:
374
+ next_token = utils.sample_top_k(probs, k=top_k)
375
+ else:
376
+ next_token = utils.multinomial(probs, num_samples=1)
377
+ else:
378
+ next_token = torch.argmax(logits, dim=-1, keepdim=True)
379
+
380
+ return next_token
381
+
382
+ @torch.no_grad()
383
+ def generate(self,
384
+ prompt: tp.Optional[torch.Tensor] = None,
385
+ conditions: tp.List[ConditioningAttributes] = [],
386
+ num_samples: tp.Optional[int] = None,
387
+ max_gen_len: int = 256,
388
+ use_sampling: bool = True,
389
+ temp: float = 1.0,
390
+ top_k: int = 250,
391
+ top_p: float = 0.0,
392
+ cfg_coef: tp.Optional[float] = None,
393
+ two_step_cfg: tp.Optional[bool] = None,
394
+ remove_prompts: bool = False,
395
+ check: bool = False,
396
+ callback: tp.Optional[tp.Callable[[int, int], None]] = None) -> torch.Tensor:
397
+ """Generate tokens sampling from the model given a prompt or unconditionally. Generation can
398
+ be perform in a greedy fashion or using sampling with top K and top P strategies.
399
+
400
+ Args:
401
+ prompt (torch.Tensor, optional): Prompt tokens of shape [B, K, T].
402
+ conditions_tensors (list of ConditioningAttributes, optional): List of conditions.
403
+ num_samples (int, optional): Number of samples to generate when no prompt and no conditions are given.
404
+ max_gen_len (int): Maximum generation length.
405
+ use_sampling (bool): Whether to use a sampling strategy or not.
406
+ temp (float): Sampling temperature.
407
+ top_k (int): K for "top-k" sampling.
408
+ top_p (float): P for "top-p" sampling.
409
+ cfg_coeff (float, optional): Classifier-free guidance coefficient.
410
+ two_step_cfg (bool, optional): Whether to perform classifier-free guidance with two steps generation.
411
+ remove_prompts (bool): Whether to remove prompts from generation or not.
412
+ check (bool): Whether to apply further checks on generated sequence.
413
+ callback (Callback, optional): Callback function to report generation progress.
414
+ Returns:
415
+ torch.Tensor: Generated tokens.
416
+ """
417
+ assert not self.training, "generation shouldn't be used in training mode."
418
+ first_param = next(iter(self.parameters()))
419
+ device = first_param.device
420
+
421
+ # Checking all input shapes are consistent.
422
+ possible_num_samples = []
423
+ if num_samples is not None:
424
+ possible_num_samples.append(num_samples)
425
+ elif prompt is not None:
426
+ possible_num_samples.append(prompt.shape[0])
427
+ elif conditions:
428
+ possible_num_samples.append(len(conditions))
429
+ else:
430
+ possible_num_samples.append(1)
431
+ assert [x == possible_num_samples[0] for x in possible_num_samples], "Inconsistent inputs shapes"
432
+ num_samples = possible_num_samples[0]
433
+
434
+ # below we create set of conditions: one conditional and one unconditional
435
+ # to do that we merge the regular condition together with the null condition
436
+ # we then do 1 forward pass instead of 2.
437
+ # the reason for that is two-fold:
438
+ # 1. it is about x2 faster than doing 2 forward passes
439
+ # 2. avoid the streaming API treating the 2 passes as part of different time steps
440
+ # We also support doing two different passes, in particular to ensure that
441
+ # the padding structure is exactly the same between train and test.
442
+ # With a batch size of 1, this can be slower though.
443
+ cfg_conditions: CFGConditions
444
+ two_step_cfg = self.two_step_cfg if two_step_cfg is None else two_step_cfg
445
+ if conditions:
446
+ null_conditions = ClassifierFreeGuidanceDropout(p=1.0)(conditions)
447
+ if two_step_cfg:
448
+ cfg_conditions = (
449
+ self.condition_provider(self.condition_provider.tokenize(conditions)),
450
+ self.condition_provider(self.condition_provider.tokenize(null_conditions)),
451
+ )
452
+ else:
453
+ conditions = conditions + null_conditions
454
+ tokenized = self.condition_provider.tokenize(conditions)
455
+ cfg_conditions = self.condition_provider(tokenized)
456
+ else:
457
+ cfg_conditions = {}
458
+
459
+ if prompt is None:
460
+ assert num_samples > 0
461
+ prompt = torch.zeros((num_samples, self.num_codebooks, 0), dtype=torch.long, device=device)
462
+
463
+ B, K, T = prompt.shape
464
+ start_offset = T
465
+ assert start_offset < max_gen_len
466
+
467
+ pattern = self.pattern_provider.get_pattern(max_gen_len)
468
+ # this token is used as default value for codes that are not generated yet
469
+ unknown_token = -1
470
+
471
+ # we generate codes up to the max_gen_len that will be mapped to the pattern sequence
472
+ gen_codes = torch.full((B, K, max_gen_len), unknown_token, dtype=torch.long, device=device)
473
+ # filling the gen_codes with the prompt if needed
474
+ gen_codes[..., :start_offset] = prompt
475
+ # create the gen_sequence with proper interleaving from the pattern: [B, K, S]
476
+ gen_sequence, indexes, mask = pattern.build_pattern_sequence(gen_codes, self.special_token_id)
477
+ # retrieve the start_offset in the sequence:
478
+ # it is the first sequence step that contains the `start_offset` timestep
479
+ start_offset_sequence = pattern.get_first_step_with_timesteps(start_offset)
480
+ assert start_offset_sequence is not None
481
+
482
+ with self.streaming():
483
+ unconditional_state = self.get_streaming_state()
484
+ prev_offset = 0
485
+ gen_sequence_len = gen_sequence.shape[-1] # gen_sequence shape is [B, K, S]
486
+ for offset in range(start_offset_sequence, gen_sequence_len):
487
+ # get current sequence (note that the streaming API is providing the caching over previous offsets)
488
+ curr_sequence = gen_sequence[..., prev_offset:offset]
489
+ curr_mask = mask[None, ..., prev_offset:offset].expand(B, -1, -1)
490
+ if check:
491
+ # check coherence between mask and sequence
492
+ assert (curr_sequence == torch.where(curr_mask, curr_sequence, self.special_token_id)).all()
493
+ # should never happen as gen_sequence is filled progressively
494
+ assert not (curr_sequence == unknown_token).any()
495
+ # sample next token from the model, next token shape is [B, K, 1]
496
+ next_token = self._sample_next_token(
497
+ curr_sequence, cfg_conditions, unconditional_state, use_sampling, temp, top_k, top_p,
498
+ cfg_coef=cfg_coef)
499
+ # ensure the tokens that should be masked are properly set to special_token_id
500
+ # as the model never output special_token_id
501
+ valid_mask = mask[..., offset:offset+1].expand(B, -1, -1)
502
+ next_token[~valid_mask] = self.special_token_id
503
+ # ensure we don't overwrite prompt tokens, we only write over unknown tokens
504
+ # (then mask tokens should be left as is as well, which is correct)
505
+ gen_sequence[..., offset:offset+1] = torch.where(
506
+ gen_sequence[..., offset:offset+1] == unknown_token,
507
+ next_token, gen_sequence[..., offset:offset+1]
508
+ )
509
+ prev_offset = offset
510
+ if callback is not None:
511
+ callback(1 + offset - start_offset_sequence, gen_sequence_len - start_offset_sequence)
512
+ unconditional_state.clear()
513
+
514
+ # ensure sequence has been entirely filled
515
+ assert not (gen_sequence == unknown_token).any()
516
+ # ensure gen_sequence pattern and mask are matching
517
+ # which means the gen_sequence is valid according to the pattern
518
+ assert (
519
+ gen_sequence == torch.where(mask[None, ...].expand(B, -1, -1), gen_sequence, self.special_token_id)
520
+ ).all()
521
+ # get back the codes, trimming the prompt if needed and cutting potentially incomplete timesteps
522
+ out_codes, out_indexes, out_mask = pattern.revert_pattern_sequence(gen_sequence, special_token=unknown_token)
523
+
524
+ # sanity checks over the returned codes and corresponding masks
525
+ assert (out_codes[..., :max_gen_len] != unknown_token).all()
526
+ assert (out_mask[..., :max_gen_len] == 1).all()
527
+
528
+ out_start_offset = start_offset if remove_prompts else 0
529
+ out_codes = out_codes[..., out_start_offset:max_gen_len]
530
+
531
+ # ensure the returned codes are all valid
532
+ assert (out_codes >= 0).all() and (out_codes <= self.card).all()
533
+ return out_codes
audiocraft/models/loaders.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ from . import builders
31
+ from .encodec import CompressionModel
32
+
33
+
34
+ def get_audiocraft_cache_dir() -> tp.Optional[str]:
35
+ return os.environ.get('AUDIOCRAFT_CACHE_DIR', None)
36
+
37
+
38
+ def _get_state_dict(
39
+ file_or_url_or_id: tp.Union[Path, str],
40
+ filename: tp.Optional[str] = None,
41
+ device='cpu',
42
+ cache_dir: tp.Optional[str] = None,
43
+ ):
44
+ if cache_dir is None:
45
+ cache_dir = get_audiocraft_cache_dir()
46
+ # Return the state dict either from a file or url
47
+ file_or_url_or_id = str(file_or_url_or_id)
48
+ assert isinstance(file_or_url_or_id, str)
49
+
50
+ if os.path.isfile(file_or_url_or_id):
51
+ return torch.load(file_or_url_or_id, map_location=device)
52
+
53
+ if os.path.isdir(file_or_url_or_id):
54
+ file = f"{file_or_url_or_id}/{filename}"
55
+ return torch.load(file, map_location=device)
56
+
57
+ elif file_or_url_or_id.startswith('https://'):
58
+ return torch.hub.load_state_dict_from_url(file_or_url_or_id, map_location=device, check_hash=True)
59
+
60
+ else:
61
+ assert filename is not None, "filename needs to be defined if using HF checkpoints"
62
+
63
+ file = hf_hub_download(repo_id=file_or_url_or_id, filename=filename, cache_dir=cache_dir)
64
+ return torch.load(file, map_location=device)
65
+
66
+
67
+ def load_compression_model_ckpt(file_or_url_or_id: tp.Union[Path, str], cache_dir: tp.Optional[str] = None):
68
+ return _get_state_dict(file_or_url_or_id, filename="compression_state_dict.bin", cache_dir=cache_dir)
69
+
70
+
71
+ def load_compression_model(file_or_url_or_id: tp.Union[Path, str], device='cpu', cache_dir: tp.Optional[str] = None):
72
+ pkg = load_compression_model_ckpt(file_or_url_or_id, cache_dir=cache_dir)
73
+ if 'pretrained' in pkg:
74
+ return CompressionModel.get_pretrained(pkg['pretrained'], device=device)
75
+ cfg = OmegaConf.create(pkg['xp.cfg'])
76
+ cfg.device = str(device)
77
+ model = builders.get_compression_model(cfg)
78
+ model.load_state_dict(pkg['best_state'])
79
+ model.eval()
80
+ return model
81
+
82
+
83
+ def load_lm_model_ckpt(file_or_url_or_id: tp.Union[Path, str], cache_dir: tp.Optional[str] = None):
84
+ return _get_state_dict(file_or_url_or_id, filename="state_dict.bin", cache_dir=cache_dir)
85
+
86
+
87
+ def _delete_param(cfg: DictConfig, full_name: str):
88
+ parts = full_name.split('.')
89
+ for part in parts[:-1]:
90
+ if part in cfg:
91
+ cfg = cfg[part]
92
+ else:
93
+ return
94
+ OmegaConf.set_struct(cfg, False)
95
+ if parts[-1] in cfg:
96
+ del cfg[parts[-1]]
97
+ OmegaConf.set_struct(cfg, True)
98
+
99
+
100
+ def load_lm_model(file_or_url_or_id: tp.Union[Path, str], device='cpu', cache_dir: tp.Optional[str] = None):
101
+ pkg = load_lm_model_ckpt(file_or_url_or_id, cache_dir=cache_dir)
102
+ cfg = OmegaConf.create(pkg['xp.cfg'])
103
+ cfg.device = str(device)
104
+ if cfg.device == 'cpu':
105
+ cfg.dtype = 'float32'
106
+ else:
107
+ cfg.dtype = 'float16'
108
+ _delete_param(cfg, 'conditioners.self_wav.chroma_stem.cache_path')
109
+ _delete_param(cfg, 'conditioners.args.merge_text_conditions_p')
110
+ _delete_param(cfg, 'conditioners.args.drop_desc_p')
111
+ model = builders.get_lm_model(cfg)
112
+ model.load_state_dict(pkg['best_state'])
113
+ model.eval()
114
+ model.cfg = cfg
115
+ return model
116
+
117
+
118
+ def load_mbd_ckpt(file_or_url_or_id: tp.Union[Path, str], cache_dir: tp.Optional[str] = None):
119
+ return _get_state_dict(file_or_url_or_id, filename="all_in_one.pt", cache_dir=cache_dir)
120
+
121
+
122
+ def load_diffusion_models(file_or_url_or_id: tp.Union[Path, str], device='cpu', cache_dir: tp.Optional[str] = None):
123
+ pkg = load_mbd_ckpt(file_or_url_or_id, cache_dir=cache_dir)
124
+ models = []
125
+ processors = []
126
+ cfgs = []
127
+ sample_rate = pkg['sample_rate']
128
+ for i in range(pkg['n_bands']):
129
+ cfg = pkg[i]['cfg']
130
+ model = builders.get_diffusion_model(cfg)
131
+ model_dict = pkg[i]['model_state']
132
+ model.load_state_dict(model_dict)
133
+ model.to(device)
134
+ processor = builders.get_processor(cfg=cfg.processor, sample_rate=sample_rate)
135
+ processor_dict = pkg[i]['processor_state']
136
+ processor.load_state_dict(processor_dict)
137
+ processor.to(device)
138
+ models.append(model)
139
+ processors.append(processor)
140
+ cfgs.append(cfg)
141
+ return models, processors, cfgs
audiocraft/models/musicgen.py ADDED
@@ -0,0 +1,583 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import numpy as np
17
+
18
+ from .encodec import CompressionModel
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, convert_txtchord2chroma, convert_txtchord2chroma_24
23
+ from ..modules.conditioners import ConditioningAttributes, WavCondition, ChordCondition, BeatCondition
24
+ from ..utils.autocast import TorchAutocast
25
+
26
+
27
+ MelodyList = tp.List[tp.Optional[torch.Tensor]]
28
+ MelodyType = tp.Union[torch.Tensor, MelodyList]
29
+
30
+
31
+ # backward compatible names mapping
32
+ _HF_MODEL_CHECKPOINTS_MAP = {
33
+ "small": "facebook/musicgen-small",
34
+ "medium": "facebook/musicgen-medium",
35
+ "large": "facebook/musicgen-large",
36
+ "melody": "facebook/musicgen-melody",
37
+ }
38
+
39
+
40
+ class MusicGen:
41
+ """MusicGen main model with convenient generation API.
42
+
43
+ Args:
44
+ name (str): name of the model.
45
+ compression_model (CompressionModel): Compression model
46
+ used to map audio to invertible discrete representations.
47
+ lm (LMModel): Language model over discrete representations.
48
+ max_duration (float, optional): maximum duration the model can produce,
49
+ otherwise, inferred from the training params.
50
+ """
51
+ def __init__(self, name: str, compression_model: CompressionModel, lm: LMModel,
52
+ max_duration: tp.Optional[float] = None):
53
+ self.name = name
54
+ self.compression_model = compression_model
55
+ self.lm = lm
56
+ if max_duration is None:
57
+ if hasattr(lm, 'cfg'):
58
+ max_duration = lm.cfg.dataset.segment_duration # type: ignore
59
+ else:
60
+ raise ValueError("You must provide max_duration when building directly MusicGen")
61
+ assert max_duration is not None
62
+ self.max_duration: float = max_duration
63
+ self.device = next(iter(lm.parameters())).device
64
+ self.generation_params: dict = {}
65
+ self.set_generation_params(duration=6, extend_stride=3) # 6 seconds by default
66
+ self._progress_callback: tp.Optional[tp.Callable[[int, int], None]] = None
67
+ if self.device.type == 'cpu':
68
+ self.autocast = TorchAutocast(enabled=False)
69
+ else:
70
+ self.autocast = TorchAutocast(
71
+ enabled=True, device_type=self.device.type, dtype=torch.float16)
72
+
73
+ @property
74
+ def frame_rate(self) -> float:
75
+ """Roughly the number of AR steps per seconds."""
76
+ return self.compression_model.frame_rate
77
+
78
+ @property
79
+ def sample_rate(self) -> int:
80
+ """Sample rate of the generated audio."""
81
+ return self.compression_model.sample_rate
82
+
83
+ @property
84
+ def audio_channels(self) -> int:
85
+ """Audio channels of the generated audio."""
86
+ return self.compression_model.channels
87
+
88
+ @staticmethod
89
+ def get_pretrained(name: str = 'facebook/musicgen-melody', device=None):
90
+ """Return pretrained model, we provide four models:
91
+ - facebook/musicgen-small (300M), text to music,
92
+ # see: https://huggingface.co/facebook/musicgen-small
93
+ - facebook/musicgen-medium (1.5B), text to music,
94
+ # see: https://huggingface.co/facebook/musicgen-medium
95
+ - facebook/musicgen-melody (1.5B) text to music and text+melody to music,
96
+ # see: https://huggingface.co/facebook/musicgen-melody
97
+ - facebook/musicgen-large (3.3B), text to music,
98
+ # see: https://huggingface.co/facebook/musicgen-large
99
+ """
100
+ if device is None:
101
+ if torch.cuda.device_count():
102
+ device = 'cuda'
103
+ else:
104
+ device = 'cpu'
105
+
106
+ if name == 'debug':
107
+ # used only for unit tests
108
+ compression_model = get_debug_compression_model(device)
109
+ lm = get_debug_lm_model(device)
110
+ return MusicGen(name, compression_model, lm, max_duration=30)
111
+
112
+ if name in _HF_MODEL_CHECKPOINTS_MAP:
113
+ warnings.warn(
114
+ "MusicGen pretrained model relying on deprecated checkpoint mapping. " +
115
+ f"Please use full pre-trained id instead: facebook/musicgen-{name}")
116
+ name = _HF_MODEL_CHECKPOINTS_MAP[name]
117
+
118
+ lm = load_lm_model(name, device=device)
119
+ compression_model = load_compression_model(name, device=device)
120
+ if 'self_wav' in lm.condition_provider.conditioners:
121
+ lm.condition_provider.conditioners['self_wav'].match_len_on_eval = True
122
+
123
+ return MusicGen(name, compression_model, lm)
124
+
125
+ def set_generation_params(self, use_sampling: bool = True, top_k: int = 250,
126
+ top_p: float = 0.0, temperature: float = 1.0,
127
+ duration: float = 30.0, cfg_coef: float = 3.0,
128
+ two_step_cfg: bool = False, extend_stride: float = 18):
129
+ """Set the generation parameters for MusicGen.
130
+
131
+ Args:
132
+ use_sampling (bool, optional): Use sampling if True, else do argmax decoding. Defaults to True.
133
+ top_k (int, optional): top_k used for sampling. Defaults to 250.
134
+ top_p (float, optional): top_p used for sampling, when set to 0 top_k is used. Defaults to 0.0.
135
+ temperature (float, optional): Softmax temperature parameter. Defaults to 1.0.
136
+ duration (float, optional): Duration of the generated waveform. Defaults to 30.0.
137
+ cfg_coef (float, optional): Coefficient used for classifier free guidance. Defaults to 3.0.
138
+ two_step_cfg (bool, optional): If True, performs 2 forward for Classifier Free Guidance,
139
+ instead of batching together the two. This has some impact on how things
140
+ are padded but seems to have little impact in practice.
141
+ extend_stride: when doing extended generation (i.e. more than 30 seconds), by how much
142
+ should we extend the audio each time. Larger values will mean less context is
143
+ preserved, and shorter value will require extra computations.
144
+ """
145
+ assert extend_stride < self.max_duration, "Cannot stride by more than max generation duration."
146
+ self.extend_stride = extend_stride
147
+ self.duration = duration
148
+ self.generation_params = {
149
+ 'use_sampling': use_sampling,
150
+ 'temp': temperature,
151
+ 'top_k': top_k,
152
+ 'top_p': top_p,
153
+ 'cfg_coef': cfg_coef,
154
+ 'two_step_cfg': two_step_cfg,
155
+ }
156
+
157
+ def set_custom_progress_callback(self, progress_callback: tp.Optional[tp.Callable[[int, int], None]] = None):
158
+ """Override the default progress callback."""
159
+ self._progress_callback = progress_callback
160
+
161
+ def generate_unconditional(self, num_samples: int, progress: bool = False,
162
+ return_tokens: bool = False) -> tp.Union[torch.Tensor,
163
+ tp.Tuple[torch.Tensor, torch.Tensor]]:
164
+ """Generate samples in an unconditional manner.
165
+
166
+ Args:
167
+ num_samples (int): Number of samples to be generated.
168
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
169
+ """
170
+ descriptions: tp.List[tp.Optional[str]] = [None] * num_samples
171
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, None)
172
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
173
+ if return_tokens:
174
+ return self.generate_audio(tokens), tokens
175
+ return self.generate_audio(tokens)
176
+
177
+ def generate(self, descriptions: tp.List[str], progress: bool = False, return_tokens: bool = False) \
178
+ -> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, torch.Tensor]]:
179
+ """Generate samples conditioned on text.
180
+
181
+ Args:
182
+ descriptions (list of str): A list of strings used as text conditioning.
183
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
184
+ """
185
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, None)
186
+ assert prompt_tokens is None
187
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
188
+ if return_tokens:
189
+ return self.generate_audio(tokens), tokens
190
+ return self.generate_audio(tokens)
191
+
192
+ def generate_with_chroma(self, descriptions: tp.List[str], melody_wavs: MelodyType,
193
+ melody_sample_rate: int, progress: bool = False,
194
+ return_tokens: bool = False) -> tp.Union[torch.Tensor,
195
+ tp.Tuple[torch.Tensor, torch.Tensor]]:
196
+ """Generate samples conditioned on text and melody.
197
+
198
+ Args:
199
+ descriptions (list of str): A list of strings used as text conditioning.
200
+ melody_wavs: (torch.Tensor or list of Tensor): A batch of waveforms used as
201
+ melody conditioning. Should have shape [B, C, T] with B matching the description length,
202
+ C=1 or 2. It can be [C, T] if there is a single description. It can also be
203
+ a list of [C, T] tensors.
204
+ melody_sample_rate: (int): Sample rate of the melody waveforms.
205
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
206
+ """
207
+ if isinstance(melody_wavs, torch.Tensor):
208
+ if melody_wavs.dim() == 2:
209
+ melody_wavs = melody_wavs[None]
210
+ if melody_wavs.dim() != 3:
211
+ raise ValueError("Melody wavs should have a shape [B, C, T].")
212
+ melody_wavs = list(melody_wavs)
213
+ else:
214
+ for melody in melody_wavs:
215
+ if melody is not None:
216
+ assert melody.dim() == 2, "One melody in the list has the wrong number of dims."
217
+
218
+ melody_wavs = [
219
+ convert_audio(wav, melody_sample_rate, self.sample_rate, self.audio_channels)
220
+ if wav is not None else None
221
+ for wav in melody_wavs]
222
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions=descriptions, prompt=None,
223
+ melody_wavs=melody_wavs)
224
+ assert prompt_tokens is None
225
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
226
+ if return_tokens:
227
+ return self.generate_audio(tokens), tokens
228
+ return self.generate_audio(tokens)
229
+
230
+ def generate_with_chords(self, descriptions: tp.List[str], melody_chords: tp.Optional[tp.Union[MelodyList,tp.List[str]]] = None,
231
+ bpms: tp.Optional[tp.Union[float,int,tp.List[float],tp.List[int]]] = [120.],
232
+ meters: tp.Optional[tp.Union[float,int,tp.List[float],tp.List[int]]] = [4.],
233
+ progress: bool = False, return_tokens: bool = False) -> tp.Union[torch.Tensor,
234
+ tp.Tuple[torch.Tensor, torch.Tensor]]:
235
+ """Generate samples conditioned on text and melody.
236
+
237
+ Args:
238
+ descriptions (list of str): A list of strings used as text conditioning.
239
+ melody_chords: (torch.Tensor or list of Tensor): A list of chords in chormagram or string type
240
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
241
+ """
242
+
243
+ if isinstance(melody_chords[0], str):
244
+ # check the bpm, meter length
245
+ if len(bpms) == 1:
246
+ bpms *= len(melody_chords)
247
+ if len(meters) == 1:
248
+ meters *= len(melody_chords)
249
+ assert len(bpms) == len(melody_chords), "bpm length is not equal to chord length"
250
+ assert len(meters) == len(melody_chords), "meter length is not equal to chord length"
251
+ # convert str to chromagram
252
+ melody_chromas = []
253
+ for melody_chord, bpm, meter in zip(melody_chords, bpms, meters):
254
+ melody_chroma = convert_txtchord2chroma(melody_chord, bpm, meter, self.duration).permute(1,0) # [C=12, T]
255
+ melody_chromas.append(melody_chroma)
256
+ melody_chromas = torch.stack(melody_chromas, dim=0)
257
+ assert melody_chromas.dim() == 3
258
+ melody_chords = list(melody_chromas)
259
+ else:
260
+ for melody in melody_chords:
261
+ if melody is not None:
262
+ assert melody.dim() == 2, "One melody in the list has the wrong number of dims."
263
+
264
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions=descriptions, prompt=None,
265
+ melody_chords=melody_chords, bpms=bpms)
266
+ assert prompt_tokens is None
267
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
268
+ if return_tokens:
269
+ return self.generate_audio(tokens), tokens
270
+ return self.generate_audio(tokens)
271
+
272
+ def generate_with_chords_and_beats(self, descriptions: tp.List[str], melody_chords: tp.Optional[tp.Union[MelodyList,tp.List[str]]] = None,
273
+ bpms: tp.Optional[tp.Union[float,int,tp.List[float],tp.List[int]]] = [120.],
274
+ meters: tp.Optional[tp.Union[float,int,tp.List[float],tp.List[int]]] = [4.],
275
+ progress: bool = False, return_tokens: bool = False) -> tp.Union[torch.Tensor,
276
+ tp.Tuple[torch.Tensor, torch.Tensor]]:
277
+ """Generate samples conditioned on text and melody.
278
+
279
+ Args:
280
+ descriptions (list of str): A list of strings used as text conditioning.
281
+ melody_chords: (torch.Tensor or list of Tensor): A list of chords in chormagram or string type
282
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
283
+ """
284
+
285
+ if isinstance(melody_chords[0], str):
286
+ # check the bpm, meter length
287
+ if len(bpms) == 1:
288
+ bpms *= len(melody_chords)
289
+ if len(meters) == 1:
290
+ meters *= len(melody_chords)
291
+ assert len(bpms) == len(melody_chords), "bpm length is not equal to chord length"
292
+ assert len(meters) == len(melody_chords), "meter length is not equal to chord length"
293
+ # convert str to chromagram
294
+ melody_chromas = []
295
+ for melody_chord, bpm, meter in zip(melody_chords, bpms, meters):
296
+ melody_chroma = convert_txtchord2chroma(melody_chord, bpm, meter, self.duration).permute(1,0) # [C=24, T]
297
+ melody_chromas.append(melody_chroma)
298
+ melody_chromas = torch.stack(melody_chromas, dim=0)
299
+ assert melody_chromas.dim() == 3
300
+ melody_chords = list(melody_chromas)
301
+ else:
302
+ for melody in melody_chords:
303
+ if melody is not None:
304
+ assert melody.dim() == 2, "One melody in the list has the wrong number of dims."
305
+
306
+ fs = self.sample_rate / 640
307
+ beats = []
308
+ for bpm, meter in zip(bpms, meters):
309
+ beat = np.zeros(int(fs * self.duration))
310
+ beat_gap = int(60 / bpm * fs)
311
+ beat[::beat_gap] = 1
312
+ bar = np.zeros(int(fs * self.duration))
313
+ bar[::beat_gap * meter] = 1
314
+ kernel = np.array([0.05, 0.1, 0.3, 0.9, 0.3, 0.1, 0.05])
315
+ beat = np.convolve(beat , kernel, 'same')
316
+ beat = beat + bar
317
+ beats.append(torch.tensor(beat).unsqueeze(0)) # [C, T]
318
+ beats = list(torch.stack(beats, dim=0)) # [B, C, T]
319
+
320
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions=descriptions, prompt=None,
321
+ melody_chords=melody_chords, beats=beats, bpms=bpms)
322
+ assert prompt_tokens is None
323
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
324
+ if return_tokens:
325
+ return self.generate_audio(tokens), tokens
326
+ return self.generate_audio(tokens)
327
+
328
+ def generate_for_eval(self, descriptions: tp.List[str], melody_chords: tp.List[torch.Tensor], beats: tp.List[torch.Tensor],
329
+ bpms: tp.List[float], progress: bool = False, return_tokens: bool = False) -> tp.Union[torch.Tensor,
330
+ tp.Tuple[torch.Tensor, torch.Tensor]]:
331
+
332
+ # assert melody_chords.dim() == 3
333
+ # assert beats.dim() == 3
334
+
335
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions=descriptions, prompt=None,
336
+ melody_chords=melody_chords, beats=beats, bpms=bpms)
337
+ assert prompt_tokens is None
338
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
339
+ if return_tokens:
340
+ return self.generate_audio(tokens), tokens
341
+ return self.generate_audio(tokens)
342
+
343
+
344
+ def generate_continuation(self, prompt: torch.Tensor, prompt_sample_rate: int,
345
+ descriptions: tp.Optional[tp.List[tp.Optional[str]]] = None, audio_channels=1,
346
+ progress: bool = False, return_tokens: bool = False) \
347
+ -> tp.Union[torch.Tensor, tp.Tuple[torch.Tensor, torch.Tensor]]:
348
+ """Generate samples conditioned on audio prompts.
349
+
350
+ Args:
351
+ prompt (torch.Tensor): A batch of waveforms used for continuation.
352
+ Prompt should be [B, C, T], or [C, T] if only one sample is generated.
353
+ prompt_sample_rate (int): Sampling rate of the given audio waveforms.
354
+ descriptions (list of str, optional): A list of strings used as text conditioning. Defaults to None.
355
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
356
+ """
357
+ if prompt.dim() == 2:
358
+ prompt = prompt[None]
359
+ if prompt.dim() != 3:
360
+ raise ValueError("prompt should have 3 dimensions: [B, C, T] (C = 1).")
361
+ prompt = convert_audio(prompt, prompt_sample_rate, self.sample_rate, audio_channels)
362
+ if descriptions is None:
363
+ descriptions = [None] * len(prompt)
364
+ attributes, prompt_tokens = self._prepare_tokens_and_attributes(descriptions, prompt)
365
+ assert prompt_tokens is not None
366
+ tokens = self._generate_tokens(attributes, prompt_tokens, progress)
367
+ if return_tokens:
368
+ return self.generate_audio(tokens), tokens
369
+ return self.generate_audio(tokens)
370
+
371
+ @torch.no_grad()
372
+ def _prepare_tokens_and_attributes(
373
+ self,
374
+ descriptions: tp.Sequence[tp.Optional[str]],
375
+ prompt: tp.Optional[torch.Tensor],
376
+ melody_wavs: tp.Optional[MelodyList] = None,
377
+ melody_chords: tp.Optional[MelodyList] = None,
378
+ beats : tp.Optional[MelodyList] = None,
379
+ bpms : tp.Optional[list] = None,
380
+ ) -> tp.Tuple[tp.List[ConditioningAttributes], tp.Optional[torch.Tensor]]:
381
+ """Prepare model inputs.
382
+
383
+ Args:
384
+ descriptions (list of str): A list of strings used as text conditioning.
385
+ prompt (torch.Tensor): A batch of waveforms used for continuation.
386
+ melody_wavs (torch.Tensor, optional): A batch of waveforms
387
+ used as melody conditioning. Defaults to None.
388
+ """
389
+ attributes = [
390
+ ConditioningAttributes(text={'description': description})
391
+ for description in descriptions]
392
+
393
+ if melody_wavs is None:
394
+ for attr in attributes:
395
+ attr.wav['self_wav'] = WavCondition(
396
+ torch.zeros((1, 1, 1), device=self.device),
397
+ torch.tensor([0], device=self.device),
398
+ sample_rate=[self.sample_rate],
399
+ path=[None])
400
+ else:
401
+ if 'self_wav' not in self.lm.condition_provider.conditioners:
402
+ raise RuntimeError("This model doesn't support melody conditioning. "
403
+ "Use the `melody` model.")
404
+ assert len(melody_wavs) == len(descriptions), \
405
+ f"number of melody wavs must match number of descriptions! " \
406
+ f"got melody len={len(melody_wavs)}, and descriptions len={len(descriptions)}"
407
+ for attr, melody in zip(attributes, melody_wavs):
408
+ if melody is None:
409
+ attr.wav['self_wav'] = WavCondition(
410
+ torch.zeros((1, 1, 1), device=self.device),
411
+ torch.tensor([0], device=self.device),
412
+ sample_rate=[self.sample_rate],
413
+ path=[None])
414
+ else:
415
+ attr.wav['self_wav'] = WavCondition(
416
+ melody[None].to(device=self.device),
417
+ torch.tensor([melody.shape[-1]], device=self.device),
418
+ sample_rate=[self.sample_rate],
419
+ path=[None],
420
+ )
421
+
422
+ if melody_chords is None:
423
+ for attr in attributes:
424
+ attr.chord['chord'] = ChordCondition(
425
+ torch.zeros((1, 12, 1), device=self.device),
426
+ torch.tensor([0], device=self.device),
427
+ bpm=[None],
428
+ path=[None])
429
+ else:
430
+ # if 'chord' not in self.lm.condition_provider.conditioners:
431
+ # raise RuntimeError("This model doesn't support chord conditioning. "
432
+ # "Use the `chord` model.")
433
+ assert len(melody_chords) == len(descriptions), \
434
+ f"number of melody_chords must match number of descriptions! " \
435
+ f"got melody len={len(melody_chords)}, and descriptions len={len(descriptions)}"
436
+ for attr, chord, bpm in zip(attributes, melody_chords, bpms):
437
+ if chord is None:
438
+ attr.chord['chord'] = ChordCondition(
439
+ torch.zeros((1, 1, 1), device=self.device),
440
+ torch.tensor([0], device=self.device),
441
+ bpm=[None],
442
+ path=[None])
443
+ else:
444
+ attr.chord['chord'] = ChordCondition(
445
+ chord[None].to(device=self.device),
446
+ torch.tensor([chord.shape[-1]], device=self.device),
447
+ bpm=[bpm],
448
+ path=[None],
449
+ )
450
+
451
+ if beats is None:
452
+ for attr in attributes:
453
+ attr.beat['beat'] = BeatCondition(
454
+ torch.zeros((1, 1, 1), device=self.device),
455
+ torch.tensor([0], device=self.device),
456
+ bpm=[None],
457
+ path=[None])
458
+ else:
459
+ # if 'beat' not in self.lm.condition_provider.conditioners:
460
+ # raise RuntimeError("This model doesn't support beat conditioning. "
461
+ # "Use the `beat` model.")
462
+ assert len(beats) == len(descriptions), \
463
+ f"number of beats must match number of descriptions! " \
464
+ f"got melody len={len(beats)}, and descriptions len={len(descriptions)}"
465
+ for attr, beat, bpm in zip(attributes, beats, bpms):
466
+ if beat is None:
467
+ attr.beat['beat'] = BeatCondition(
468
+ torch.zeros((1, 1, 1), device=self.device),
469
+ torch.tensor([0], device=self.device),
470
+ bpm=[None],
471
+ path=[None])
472
+ else:
473
+ attr.beat['beat'] = BeatCondition(
474
+ beat[None].to(device=self.device),
475
+ torch.tensor([beat.shape[-1]], device=self.device),
476
+ bpm=[bpm],
477
+ path=[None],
478
+ )
479
+
480
+ if prompt is not None:
481
+ if descriptions is not None:
482
+ assert len(descriptions) == len(prompt), "Prompt and nb. descriptions doesn't match"
483
+ prompt = prompt.to(self.device)
484
+ prompt_tokens, scale = self.compression_model.encode(prompt)
485
+ assert scale is None
486
+ else:
487
+ prompt_tokens = None
488
+ return attributes, prompt_tokens
489
+
490
+ def _generate_tokens(self, attributes: tp.List[ConditioningAttributes],
491
+ prompt_tokens: tp.Optional[torch.Tensor], progress: bool = False) -> torch.Tensor:
492
+ """Generate discrete audio tokens given audio prompt and/or conditions.
493
+
494
+ Args:
495
+ attributes (list of ConditioningAttributes): Conditions used for generation (text/melody).
496
+ prompt_tokens (torch.Tensor, optional): Audio prompt used for continuation.
497
+ progress (bool, optional): Flag to display progress of the generation process. Defaults to False.
498
+ Returns:
499
+ torch.Tensor: Generated audio, of shape [B, C, T], T is defined by the generation params.
500
+ """
501
+ total_gen_len = int(self.duration * self.frame_rate)
502
+ max_prompt_len = int(min(self.duration, self.max_duration) * self.frame_rate)
503
+ current_gen_offset: int = 0
504
+
505
+ def _progress_callback(generated_tokens: int, tokens_to_generate: int):
506
+ generated_tokens += current_gen_offset
507
+ if self._progress_callback is not None:
508
+ # Note that total_gen_len might be quite wrong depending on the
509
+ # codebook pattern used, but with delay it is almost accurate.
510
+ self._progress_callback(generated_tokens, total_gen_len)
511
+ else:
512
+ print(f'{generated_tokens: 6d} / {total_gen_len: 6d}', end='\r')
513
+
514
+ if prompt_tokens is not None:
515
+ assert max_prompt_len >= prompt_tokens.shape[-1], \
516
+ "Prompt is longer than audio to generate"
517
+
518
+ callback = None
519
+ if progress:
520
+ callback = _progress_callback
521
+
522
+ if self.duration <= self.max_duration:
523
+ # generate by sampling from LM, simple case.
524
+ with self.autocast:
525
+ gen_tokens = self.lm.generate(
526
+ prompt_tokens, attributes,
527
+ callback=callback, max_gen_len=total_gen_len, **self.generation_params)
528
+
529
+ else:
530
+ # now this gets a bit messier, we need to handle prompts,
531
+ # melody conditioning etc.
532
+ ref_wavs = [attr.wav['self_wav'] for attr in attributes]
533
+ all_tokens = []
534
+ if prompt_tokens is None:
535
+ prompt_length = 0
536
+ else:
537
+ all_tokens.append(prompt_tokens)
538
+ prompt_length = prompt_tokens.shape[-1]
539
+
540
+ stride_tokens = int(self.frame_rate * self.extend_stride)
541
+
542
+ while current_gen_offset + prompt_length < total_gen_len:
543
+ time_offset = current_gen_offset / self.frame_rate
544
+ chunk_duration = min(self.duration - time_offset, self.max_duration)
545
+ max_gen_len = int(chunk_duration * self.frame_rate)
546
+ for attr, ref_wav in zip(attributes, ref_wavs):
547
+ wav_length = ref_wav.length.item()
548
+ if wav_length == 0:
549
+ continue
550
+ # We will extend the wav periodically if it not long enough.
551
+ # we have to do it here rather than in conditioners.py as otherwise
552
+ # we wouldn't have the full wav.
553
+ initial_position = int(time_offset * self.sample_rate)
554
+ wav_target_length = int(self.max_duration * self.sample_rate)
555
+ positions = torch.arange(initial_position,
556
+ initial_position + wav_target_length, device=self.device)
557
+ attr.wav['self_wav'] = WavCondition(
558
+ ref_wav[0][..., positions % wav_length],
559
+ torch.full_like(ref_wav[1], wav_target_length),
560
+ [self.sample_rate] * ref_wav[0].size(0),
561
+ [None], [0.])
562
+ with self.autocast:
563
+ gen_tokens = self.lm.generate(
564
+ prompt_tokens, attributes,
565
+ callback=callback, max_gen_len=max_gen_len, **self.generation_params)
566
+ if prompt_tokens is None:
567
+ all_tokens.append(gen_tokens)
568
+ else:
569
+ all_tokens.append(gen_tokens[:, :, prompt_tokens.shape[-1]:])
570
+ prompt_tokens = gen_tokens[:, :, stride_tokens:]
571
+ prompt_length = prompt_tokens.shape[-1]
572
+ current_gen_offset += stride_tokens
573
+
574
+ gen_tokens = torch.cat(all_tokens, dim=-1)
575
+ return gen_tokens
576
+
577
+ def generate_audio(self, gen_tokens: torch.Tensor):
578
+ """Generate Audio from tokens"""
579
+ assert gen_tokens.dim() == 3
580
+ with torch.no_grad():
581
+ n_channel = gen_tokens.shape[1]
582
+ gen_audio = self.compression_model.decode(gen_tokens, None)
583
+ return gen_audio
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 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)
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
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
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
audiocraft/modules/codebooks_patterns.py ADDED
@@ -0,0 +1,542 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 S 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
+ assert self.layout[0] == []
53
+ self._validate_layout()
54
+ self._build_reverted_sequence_scatter_indexes = lru_cache(100)(self._build_reverted_sequence_scatter_indexes)
55
+ self._build_pattern_sequence_scatter_indexes = lru_cache(100)(self._build_pattern_sequence_scatter_indexes)
56
+ logger.info("New pattern, time steps: %d, sequence steps: %d", self.timesteps, len(self.layout))
57
+
58
+ def _validate_layout(self):
59
+ """Runs checks on the layout to ensure a valid pattern is defined.
60
+ A pattern is considered invalid if:
61
+ - Multiple timesteps for a same codebook are defined in the same sequence step
62
+ - The timesteps for a given codebook are not in ascending order as we advance in the sequence
63
+ (this would mean that we have future timesteps before past timesteps).
64
+ """
65
+ q_timesteps = {q: 0 for q in range(self.n_q)}
66
+ for s, seq_coords in enumerate(self.layout):
67
+ if len(seq_coords) > 0:
68
+ qs = set()
69
+ for coord in seq_coords:
70
+ qs.add(coord.q)
71
+ last_q_timestep = q_timesteps[coord.q]
72
+ assert coord.t >= last_q_timestep, \
73
+ f"Past timesteps are found in the sequence for codebook = {coord.q} at step {s}"
74
+ q_timesteps[coord.q] = coord.t
75
+ # each sequence step contains at max 1 coordinate per codebook
76
+ assert len(qs) == len(seq_coords), \
77
+ f"Multiple entries for a same codebook are found at step {s}"
78
+
79
+ @property
80
+ def num_sequence_steps(self):
81
+ return len(self.layout) - 1
82
+
83
+ @property
84
+ def max_delay(self):
85
+ max_t_in_seq_coords = 0
86
+ for seq_coords in self.layout[1:]:
87
+ for coords in seq_coords:
88
+ max_t_in_seq_coords = max(max_t_in_seq_coords, coords.t + 1)
89
+ return max_t_in_seq_coords - self.timesteps
90
+
91
+ @property
92
+ def valid_layout(self):
93
+ valid_step = len(self.layout) - self.max_delay
94
+ return self.layout[:valid_step]
95
+
96
+ def get_sequence_coords_with_timestep(self, t: int, q: tp.Optional[int] = None):
97
+ """Get codebook coordinates in the layout that corresponds to the specified timestep t
98
+ and optionally to the codebook q. Coordinates are returned as a tuple with the sequence step
99
+ and the actual codebook coordinates.
100
+ """
101
+ assert t <= self.timesteps, "provided timesteps is greater than the pattern's number of timesteps"
102
+ if q is not None:
103
+ assert q <= self.n_q, "provided number of codebooks is greater than the pattern's number of codebooks"
104
+ coords = []
105
+ for s, seq_codes in enumerate(self.layout):
106
+ for code in seq_codes:
107
+ if code.t == t and (q is None or code.q == q):
108
+ coords.append((s, code))
109
+ return coords
110
+
111
+ def get_steps_with_timestep(self, t: int, q: tp.Optional[int] = None) -> tp.List[int]:
112
+ return [step for step, coords in self.get_sequence_coords_with_timestep(t, q)]
113
+
114
+ def get_first_step_with_timesteps(self, t: int, q: tp.Optional[int] = None) -> tp.Optional[int]:
115
+ steps_with_timesteps = self.get_steps_with_timestep(t, q)
116
+ return steps_with_timesteps[0] if len(steps_with_timesteps) > 0 else None
117
+
118
+ def _build_pattern_sequence_scatter_indexes(self, timesteps: int, n_q: int, keep_only_valid_steps: bool,
119
+ device: tp.Union[torch.device, str] = 'cpu'):
120
+ """Build scatter indexes corresponding to the pattern, up to the provided sequence_steps.
121
+
122
+ Args:
123
+ timesteps (int): Maximum number of timesteps steps to consider.
124
+ keep_only_valid_steps (bool): Restrict the pattern layout to match only valid steps.
125
+ device (torch.device or str): Device for created tensors.
126
+ Returns:
127
+ indexes (torch.Tensor): Indexes corresponding to the sequence, of shape [K, S].
128
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes, of shape [K, S].
129
+ """
130
+ # assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
131
+ assert timesteps <= self.timesteps, "invalid number of timesteps used to build the sequence from the pattern"
132
+ # use the proper layout based on whether we limit ourselves to valid steps only or not,
133
+ # note that using the valid_layout will result in a truncated sequence up to the valid steps
134
+ ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
135
+ # single item indexing being super slow with pytorch vs. numpy, so we use numpy here
136
+ indexes = torch.zeros(n_q, len(ref_layout), dtype=torch.long).numpy()
137
+ mask = torch.zeros(n_q, len(ref_layout), dtype=torch.bool).numpy()
138
+ # fill indexes with last sequence step value that will correspond to our special token
139
+ # the last value is n_q * timesteps as we have flattened z and append special token as the last token
140
+ # which will correspond to the index: n_q * timesteps
141
+ indexes[:] = n_q * timesteps
142
+ # iterate over the pattern and fill scattered indexes and mask
143
+ for s, sequence_coords in enumerate(ref_layout):
144
+ for coords in sequence_coords:
145
+ if coords.t < timesteps:
146
+ indexes[coords.q, s] = coords.t + coords.q * timesteps
147
+ mask[coords.q, s] = 1
148
+ indexes = torch.from_numpy(indexes).to(device)
149
+ mask = torch.from_numpy(mask).to(device)
150
+ return indexes, mask
151
+
152
+ def build_pattern_sequence(self, z: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
153
+ """Build sequence corresponding to the pattern from the input tensor z.
154
+ The sequence is built using up to sequence_steps if specified, and non-pattern
155
+ coordinates are filled with the special token.
156
+
157
+ Args:
158
+ z (torch.Tensor): Input tensor of multi-codebooks sequence, of shape [B, K, T].
159
+ special_token (int): Special token used to fill non-pattern coordinates in the new sequence.
160
+ keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
161
+ Steps that are beyond valid steps will be replaced by the special_token in that case.
162
+ Returns:
163
+ values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, S] with S
164
+ corresponding either to the sequence_steps if provided, otherwise to the length of the pattern.
165
+ indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, S].
166
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, S].
167
+ """
168
+ B, K, T = z.shape
169
+ indexes, mask = self._build_pattern_sequence_scatter_indexes(
170
+ T, K, keep_only_valid_steps=keep_only_valid_steps, device=str(z.device)
171
+ )
172
+ z = z.view(B, -1)
173
+ # we append the special token as the last index of our flattened z tensor
174
+ z = torch.cat([z, torch.zeros_like(z[:, :1]) + special_token], dim=1)
175
+ values = z[:, indexes.view(-1)]
176
+ values = values.view(B, K, indexes.shape[-1])
177
+ return values, indexes, mask
178
+
179
+ def _build_reverted_sequence_scatter_indexes(self, sequence_steps: int, n_q: int,
180
+ keep_only_valid_steps: bool = False,
181
+ is_model_output: bool = False,
182
+ device: tp.Union[torch.device, str] = 'cpu'):
183
+ """Builds scatter indexes required to retrieve the original multi-codebook sequence
184
+ from interleaving pattern.
185
+
186
+ Args:
187
+ sequence_steps (int): Sequence steps.
188
+ n_q (int): Number of codebooks.
189
+ keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
190
+ Steps that are beyond valid steps will be replaced by the special_token in that case.
191
+ is_model_output (bool): Whether to keep the sequence item corresponding to initial special token or not.
192
+ device (torch.device or str): Device for created tensors.
193
+ Returns:
194
+ indexes (torch.Tensor): Indexes for reconstructing the output, of shape [K, T].
195
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
196
+ """
197
+ ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
198
+ # TODO(jade): Do we want to further truncate to only valid timesteps here as well?
199
+ timesteps = self.timesteps
200
+ #assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
201
+ assert sequence_steps <= len(ref_layout), \
202
+ f"sequence to revert is longer than the defined pattern: {sequence_steps} > {len(ref_layout)}"
203
+
204
+ # ensure we take the appropriate indexes to keep the model output from the first special token as well
205
+ if is_model_output:
206
+ ref_layout = ref_layout[1:]
207
+
208
+ # single item indexing being super slow with pytorch vs. numpy, so we use numpy here
209
+ indexes = torch.zeros(n_q, timesteps, dtype=torch.long).numpy()
210
+ mask = torch.zeros(n_q, timesteps, dtype=torch.bool).numpy()
211
+ # fill indexes with last sequence step value that will correspond to our special token
212
+ indexes[:] = n_q * sequence_steps
213
+ for s, sequence_codes in enumerate(ref_layout):
214
+ if s < sequence_steps:
215
+ for code in sequence_codes:
216
+ if code.t < timesteps:
217
+ indexes[code.q, code.t] = s + code.q * sequence_steps
218
+ mask[code.q, code.t] = 1
219
+ indexes = torch.from_numpy(indexes).to(device)
220
+ mask = torch.from_numpy(mask).to(device)
221
+ return indexes, mask
222
+
223
+ def revert_pattern_sequence(self, s: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
224
+ """Revert a sequence built from the pattern back to the original multi-codebook sequence without interleaving.
225
+ The sequence is reverted using up to timesteps if specified, and non-pattern coordinates
226
+ are filled with the special token.
227
+
228
+ Args:
229
+ s (torch.Tensor): Interleaved sequence tensor obtained from the pattern, of shape [B, K, S].
230
+ special_token (int or float): Special token used to fill non-pattern coordinates in the new sequence.
231
+ Returns:
232
+ values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, T] with T
233
+ corresponding either to the timesteps if provided, or the total timesteps in pattern otherwise.
234
+ indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, T].
235
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
236
+ """
237
+ B, K, S = s.shape
238
+ indexes, mask = self._build_reverted_sequence_scatter_indexes(
239
+ S, K, keep_only_valid_steps, is_model_output=False, device=str(s.device)
240
+ )
241
+ s = s.view(B, -1)
242
+ # we append the special token as the last index of our flattened z tensor
243
+ s = torch.cat([s, torch.zeros_like(s[:, :1]) + special_token], dim=1)
244
+ values = s[:, indexes.view(-1)]
245
+ values = values.view(B, K, indexes.shape[-1])
246
+ return values, indexes, mask
247
+
248
+ def revert_pattern_logits(self, logits: torch.Tensor, special_token: float, keep_only_valid_steps: bool = False):
249
+ """Revert model logits obtained on a sequence built from the pattern
250
+ back to a tensor matching the original sequence.
251
+
252
+ This method is similar to ``revert_pattern_sequence`` with the following specificities:
253
+ 1. It is designed to work with the extra cardinality dimension
254
+ 2. We return the logits for the first sequence item that matches the special_token and
255
+ which matching target in the original sequence is the first item of the sequence,
256
+ while we skip the last logits as there is no matching target
257
+ """
258
+ B, card, K, S = logits.shape
259
+ indexes, mask = self._build_reverted_sequence_scatter_indexes(
260
+ S, K, keep_only_valid_steps, is_model_output=True, device=logits.device
261
+ )
262
+ logits = logits.reshape(B, card, -1)
263
+ # we append the special token as the last index of our flattened z tensor
264
+ logits = torch.cat([logits, torch.zeros_like(logits[:, :, :1]) + special_token], dim=-1) # [B, card, K x S]
265
+ values = logits[:, :, indexes.view(-1)]
266
+ values = values.view(B, card, K, indexes.shape[-1])
267
+ return values, indexes, mask
268
+
269
+
270
+ class CodebooksPatternProvider(ABC):
271
+ """Abstraction around providing pattern for interleaving codebooks.
272
+
273
+ The CodebooksPatternProvider abstraction allows to implement various strategies to
274
+ define interleaving pattern of sequences composed of multiple codebooks. For a given
275
+ number of codebooks `n_q`, the pattern provider can generate a specified pattern
276
+ corresponding to a sequence of `T` timesteps with `n_q` parallel codebooks. This pattern
277
+ can be used to construct a new sequence from the original codes respecting the specified
278
+ pattern. The pattern is defined as a list of list of code coordinates, code coordinate
279
+ being a tuple with the original timestep and codebook to build the new sequence.
280
+ Note that all patterns must start with an empty list that is then used to insert a first
281
+ sequence step of special tokens in the newly generated sequence.
282
+
283
+ Args:
284
+ n_q (int): number of codebooks.
285
+ cached (bool): if True, patterns for a given length are cached. In general
286
+ that should be true for efficiency reason to avoid synchronization points.
287
+ """
288
+ def __init__(self, n_q: int, cached: bool = True, stereo: bool = False):
289
+ assert n_q > 0
290
+ if stereo:
291
+ self.n_q = n_q // 2
292
+ else:
293
+ self.n_q = n_q
294
+ self.get_pattern = lru_cache(100)(self.get_pattern) # type: ignore
295
+
296
+ @abstractmethod
297
+ def get_pattern(self, timesteps: int) -> Pattern:
298
+ """Builds pattern with specific interleaving between codebooks.
299
+
300
+ Args:
301
+ timesteps (int): Total number of timesteps.
302
+ """
303
+ raise NotImplementedError()
304
+
305
+
306
+ class DelayedPatternProvider(CodebooksPatternProvider):
307
+ """Provider for delayed pattern across delayed codebooks.
308
+ Codebooks are delayed in the sequence and sequence steps will contain codebooks
309
+ from different timesteps.
310
+
311
+ Example:
312
+ Taking timesteps=4 and n_q=3, delays=None, the multi-codebook sequence:
313
+ [[1, 2, 3, 4],
314
+ [1, 2, 3, 4],
315
+ [1, 2, 3, 4]]
316
+ The resulting sequence obtained from the returned pattern is:
317
+ [[S, 1, 2, 3, 4],
318
+ [S, S, 1, 2, 3],
319
+ [S, S, S, 1, 2]]
320
+ (with S being a special token)
321
+
322
+ Args:
323
+ n_q (int): Number of codebooks.
324
+ delays (list of int, optional): Delay for each of the codebooks.
325
+ If delays not defined, each codebook is delayed by 1 compared to the previous one.
326
+ flatten_first (int): Flatten the first N timesteps.
327
+ empty_initial (int): Prepend with N empty list of coordinates.
328
+ """
329
+ def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None,
330
+ flatten_first: int = 0, empty_initial: int = 0):
331
+ super().__init__(n_q)
332
+ if delays is None:
333
+ delays = list(range(n_q))
334
+ self.delays = delays
335
+ self.flatten_first = flatten_first
336
+ self.empty_initial = empty_initial
337
+ # assert len(self.delays) == self.n_q
338
+ assert sorted(self.delays) == self.delays
339
+
340
+ def get_pattern(self, timesteps: int) -> Pattern:
341
+ out: PatternLayout = [[]]
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
+ """
367
+ def __init__(self, n_q: int):
368
+ super().__init__(n_q, [0] * n_q)
369
+
370
+
371
+ class UnrolledPatternProvider(CodebooksPatternProvider):
372
+ """Provider for unrolling codebooks pattern.
373
+ This pattern provider enables to represent the codebook flattened completely or only to some extend
374
+ while also specifying a given delay between the flattened codebooks representation, allowing to
375
+ unroll the codebooks in the sequence.
376
+
377
+ Example:
378
+ 1. Flattening of the codebooks.
379
+ By default, the pattern provider will fully flatten the codebooks such as flattening=range(n_q),
380
+ taking n_q = 3 and timesteps = 4:
381
+ [[1, 2, 3, 4],
382
+ [1, 2, 3, 4],
383
+ [1, 2, 3, 4]]
384
+ will result into:
385
+ [[S, S, 1, S, S, 2, S, S, 3, S, S, 4],
386
+ [S, 1, S, S, 2, S, S, 3, S, S, 4, S],
387
+ [1, S, S, 2, S, S, 3, S, S, 4, S, S]]
388
+ 2. Partial flattening of the codebooks. The ``flattening`` parameter allows to specify the inner step
389
+ for each of the codebook, allowing to define which codebook to flatten (or keep in parallel), for example
390
+ taking n_q = 3, timesteps = 4 and flattening = [0, 1, 1]:
391
+ [[1, 2, 3, 4],
392
+ [1, 2, 3, 4],
393
+ [1, 2, 3, 4]]
394
+ will result into:
395
+ [[S, 1, S, S, 2, S, S, 3, S, S, 4, S],
396
+ [S, 1, S, S, 2, S, S, 3, S, S, 4, S],
397
+ [1, S, S, 2, S, S, 3, S, S, 4, S, S]]
398
+ 3. Flattening with delay. The ``delay`` parameter allows to further unroll the sequence of codebooks
399
+ allowing to specify the delay per codebook. Note that the delay between codebooks flattened to the
400
+ same inner timestep should be coherent. For example, taking n_q = 3, timesteps = 4, flattening = [0, 1, 1]
401
+ and delays = [0, 3, 3]:
402
+ [[1, 2, 3, 4],
403
+ [1, 2, 3, 4],
404
+ [1, 2, 3, 4]]
405
+ will result into:
406
+ [[S, S, S, 1, S, 2, S, 3, S, 4],
407
+ [S, S, S, 1, S, 2, S, 3, S, 4],
408
+ [1, 2, 3, S, 4, S, 5, S, 6, S]]
409
+
410
+ Args:
411
+ n_q (int): Number of codebooks.
412
+ flattening (list of int, optional): Flattening schema over the codebooks. If not defined,
413
+ the codebooks will be flattened to 1 codebook per step, meaning that the sequence will
414
+ have n_q extra steps for each timestep.
415
+ delays (list of int, optional): Delay for each of the codebooks. If not defined,
416
+ no delay is added and therefore will default to [0] * ``n_q``.
417
+ Note that two codebooks that will be flattened to the same inner step
418
+ should have the same delay, otherwise the pattern is considered as invalid.
419
+ """
420
+ FlattenedCodebook = namedtuple('FlattenedCodebook', ['codebooks', 'delay'])
421
+
422
+ def __init__(self, n_q: int, flattening: tp.Optional[tp.List[int]] = None,
423
+ delays: tp.Optional[tp.List[int]] = None):
424
+ super().__init__(n_q)
425
+ if flattening is None:
426
+ flattening = list(range(n_q))
427
+ if delays is None:
428
+ delays = [0] * n_q
429
+ assert len(flattening) == n_q
430
+ assert len(delays) == n_q
431
+ assert sorted(flattening) == flattening
432
+ assert sorted(delays) == delays
433
+ self._flattened_codebooks = self._build_flattened_codebooks(delays, flattening)
434
+ self.max_delay = max(delays)
435
+
436
+ def _build_flattened_codebooks(self, delays: tp.List[int], flattening: tp.List[int]):
437
+ """Build a flattened codebooks representation as a dictionary of inner step
438
+ and the actual codebook indices corresponding to the flattened codebook. For convenience, we
439
+ also store the delay associated to the flattened codebook to avoid maintaining an extra mapping.
440
+ """
441
+ flattened_codebooks: dict = {}
442
+ for q, (inner_step, delay) in enumerate(zip(flattening, delays)):
443
+ if inner_step not in flattened_codebooks:
444
+ flat_codebook = UnrolledPatternProvider.FlattenedCodebook(codebooks=[q], delay=delay)
445
+ else:
446
+ flat_codebook = flattened_codebooks[inner_step]
447
+ assert flat_codebook.delay == delay, (
448
+ "Delay and flattening between codebooks is inconsistent: ",
449
+ "two codebooks flattened to the same position should have the same delay."
450
+ )
451
+ flat_codebook.codebooks.append(q)
452
+ flattened_codebooks[inner_step] = flat_codebook
453
+ return flattened_codebooks
454
+
455
+ @property
456
+ def _num_inner_steps(self):
457
+ """Number of inner steps to unroll between timesteps in order to flatten the codebooks.
458
+ """
459
+ return max([inner_step for inner_step in self._flattened_codebooks.keys()]) + 1
460
+
461
+ def num_virtual_steps(self, timesteps: int) -> int:
462
+ return timesteps * self._num_inner_steps + 1
463
+
464
+ def get_pattern(self, timesteps: int) -> Pattern:
465
+ """Builds pattern for delay across codebooks.
466
+
467
+ Args:
468
+ timesteps (int): Total number of timesteps.
469
+ """
470
+ # the PatternLayout is built as a tuple of sequence position and list of coordinates
471
+ # so that it can be reordered properly given the required delay between codebooks of given timesteps
472
+ indexed_out: list = [(-1, [])]
473
+ max_timesteps = timesteps + self.max_delay
474
+ for t in range(max_timesteps):
475
+ # for each timestep, we unroll the flattened codebooks,
476
+ # emitting the sequence step with the corresponding delay
477
+ for step in range(self._num_inner_steps):
478
+ if step in self._flattened_codebooks:
479
+ # we have codebooks at this virtual step to emit
480
+ step_codebooks = self._flattened_codebooks[step]
481
+ t_for_q = t + step_codebooks.delay
482
+ coords = [LayoutCoord(t, q) for q in step_codebooks.codebooks]
483
+ if t_for_q < max_timesteps and t < max_timesteps:
484
+ indexed_out.append((t_for_q, coords))
485
+ else:
486
+ # there is no codebook in this virtual step so we emit an empty list
487
+ indexed_out.append((t, []))
488
+ out = [coords for _, coords in sorted(indexed_out)]
489
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
490
+
491
+
492
+ class VALLEPattern(CodebooksPatternProvider):
493
+ """Almost VALL-E style pattern.
494
+ We further allow some delays for the codebooks other than the first one.
495
+
496
+ Args:
497
+ n_q (int): Number of codebooks.
498
+ delays (list of int, optional): Delay for each of the codebooks.
499
+ If delays not defined, each codebook is delayed by 1 compared to the previous one.
500
+ """
501
+ def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None):
502
+ super().__init__(n_q)
503
+ if delays is None:
504
+ delays = [0] * (n_q - 1)
505
+ self.delays = delays
506
+ assert len(self.delays) == self.n_q - 1
507
+ assert sorted(self.delays) == self.delays
508
+
509
+ def get_pattern(self, timesteps: int) -> Pattern:
510
+ out: PatternLayout = [[]]
511
+ for t in range(timesteps):
512
+ out.append([LayoutCoord(t, 0)])
513
+ max_delay = max(self.delays)
514
+ for t in range(timesteps + max_delay):
515
+ v = []
516
+ for q, delay in enumerate(self.delays):
517
+ t_for_q = t - delay
518
+ if t_for_q >= 0:
519
+ v.append(LayoutCoord(t_for_q, q + 1))
520
+ out.append(v)
521
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
522
+
523
+
524
+ class MusicLMPattern(CodebooksPatternProvider):
525
+ """Almost MusicLM style pattern. This is equivalent to full flattening
526
+ but in a different order.
527
+
528
+ Args:
529
+ n_q (int): Number of codebooks.
530
+ group_by (int): Number of codebooks to group together.
531
+ """
532
+ def __init__(self, n_q: int, group_by: int = 2):
533
+ super().__init__(n_q)
534
+ self.group_by = group_by
535
+
536
+ def get_pattern(self, timesteps: int) -> Pattern:
537
+ out: PatternLayout = [[]]
538
+ for offset in range(0, self.n_q, self.group_by):
539
+ for t in range(timesteps):
540
+ for q in range(offset, offset + self.group_by):
541
+ out.append([LayoutCoord(t, q)])
542
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
audiocraft/modules/conditioners.py ADDED
@@ -0,0 +1,1678 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 pretty_midi
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 ChordCondition(tp.NamedTuple):
55
+ chord: torch.Tensor
56
+ length: torch.Tensor
57
+ bpm: tp.List[tp.Optional[float]] = []
58
+ path: tp.List[tp.Optional[str]] = []
59
+ seek_frame: tp.List[tp.Optional[float]] = []
60
+
61
+
62
+ class BeatCondition(tp.NamedTuple):
63
+ beat: torch.Tensor
64
+ length: torch.Tensor
65
+ bpm: tp.List[tp.Optional[float]] = []
66
+ path: tp.List[tp.Optional[str]] = []
67
+ seek_frame: tp.List[tp.Optional[float]] = []
68
+
69
+
70
+ class JointEmbedCondition(tp.NamedTuple):
71
+ wav: torch.Tensor
72
+ text: tp.List[tp.Optional[str]]
73
+ length: torch.Tensor
74
+ sample_rate: tp.List[int]
75
+ path: tp.List[tp.Optional[str]] = []
76
+ seek_time: tp.List[tp.Optional[float]] = []
77
+
78
+
79
+ @dataclass
80
+ class ConditioningAttributes:
81
+ text: tp.Dict[str, tp.Optional[str]] = field(default_factory=dict)
82
+ wav: tp.Dict[str, WavCondition] = field(default_factory=dict)
83
+ beat: tp.Dict[str, BeatCondition] = field(default_factory=dict)
84
+ chord: tp.Dict[str, ChordCondition] = field(default_factory=dict)
85
+ joint_embed: tp.Dict[str, JointEmbedCondition] = field(default_factory=dict)
86
+
87
+ def __getitem__(self, item):
88
+ return getattr(self, item)
89
+
90
+ @property
91
+ def text_attributes(self):
92
+ return self.text.keys()
93
+
94
+ @property
95
+ def wav_attributes(self):
96
+ return self.wav.keys()
97
+
98
+ @property
99
+ def beat_attributes(self):
100
+ return self.beat.keys()
101
+
102
+ @property
103
+ def chord_attributes(self):
104
+ return self.chord.keys()
105
+
106
+ @property
107
+ def joint_embed_attributes(self):
108
+ return self.joint_embed.keys()
109
+
110
+ @property
111
+ def attributes(self):
112
+ return {
113
+ "text": self.text_attributes,
114
+ "wav": self.wav_attributes,
115
+ "beat" : self.beat_attributes,
116
+ "chord": self.chord_attributes,
117
+ "joint_embed": self.joint_embed_attributes,
118
+ }
119
+
120
+ def to_flat_dict(self):
121
+ return {
122
+ **{f"text.{k}": v for k, v in self.text.items()},
123
+ **{f"wav.{k}": v for k, v in self.wav.items()},
124
+ **{f"beat.{k}": v for k, v in self.beat.items()},
125
+ **{f"chord.{k}": v for k, v in self.chord.items()},
126
+ **{f"joint_embed.{k}": v for k, v in self.joint_embed.items()}
127
+ }
128
+
129
+ @classmethod
130
+ def from_flat_dict(cls, x):
131
+ out = cls()
132
+ for k, v in x.items():
133
+ kind, att = k.split(".")
134
+ out[kind][att] = v
135
+ return out
136
+
137
+
138
+ class SegmentWithAttributes(SegmentInfo):
139
+ """Base class for all dataclasses that are used for conditioning.
140
+ All child classes should implement `to_condition_attributes` that converts
141
+ the existing attributes to a dataclass of type ConditioningAttributes.
142
+ """
143
+ def to_condition_attributes(self) -> ConditioningAttributes:
144
+ raise NotImplementedError()
145
+
146
+
147
+ def nullify_condition(condition: ConditionType, dim: int = 1):
148
+ """Transform an input condition to a null condition.
149
+ The way it is done by converting it to a single zero vector similarly
150
+ to how it is done inside WhiteSpaceTokenizer and NoopTokenizer.
151
+
152
+ Args:
153
+ condition (ConditionType): A tuple of condition and mask (tuple[torch.Tensor, torch.Tensor])
154
+ dim (int): The dimension that will be truncated (should be the time dimension)
155
+ WARNING!: dim should not be the batch dimension!
156
+ Returns:
157
+ ConditionType: A tuple of null condition and mask
158
+ """
159
+ assert dim != 0, "dim cannot be the batch dimension!"
160
+ assert isinstance(condition, tuple) and \
161
+ isinstance(condition[0], torch.Tensor) and \
162
+ isinstance(condition[1], torch.Tensor), "'nullify_condition' got an unexpected input type!"
163
+ cond, mask = condition
164
+ B = cond.shape[0]
165
+ last_dim = cond.dim() - 1
166
+ out = cond.transpose(dim, last_dim)
167
+ out = 0. * out[..., :1]
168
+ out = out.transpose(dim, last_dim)
169
+ mask = torch.zeros((B, 1), device=out.device).int()
170
+ assert cond.dim() == out.dim()
171
+ return out, mask
172
+
173
+
174
+ def nullify_wav(cond: WavCondition) -> WavCondition:
175
+ """Transform a WavCondition to a nullified WavCondition.
176
+ It replaces the wav by a null tensor, forces its length to 0, and replaces metadata by dummy attributes.
177
+
178
+ Args:
179
+ cond (WavCondition): Wav condition with wav, tensor of shape [B, T].
180
+ Returns:
181
+ WavCondition: Nullified wav condition.
182
+ """
183
+ null_wav, _ = nullify_condition((cond.wav, torch.zeros_like(cond.wav)), dim=cond.wav.dim() - 1)
184
+ return WavCondition(
185
+ wav=null_wav,
186
+ length=torch.tensor([0] * cond.wav.shape[0], device=cond.wav.device),
187
+ sample_rate=cond.sample_rate,
188
+ path=[None] * cond.wav.shape[0],
189
+ seek_time=[None] * cond.wav.shape[0],
190
+ )
191
+
192
+ def nullify_chord(cond: ChordCondition) -> ChordCondition:
193
+ """Transform a ChordCondition to a nullified ChordCondition.
194
+ It replaces the wav by a null tensor, forces its length to 0, and replaces metadata by dummy attributes.
195
+
196
+ Args:
197
+ cond (ChordCondition): Chord condition with chord, tensor of shape [B, C, T].
198
+ Returns:
199
+ ChordCondition: Nullified chord condition.
200
+ """
201
+ null_chord, _ = nullify_condition((cond.chord, torch.zeros_like(cond.chord)), dim=cond.chord.dim() - 1)
202
+ return ChordCondition(
203
+ chord=null_chord,
204
+ length=torch.tensor([0] * cond.chord.shape[0], device=cond.chord.device),
205
+ bpm=[None] * cond.chord.shape[0],
206
+ path=[None] * cond.chord.shape[0],
207
+ seek_frame=[None] * cond.chord.shape[0],
208
+ )
209
+
210
+
211
+ def nullify_beat(cond: BeatCondition) -> BeatCondition:
212
+ """
213
+ Args:
214
+ cond (ChordCondition): Chord condition with chord, tensor of shape [B, C, T].
215
+ Returns:
216
+ ChordCondition: Nullified chord condition.
217
+ """
218
+ null_beat, _ = nullify_condition((cond.beat, torch.zeros_like(cond.beat)), dim=cond.beat.dim() - 1)
219
+ return BeatCondition(
220
+ beat=null_beat,
221
+ length=torch.tensor([0] * cond.beat.shape[0], device=cond.beat.device),
222
+ bpm=[None] * cond.beat.shape[0],
223
+ path=[None] * cond.beat.shape[0],
224
+ seek_frame=[None] * cond.beat.shape[0],
225
+ )
226
+
227
+
228
+ def nullify_joint_embed(embed: JointEmbedCondition) -> JointEmbedCondition:
229
+ """Nullify the joint embedding condition by replacing it by a null tensor, forcing its length to 0,
230
+ and replacing metadata by dummy attributes.
231
+
232
+ Args:
233
+ cond (JointEmbedCondition): Joint embedding condition with wav and text, wav tensor of shape [B, C, T].
234
+ """
235
+ null_wav, _ = nullify_condition((embed.wav, torch.zeros_like(embed.wav)), dim=embed.wav.dim() - 1)
236
+ return JointEmbedCondition(
237
+ wav=null_wav, text=[None] * len(embed.text),
238
+ length=torch.LongTensor([0]).to(embed.wav.device),
239
+ sample_rate=embed.sample_rate,
240
+ path=[None] * embed.wav.shape[0],
241
+ seek_time=[0] * embed.wav.shape[0],
242
+ )
243
+
244
+
245
+ class Tokenizer:
246
+ """Base tokenizer implementation
247
+ (in case we want to introduce more advances tokenizers in the future).
248
+ """
249
+ def __call__(self, texts: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
250
+ raise NotImplementedError()
251
+
252
+
253
+ class WhiteSpaceTokenizer(Tokenizer):
254
+ """This tokenizer should be used for natural language descriptions.
255
+ For example:
256
+ ["he didn't, know he's going home.", 'shorter sentence'] =>
257
+ [[78, 62, 31, 4, 78, 25, 19, 34],
258
+ [59, 77, 0, 0, 0, 0, 0, 0]]
259
+ """
260
+ PUNCTUATION = "?:!.,;"
261
+
262
+ def __init__(self, n_bins: int, pad_idx: int = 0, language: str = "en_core_web_sm",
263
+ lemma: bool = True, stopwords: bool = True) -> None:
264
+ self.n_bins = n_bins
265
+ self.pad_idx = pad_idx
266
+ self.lemma = lemma
267
+ self.stopwords = stopwords
268
+ try:
269
+ self.nlp = spacy.load(language)
270
+ except IOError:
271
+ spacy.cli.download(language) # type: ignore
272
+ self.nlp = spacy.load(language)
273
+
274
+ @tp.no_type_check
275
+ def __call__(self, texts: tp.List[tp.Optional[str]],
276
+ return_text: bool = False) -> tp.Tuple[torch.Tensor, torch.Tensor]:
277
+ """Take a list of strings and convert them to a tensor of indices.
278
+
279
+ Args:
280
+ texts (list[str]): List of strings.
281
+ return_text (bool, optional): Whether to return text as additional tuple item. Defaults to False.
282
+ Returns:
283
+ tuple[torch.Tensor, torch.Tensor]:
284
+ - Indices of words in the LUT.
285
+ - And a mask indicating where the padding tokens are
286
+ """
287
+ output, lengths = [], []
288
+ texts = deepcopy(texts)
289
+ for i, text in enumerate(texts):
290
+ # if current sample doesn't have a certain attribute, replace with pad token
291
+ if text is None:
292
+ output.append(torch.Tensor([self.pad_idx]))
293
+ lengths.append(0)
294
+ continue
295
+
296
+ # convert numbers to words
297
+ text = re.sub(r"(\d+)", lambda x: num2words(int(x.group(0))), text) # type: ignore
298
+ # normalize text
299
+ text = self.nlp(text) # type: ignore
300
+ # remove stopwords
301
+ if self.stopwords:
302
+ text = [w for w in text if not w.is_stop] # type: ignore
303
+ # remove punctuation
304
+ text = [w for w in text if w.text not in self.PUNCTUATION] # type: ignore
305
+ # lemmatize if needed
306
+ text = [getattr(t, "lemma_" if self.lemma else "text") for t in text] # type: ignore
307
+
308
+ texts[i] = " ".join(text)
309
+ lengths.append(len(text))
310
+ # convert to tensor
311
+ tokens = torch.Tensor([hash_trick(w, self.n_bins) for w in text])
312
+ output.append(tokens)
313
+
314
+ mask = length_to_mask(torch.IntTensor(lengths)).int()
315
+ padded_output = pad_sequence(output, padding_value=self.pad_idx).int().t()
316
+ if return_text:
317
+ return padded_output, mask, texts # type: ignore
318
+ return padded_output, mask
319
+
320
+
321
+ class NoopTokenizer(Tokenizer):
322
+ """This tokenizer should be used for global conditioners such as: artist, genre, key, etc.
323
+ The difference between this and WhiteSpaceTokenizer is that NoopTokenizer does not split
324
+ strings, so "Jeff Buckley" will get it's own index. Whereas WhiteSpaceTokenizer will
325
+ split it to ["Jeff", "Buckley"] and return an index per word.
326
+
327
+ For example:
328
+ ["Queen", "ABBA", "Jeff Buckley"] => [43, 55, 101]
329
+ ["Metal", "Rock", "Classical"] => [0, 223, 51]
330
+ """
331
+ def __init__(self, n_bins: int, pad_idx: int = 0):
332
+ self.n_bins = n_bins
333
+ self.pad_idx = pad_idx
334
+
335
+ def __call__(self, texts: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
336
+ output, lengths = [], []
337
+ for text in texts:
338
+ # if current sample doesn't have a certain attribute, replace with pad token
339
+ if text is None:
340
+ output.append(self.pad_idx)
341
+ lengths.append(0)
342
+ else:
343
+ output.append(hash_trick(text, self.n_bins))
344
+ lengths.append(1)
345
+
346
+ tokens = torch.LongTensor(output).unsqueeze(1)
347
+ mask = length_to_mask(torch.IntTensor(lengths)).int()
348
+ return tokens, mask
349
+
350
+
351
+ class BaseConditioner(nn.Module):
352
+ """Base model for all conditioner modules.
353
+ We allow the output dim to be different than the hidden dim for two reasons:
354
+ 1) keep our LUTs small when the vocab is large;
355
+ 2) make all condition dims consistent.
356
+
357
+ Args:
358
+ dim (int): Hidden dim of the model.
359
+ output_dim (int): Output dim of the conditioner.
360
+ """
361
+ def __init__(self, dim: int, output_dim: int):
362
+ super().__init__()
363
+ self.dim = dim
364
+ self.output_dim = output_dim
365
+ self.output_proj = nn.Linear(dim, output_dim)
366
+
367
+ def tokenize(self, *args, **kwargs) -> tp.Any:
368
+ """Should be any part of the processing that will lead to a synchronization
369
+ point, e.g. BPE tokenization with transfer to the GPU.
370
+
371
+ The returned value will be saved and return later when calling forward().
372
+ """
373
+ raise NotImplementedError()
374
+
375
+ def forward(self, inputs: tp.Any) -> ConditionType:
376
+ """Gets input that should be used as conditioning (e.g, genre, description or a waveform).
377
+ Outputs a ConditionType, after the input data was embedded as a dense vector.
378
+
379
+ Returns:
380
+ ConditionType:
381
+ - A tensor of size [B, T, D] where B is the batch size, T is the length of the
382
+ output embedding and D is the dimension of the embedding.
383
+ - And a mask indicating where the padding tokens.
384
+ """
385
+ raise NotImplementedError()
386
+
387
+
388
+ class TextConditioner(BaseConditioner):
389
+ ...
390
+
391
+
392
+ class LUTConditioner(TextConditioner):
393
+ """Lookup table TextConditioner.
394
+
395
+ Args:
396
+ n_bins (int): Number of bins.
397
+ dim (int): Hidden dim of the model (text-encoder/LUT).
398
+ output_dim (int): Output dim of the conditioner.
399
+ tokenizer (str): Name of the tokenizer.
400
+ pad_idx (int, optional): Index for padding token. Defaults to 0.
401
+ """
402
+ def __init__(self, n_bins: int, dim: int, output_dim: int, tokenizer: str, pad_idx: int = 0):
403
+ super().__init__(dim, output_dim)
404
+ self.embed = nn.Embedding(n_bins, dim)
405
+ self.tokenizer: Tokenizer
406
+ if tokenizer == 'whitespace':
407
+ self.tokenizer = WhiteSpaceTokenizer(n_bins, pad_idx=pad_idx)
408
+ elif tokenizer == 'noop':
409
+ self.tokenizer = NoopTokenizer(n_bins, pad_idx=pad_idx)
410
+ else:
411
+ raise ValueError(f"unrecognized tokenizer `{tokenizer}`.")
412
+
413
+ def tokenize(self, x: tp.List[tp.Optional[str]]) -> tp.Tuple[torch.Tensor, torch.Tensor]:
414
+ device = self.embed.weight.device
415
+ tokens, mask = self.tokenizer(x)
416
+ tokens, mask = tokens.to(device), mask.to(device)
417
+ return tokens, mask
418
+
419
+ def forward(self, inputs: tp.Tuple[torch.Tensor, torch.Tensor]) -> ConditionType:
420
+ tokens, mask = inputs
421
+ embeds = self.embed(tokens)
422
+ embeds = self.output_proj(embeds)
423
+ embeds = (embeds * mask.unsqueeze(-1))
424
+ return embeds, mask
425
+
426
+
427
+ class T5Conditioner(TextConditioner):
428
+ """T5-based TextConditioner.
429
+
430
+ Args:
431
+ name (str): Name of the T5 model.
432
+ output_dim (int): Output dim of the conditioner.
433
+ finetune (bool): Whether to fine-tune T5 at train time.
434
+ device (str): Device for T5 Conditioner.
435
+ autocast_dtype (tp.Optional[str], optional): Autocast dtype.
436
+ word_dropout (float, optional): Word dropout probability.
437
+ normalize_text (bool, optional): Whether to apply text normalization.
438
+ """
439
+ MODELS = ["t5-small", "t5-base", "t5-large", "t5-3b", "t5-11b",
440
+ "google/flan-t5-small", "google/flan-t5-base", "google/flan-t5-large",
441
+ "google/flan-t5-xl", "google/flan-t5-xxl"]
442
+ MODELS_DIMS = {
443
+ "t5-small": 512,
444
+ "t5-base": 768,
445
+ "t5-large": 1024,
446
+ "t5-3b": 1024,
447
+ "t5-11b": 1024,
448
+ "google/flan-t5-small": 512,
449
+ "google/flan-t5-base": 768,
450
+ "google/flan-t5-large": 1024,
451
+ "google/flan-t5-3b": 1024,
452
+ "google/flan-t5-11b": 1024,
453
+ }
454
+
455
+ def __init__(self, name: str, output_dim: int, finetune: bool, device: str,
456
+ autocast_dtype: tp.Optional[str] = 'float32', word_dropout: float = 0.,
457
+ normalize_text: bool = False):
458
+ assert name in self.MODELS, f"Unrecognized t5 model name (should in {self.MODELS})"
459
+ super().__init__(self.MODELS_DIMS[name], output_dim)
460
+ self.device = device
461
+ self.name = name
462
+ self.finetune = finetune
463
+ self.word_dropout = word_dropout
464
+ if autocast_dtype is None or self.device == 'cpu':
465
+ self.autocast = TorchAutocast(enabled=False)
466
+ if self.device != 'cpu':
467
+ logger.warning("T5 has no autocast, this might lead to NaN")
468
+ else:
469
+ dtype = getattr(torch, autocast_dtype)
470
+ assert isinstance(dtype, torch.dtype)
471
+ logger.info(f"T5 will be evaluated with autocast as {autocast_dtype}")
472
+ self.autocast = TorchAutocast(enabled=True, device_type=self.device, dtype=dtype)
473
+ # Let's disable logging temporarily because T5 will vomit some errors otherwise.
474
+ # thanks https://gist.github.com/simon-weber/7853144
475
+ previous_level = logging.root.manager.disable
476
+ logging.disable(logging.ERROR)
477
+ with warnings.catch_warnings():
478
+ warnings.simplefilter("ignore")
479
+ try:
480
+ self.t5_tokenizer = T5Tokenizer.from_pretrained(name)
481
+ t5 = T5EncoderModel.from_pretrained(name).train(mode=finetune)
482
+ finally:
483
+ logging.disable(previous_level)
484
+ if finetune:
485
+ self.t5 = t5
486
+ else:
487
+ # this makes sure that the t5 models is not part
488
+ # of the saved checkpoint
489
+ self.__dict__['t5'] = t5.to(device)
490
+
491
+ self.normalize_text = normalize_text
492
+ if normalize_text:
493
+ self.text_normalizer = WhiteSpaceTokenizer(1, lemma=True, stopwords=True)
494
+
495
+ def tokenize(self, x: tp.List[tp.Optional[str]]) -> tp.Dict[str, torch.Tensor]:
496
+ # if current sample doesn't have a certain attribute, replace with empty string
497
+ entries: tp.List[str] = [xi if xi is not None else "" for xi in x]
498
+ if self.normalize_text:
499
+ _, _, entries = self.text_normalizer(entries, return_text=True)
500
+ if self.word_dropout > 0. and self.training:
501
+ new_entries = []
502
+ for entry in entries:
503
+ words = [word for word in entry.split(" ") if random.random() >= self.word_dropout]
504
+ new_entries.append(" ".join(words))
505
+ entries = new_entries
506
+
507
+ empty_idx = torch.LongTensor([i for i, xi in enumerate(entries) if xi == ""])
508
+
509
+ inputs = self.t5_tokenizer(entries, return_tensors='pt', padding=True).to(self.device)
510
+ mask = inputs['attention_mask']
511
+ mask[empty_idx, :] = 0 # zero-out index where the input is non-existant
512
+ return inputs
513
+
514
+ def forward(self, inputs: tp.Dict[str, torch.Tensor]) -> ConditionType:
515
+ mask = inputs['attention_mask']
516
+ with torch.set_grad_enabled(self.finetune), self.autocast:
517
+ embeds = self.t5(**inputs).last_hidden_state
518
+ embeds = self.output_proj(embeds.to(self.output_proj.weight))
519
+ embeds = (embeds * mask.unsqueeze(-1))
520
+ return embeds, mask
521
+
522
+
523
+ class WaveformConditioner(BaseConditioner):
524
+ """Base class for all conditioners that take a waveform as input.
525
+ Classes that inherit must implement `_get_wav_embedding` that outputs
526
+ a continuous tensor, and `_downsampling_factor` that returns the down-sampling
527
+ factor of the embedding model.
528
+
529
+ Args:
530
+ dim (int): The internal representation dimension.
531
+ output_dim (int): Output dimension.
532
+ device (tp.Union[torch.device, str]): Device.
533
+ """
534
+ def __init__(self, dim: int, output_dim: int, device: tp.Union[torch.device, str]):
535
+ super().__init__(dim, output_dim)
536
+ self.device = device
537
+
538
+ def tokenize(self, x: WavCondition) -> WavCondition:
539
+ wav, length, sample_rate, path, seek_time = x
540
+ assert length is not None
541
+ return WavCondition(wav.to(self.device), length.to(self.device), sample_rate, path, seek_time)
542
+
543
+ def _get_wav_embedding(self, x: WavCondition) -> torch.Tensor:
544
+ """Gets as input a WavCondition and returns a dense embedding."""
545
+ raise NotImplementedError()
546
+
547
+ def _downsampling_factor(self):
548
+ """Returns the downsampling factor of the embedding model."""
549
+ raise NotImplementedError()
550
+
551
+ def forward(self, x: WavCondition) -> ConditionType:
552
+ """Extract condition embedding and mask from a waveform and its metadata.
553
+ Args:
554
+ x (WavCondition): Waveform condition containing raw waveform and metadata.
555
+ Returns:
556
+ ConditionType: a dense vector representing the conditioning along with its mask
557
+ """
558
+ wav, lengths, *_ = x
559
+ with torch.no_grad():
560
+ embeds = self._get_wav_embedding(x)
561
+ embeds = embeds.to(self.output_proj.weight)
562
+ embeds = self.output_proj(embeds)
563
+
564
+ if lengths is not None:
565
+ lengths = lengths / self._downsampling_factor()
566
+ mask = length_to_mask(lengths, max_len=embeds.shape[1]).int() # type: ignore
567
+ else:
568
+ mask = torch.ones_like(embeds)
569
+ embeds = (embeds * mask.unsqueeze(2).to(self.device))
570
+
571
+ return embeds, mask
572
+
573
+
574
+ class ChromaStemConditioner(WaveformConditioner):
575
+ """Chroma conditioner based on stems.
576
+ The ChromaStemConditioner uses DEMUCS to first filter out drums and bass, as
577
+ the drums and bass often dominate the chroma leading to the chroma features
578
+ not containing information about the melody.
579
+
580
+ Args:
581
+ output_dim (int): Output dimension for the conditioner.
582
+ sample_rate (int): Sample rate for the chroma extractor.
583
+ n_chroma (int): Number of chroma bins for the chroma extractor.
584
+ radix2_exp (int): Size of stft window for the chroma extractor (power of 2, e.g. 12 -> 2^12).
585
+ duration (int): duration used during training. This is later used for correct padding
586
+ in case we are using chroma as prefix.
587
+ match_len_on_eval (bool, optional): if True then all chromas are padded to the training
588
+ duration. Defaults to False.
589
+ eval_wavs (str, optional): path to a dataset manifest with waveform, this waveforms are used as
590
+ conditions during eval (for cases where we don't want to leak test conditions like MusicCaps).
591
+ Defaults to None.
592
+ n_eval_wavs (int, optional): limits the number of waveforms used for conditioning. Defaults to 0.
593
+ device (tp.Union[torch.device, str], optional): Device for the conditioner.
594
+ **kwargs: Additional parameters for the chroma extractor.
595
+ """
596
+ def __init__(self, output_dim: int, sample_rate: int, n_chroma: int, radix2_exp: int,
597
+ duration: float, match_len_on_eval: bool = True, eval_wavs: tp.Optional[str] = None,
598
+ n_eval_wavs: int = 0, cache_path: tp.Optional[tp.Union[str, Path]] = None,
599
+ device: tp.Union[torch.device, str] = 'cpu', **kwargs):
600
+ from demucs import pretrained
601
+ super().__init__(dim=n_chroma, output_dim=output_dim, device=device)
602
+ self.autocast = TorchAutocast(enabled=device != 'cpu', device_type=self.device, dtype=torch.float32)
603
+ self.sample_rate = sample_rate
604
+ self.match_len_on_eval = match_len_on_eval
605
+ self.duration = duration
606
+ self.__dict__['demucs'] = pretrained.get_model('htdemucs').to(device)
607
+ stem_sources: list = self.demucs.sources # type: ignore
608
+ self.stem_indices = torch.LongTensor([stem_sources.index('vocals'), stem_sources.index('other')]).to(device)
609
+ self.chroma = ChromaExtractor(sample_rate=sample_rate, n_chroma=n_chroma,
610
+ radix2_exp=radix2_exp, **kwargs).to(device)
611
+ self.chroma_len = self._get_chroma_len()
612
+ self.eval_wavs: tp.Optional[torch.Tensor] = self._load_eval_wavs(eval_wavs, n_eval_wavs)
613
+ self.cache = None
614
+ if cache_path is not None:
615
+ self.cache = EmbeddingCache(Path(cache_path) / 'wav', self.device,
616
+ compute_embed_fn=self._get_full_chroma_for_cache,
617
+ extract_embed_fn=self._extract_chroma_chunk)
618
+
619
+ def _downsampling_factor(self) -> int:
620
+ return self.chroma.winhop
621
+
622
+ def _load_eval_wavs(self, path: tp.Optional[str], num_samples: int) -> tp.Optional[torch.Tensor]:
623
+ """Load pre-defined waveforms from a json.
624
+ These waveforms will be used for chroma extraction during evaluation.
625
+ This is done to make the evaluation on MusicCaps fair (we shouldn't see the chromas of MusicCaps).
626
+ """
627
+ if path is None:
628
+ return None
629
+
630
+ logger.info(f"Loading evaluation wavs from {path}")
631
+ from audiocraft.data.audio_dataset import AudioDataset
632
+ dataset: AudioDataset = AudioDataset.from_meta(
633
+ path, segment_duration=self.duration, min_audio_duration=self.duration,
634
+ sample_rate=self.sample_rate, channels=1)
635
+
636
+ if len(dataset) > 0:
637
+ eval_wavs = dataset.collater([dataset[i] for i in range(num_samples)]).to(self.device)
638
+ logger.info(f"Using {len(eval_wavs)} evaluation wavs for chroma-stem conditioner")
639
+ return eval_wavs
640
+ else:
641
+ raise ValueError("Could not find evaluation wavs, check lengths of wavs")
642
+
643
+ def reset_eval_wavs(self, eval_wavs: tp.Optional[torch.Tensor]) -> None:
644
+ self.eval_wavs = eval_wavs
645
+
646
+ def has_eval_wavs(self) -> bool:
647
+ return self.eval_wavs is not None
648
+
649
+ def _sample_eval_wavs(self, num_samples: int) -> torch.Tensor:
650
+ """Sample wavs from a predefined list."""
651
+ assert self.eval_wavs is not None, "Cannot sample eval wavs as no eval wavs provided."
652
+ total_eval_wavs = len(self.eval_wavs)
653
+ out = self.eval_wavs
654
+ if num_samples > total_eval_wavs:
655
+ out = self.eval_wavs.repeat(num_samples // total_eval_wavs + 1, 1, 1)
656
+ return out[torch.randperm(len(out))][:num_samples]
657
+
658
+ def _get_chroma_len(self) -> int:
659
+ """Get length of chroma during training."""
660
+ dummy_wav = torch.zeros((1, int(self.sample_rate * self.duration)), device=self.device)
661
+ dummy_chr = self.chroma(dummy_wav)
662
+ return dummy_chr.shape[1]
663
+
664
+ @torch.no_grad()
665
+ def _get_stemmed_wav(self, wav: torch.Tensor, sample_rate: int) -> torch.Tensor:
666
+ """Get parts of the wav that holds the melody, extracting the main stems from the wav."""
667
+ from demucs.apply import apply_model
668
+ from demucs.audio import convert_audio
669
+ with self.autocast:
670
+ wav = convert_audio(
671
+ wav, sample_rate, self.demucs.samplerate, self.demucs.audio_channels) # type: ignore
672
+ stems = apply_model(self.demucs, wav, device=self.device)
673
+ stems = stems[:, self.stem_indices] # extract relevant stems for melody conditioning
674
+ mix_wav = stems.sum(1) # merge extracted stems to single waveform
675
+ mix_wav = convert_audio(mix_wav, self.demucs.samplerate, self.sample_rate, 1) # type: ignore
676
+ return mix_wav
677
+
678
+ @torch.no_grad()
679
+ def _extract_chroma(self, wav: torch.Tensor) -> torch.Tensor:
680
+ """Extract chroma features from the waveform."""
681
+ with self.autocast:
682
+ return self.chroma(wav)
683
+
684
+ @torch.no_grad()
685
+ def _compute_wav_embedding(self, wav: torch.Tensor, sample_rate: int) -> torch.Tensor:
686
+ """Compute wav embedding, applying stem and chroma extraction."""
687
+ # avoid 0-size tensors when we are working with null conds
688
+ if wav.shape[-1] == 1:
689
+ return self._extract_chroma(wav)
690
+ stems = self._get_stemmed_wav(wav, sample_rate)
691
+ chroma = self._extract_chroma(stems)
692
+ return chroma
693
+
694
+ @torch.no_grad()
695
+ def _get_full_chroma_for_cache(self, path: tp.Union[str, Path], x: WavCondition, idx: int) -> torch.Tensor:
696
+ """Extract chroma from the whole audio waveform at the given path."""
697
+ wav, sr = audio_read(path)
698
+ wav = wav[None].to(self.device)
699
+ wav = convert_audio(wav, sr, self.sample_rate, to_channels=1)
700
+ chroma = self._compute_wav_embedding(wav, self.sample_rate)[0]
701
+ return chroma
702
+
703
+ def _extract_chroma_chunk(self, full_chroma: torch.Tensor, x: WavCondition, idx: int) -> torch.Tensor:
704
+ """Extract a chunk of chroma from the full chroma derived from the full waveform."""
705
+ wav_length = x.wav.shape[-1]
706
+ seek_time = x.seek_time[idx]
707
+ assert seek_time is not None, (
708
+ "WavCondition seek_time is required "
709
+ "when extracting chroma chunks from pre-computed chroma.")
710
+ full_chroma = full_chroma.float()
711
+ frame_rate = self.sample_rate / self._downsampling_factor()
712
+ target_length = int(frame_rate * wav_length / self.sample_rate)
713
+ index = int(frame_rate * seek_time)
714
+ out = full_chroma[index: index + target_length]
715
+ out = F.pad(out[None], (0, 0, 0, target_length - out.shape[0]))[0]
716
+ return out.to(self.device)
717
+
718
+ @torch.no_grad()
719
+ def _get_wav_embedding(self, x: WavCondition) -> torch.Tensor:
720
+ """Get the wav embedding from the WavCondition.
721
+ The conditioner will either extract the embedding on-the-fly computing it from the condition wav directly
722
+ or will rely on the embedding cache to load the pre-computed embedding if relevant.
723
+ """
724
+ sampled_wav: tp.Optional[torch.Tensor] = None
725
+ if not self.training and self.eval_wavs is not None:
726
+ warn_once(logger, "Using precomputed evaluation wavs!")
727
+ sampled_wav = self._sample_eval_wavs(len(x.wav))
728
+
729
+ no_undefined_paths = all(p is not None for p in x.path)
730
+ no_nullified_cond = x.wav.shape[-1] > 1
731
+ if sampled_wav is not None:
732
+ chroma = self._compute_wav_embedding(sampled_wav, self.sample_rate)
733
+ elif self.cache is not None and no_undefined_paths and no_nullified_cond:
734
+ paths = [Path(p) for p in x.path if p is not None]
735
+ chroma = self.cache.get_embed_from_cache(paths, x)
736
+ else:
737
+ assert all(sr == x.sample_rate[0] for sr in x.sample_rate), "All sample rates in batch should be equal."
738
+ chroma = self._compute_wav_embedding(x.wav, x.sample_rate[0])
739
+
740
+ if self.match_len_on_eval:
741
+ B, T, C = chroma.shape
742
+ if T > self.chroma_len:
743
+ chroma = chroma[:, :self.chroma_len]
744
+ logger.debug(f"Chroma was truncated to match length! ({T} -> {chroma.shape[1]})")
745
+ elif T < self.chroma_len:
746
+ n_repeat = int(math.ceil(self.chroma_len / T))
747
+ chroma = chroma.repeat(1, n_repeat, 1)
748
+ chroma = chroma[:, :self.chroma_len]
749
+ logger.debug(f"Chroma was repeated to match length! ({T} -> {chroma.shape[1]})")
750
+
751
+ return chroma
752
+
753
+ def tokenize(self, x: WavCondition) -> WavCondition:
754
+ """Apply WavConditioner tokenization and populate cache if needed."""
755
+ x = super().tokenize(x)
756
+ no_undefined_paths = all(p is not None for p in x.path)
757
+ if self.cache is not None and no_undefined_paths:
758
+ paths = [Path(p) for p in x.path if p is not None]
759
+ self.cache.populate_embed_cache(paths, x)
760
+ return x
761
+
762
+ class ChordProgressionConditioner(BaseConditioner):
763
+ """Chord progression conditioning supporting chord progression conditioning.
764
+
765
+ Args:
766
+ dim (int): Dimension.
767
+ output_dim (int): Output dimension.
768
+ device (str): Device.
769
+ attribute (str): Attribute used by the conditioner.
770
+ autocast_dtype (str): Autocast for the conditioner.
771
+ """
772
+
773
+ def __init__(self, output_dim: int, device: str, name: str):
774
+ n_chroma = 12
775
+ # n_chroma = 24
776
+ super().__init__(dim=n_chroma, output_dim=output_dim)
777
+ self.device = device
778
+
779
+ def forward(self, x: ChordCondition) -> ConditionType:
780
+ chord, lengths, *_ = x
781
+ embeds = chord.to(self.output_proj.weight) # chrod is already a tensor, [N, C]
782
+ embeds = self.output_proj(embeds)
783
+
784
+ if lengths is not None:
785
+ mask = length_to_mask(lengths, max_len=embeds.shape[1]).int() # type: ignore
786
+ else:
787
+ mask = torch.ones_like(embeds)
788
+ embeds = (embeds * mask.unsqueeze(2).to(self.device))
789
+
790
+ return embeds, mask
791
+
792
+ def tokenize(self, x: ChordCondition) -> ChordCondition:
793
+ """Apply ChordConditioner tokenization and populate cache if needed."""
794
+ chord, length, bpm, path, seek_frame = x
795
+ chord = F.pad(chord, (0, length[0] - chord.shape[-1])) # [B, C, t] -> [B, C, T]
796
+ chord = chord.permute(0, 2, 1) # [B, T, C]
797
+ x = ChordCondition(chord.to(self.device), length.to(self.device), bpm, path, seek_frame)
798
+ return x
799
+
800
+ class BeatConditioner(BaseConditioner):
801
+ """Beat conditioning supporting beat conditioning.
802
+
803
+ Args:
804
+ dim (int): Dimension.
805
+ output_dim (int): Output dimension.
806
+ device (str): Device.
807
+ attribute (str): Attribute used by the conditioner.
808
+ autocast_dtype (str): Autocast for the conditioner.
809
+ """
810
+
811
+ def __init__(self, output_dim: int, device: str, name: str):
812
+ beat_channel = 1
813
+ super().__init__(dim=beat_channel, output_dim=output_dim)
814
+ self.device = device
815
+
816
+ def forward(self, x: BeatCondition) -> ConditionType:
817
+ beat, lengths, *_ = x
818
+ embeds = beat.to(self.output_proj.weight) # chrod is already a tensor, [N, C]
819
+ embeds = self.output_proj(embeds)
820
+
821
+ if lengths is not None:
822
+ mask = length_to_mask(lengths, max_len=embeds.shape[1]).int() # type: ignore
823
+ else:
824
+ mask = torch.ones_like(embeds)
825
+ embeds = (embeds * mask.unsqueeze(2).to(self.device))
826
+
827
+ return embeds, mask
828
+
829
+ def tokenize(self, x: BeatCondition) -> BeatCondition:
830
+ """Apply ChordConditioner tokenization and populate cache if needed."""
831
+ beat, length, bpm, path, seek_frame = x
832
+ beat = F.pad(beat, (0, length[0] - beat.shape[-1])) # [B, C, t] -> [B, C, T]
833
+ beat = beat.permute(0, 2, 1) # [B, T, C]
834
+ x = BeatCondition(beat.to(self.device), length.to(self.device), bpm, path, seek_frame)
835
+ return x
836
+
837
+
838
+ class JointEmbeddingConditioner(BaseConditioner):
839
+ """Joint embedding conditioning supporting both audio or text conditioning.
840
+
841
+ Args:
842
+ dim (int): Dimension.
843
+ output_dim (int): Output dimension.
844
+ device (str): Device.
845
+ attribute (str): Attribute used by the conditioner.
846
+ autocast_dtype (str): Autocast for the conditioner.
847
+ quantize (bool): Whether to quantize the CLAP embedding.
848
+ n_q (int): Number of residual quantizers (used if quantize is true).
849
+ bins (int): Quantizers' codebooks size (used if quantize is true).
850
+ kwargs: Additional parameters for residual vector quantizer.
851
+ """
852
+ def __init__(self, dim: int, output_dim: int, device: str, attribute: str,
853
+ autocast_dtype: tp.Optional[str] = 'float32', quantize: bool = True,
854
+ n_q: int = 12, bins: int = 1024, **kwargs):
855
+ super().__init__(dim=dim, output_dim=output_dim)
856
+ self.device = device
857
+ self.attribute = attribute
858
+ if autocast_dtype is None or device == 'cpu':
859
+ self.autocast = TorchAutocast(enabled=False)
860
+ logger.warning("JointEmbeddingConditioner has no autocast, this might lead to NaN.")
861
+ else:
862
+ dtype = getattr(torch, autocast_dtype)
863
+ assert isinstance(dtype, torch.dtype)
864
+ logger.info(f"JointEmbeddingConditioner will be evaluated with autocast as {autocast_dtype}.")
865
+ self.autocast = TorchAutocast(enabled=True, device_type=self.device, dtype=dtype)
866
+ # residual vector quantizer to discretize the conditioned embedding
867
+ self.quantizer: tp.Optional[ResidualVectorQuantizer] = None
868
+ if quantize:
869
+ self.quantizer = ResidualVectorQuantizer(dim, n_q=n_q, bins=bins, **kwargs)
870
+
871
+ def _get_embed(self, x: JointEmbedCondition) -> tp.Tuple[torch.Tensor, torch.Tensor]:
872
+ """Get joint embedding in latent space from the inputs.
873
+
874
+ Returns:
875
+ tuple[torch.Tensor, torch.Tensor]: Tensor for the latent embedding
876
+ and corresponding empty indexes.
877
+ """
878
+ raise NotImplementedError()
879
+
880
+ def forward(self, x: JointEmbedCondition) -> ConditionType:
881
+ with self.autocast:
882
+ embed, empty_idx = self._get_embed(x)
883
+ if self.quantizer is not None:
884
+ embed = embed.view(-1, self.dim, 1)
885
+ q_res = self.quantizer(embed, frame_rate=1)
886
+ out_embed = q_res.x.view(-1, self.dim)
887
+ else:
888
+ out_embed = embed
889
+ out_embed = self.output_proj(out_embed).view(-1, 1, self.output_dim)
890
+ mask = torch.ones(*out_embed.shape[:2], device=out_embed.device)
891
+ mask[empty_idx, :] = 0 # zero-out index where the input is non-existant
892
+ out_embed = (out_embed * mask.unsqueeze(-1))
893
+ return out_embed, mask
894
+
895
+ def tokenize(self, x: JointEmbedCondition) -> JointEmbedCondition:
896
+ return x
897
+
898
+
899
+ class CLAPEmbeddingConditioner(JointEmbeddingConditioner):
900
+ """Joint Embedding conditioner based on pre-trained CLAP model.
901
+
902
+ This CLAP-based conditioner supports a caching mechanism
903
+ over the computed embeddings for faster training.
904
+
905
+ Args:
906
+ dim (int): Dimension.
907
+ output_dim (int): Output dimension.
908
+ device (str): Device.
909
+ attribute (str): Attribute used by the conditioner.
910
+ quantize (bool): Whether to quantize the CLAP embedding.
911
+ n_q (int): Number of residual quantizers (used if quantize is true).
912
+ bins (int): Quantizers' codebooks size (used if quantize is true).
913
+ checkpoint (str): Path to CLAP checkpoint.
914
+ model_arch (str): CLAP model architecture.
915
+ enable_fusion (bool): Enable fusion for CLAP model.
916
+ sample_rate (int): Sample rate used by CLAP model.
917
+ max_audio_length (float): Maximum audio length for CLAP model.
918
+ audio_stride (float): Stride to use for getting a CLAP embedding on the full sequence.
919
+ normalize (bool): Whether to normalize the CLAP embedding.
920
+ text_p (float): Probability of using text representation instead of audio at train time.
921
+ batch_size (Optional[int]): Batch size for CLAP embedding computation.
922
+ autocast_dtype (str): Autocast for the conditioner.
923
+ cache_path (Optional[str]): Path for pre-computed embeddings caching.
924
+ kwargs: Additional parameters for residual vector quantizer.
925
+ """
926
+ def __init__(self, dim: int, output_dim: int, device: str, attribute: str,
927
+ quantize: bool, n_q: int, bins: int, checkpoint: tp.Union[str, Path], model_arch: str,
928
+ enable_fusion: bool, sample_rate: int, max_audio_length: int, audio_stride: int,
929
+ normalize: bool, text_p: bool, batch_size: tp.Optional[int] = None,
930
+ autocast_dtype: tp.Optional[str] = 'float32', cache_path: tp.Optional[str] = None, **kwargs):
931
+ try:
932
+ import laion_clap # type: ignore
933
+ except ImportError:
934
+ raise ImportError("Please install CLAP to use the CLAPEmbeddingConditioner: 'pip install laion_clap'")
935
+ checkpoint = AudioCraftEnvironment.resolve_reference_path(checkpoint)
936
+ clap_tokenize = RobertaTokenizer.from_pretrained('roberta-base')
937
+ clap_model = laion_clap.CLAP_Module(enable_fusion=enable_fusion, amodel=model_arch)
938
+ load_clap_state_dict(clap_model, checkpoint)
939
+ clap_model.eval()
940
+ clap_model.to(device)
941
+ super().__init__(dim=dim, output_dim=output_dim, device=device, attribute=attribute,
942
+ autocast_dtype=autocast_dtype, quantize=quantize, n_q=n_q, bins=bins,
943
+ **kwargs)
944
+ self.checkpoint = checkpoint
945
+ self.enable_fusion = enable_fusion
946
+ self.model_arch = model_arch
947
+ self.clap: laion_clap.CLAP_Module
948
+ self.clap_tokenize: RobertaTokenizer
949
+ self.clap_sample_rate = sample_rate
950
+ self.clap_max_frames = int(self.clap_sample_rate * max_audio_length)
951
+ self.clap_stride = int(self.clap_sample_rate * audio_stride)
952
+ self.batch_size = batch_size or 1
953
+ self.normalize = normalize
954
+ self.text_p = text_p
955
+ self.__dict__['clap_tokenize'] = clap_tokenize
956
+ self.__dict__['clap'] = clap_model
957
+ self.wav_cache, self.text_cache = None, None
958
+ if cache_path is not None:
959
+ self.wav_cache = EmbeddingCache(Path(cache_path) / 'wav', self.device,
960
+ compute_embed_fn=self._get_wav_embedding_for_cache,
961
+ extract_embed_fn=self._extract_wav_embedding_chunk)
962
+ self.text_cache = EmbeddingCache(Path(cache_path) / 'text', self.device,
963
+ compute_embed_fn=self._get_text_embedding_for_cache)
964
+
965
+ def _tokenizer(self, texts: tp.Union[str, tp.List[str]]) -> dict:
966
+ # we use the default params from CLAP module here as well
967
+ return self.clap_tokenize(texts, padding="max_length", truncation=True, max_length=77, return_tensors="pt")
968
+
969
+ def _compute_text_embedding(self, text: tp.List[str]) -> torch.Tensor:
970
+ """Compute text embedding from CLAP model on a given a batch of text.
971
+
972
+ Args:
973
+ text (list[str]): List of text for the batch, with B items.
974
+ Returns:
975
+ torch.Tensor: CLAP embedding derived from text, of shape [B, 1, D], with D the CLAP embedding dimension.
976
+ """
977
+ with torch.no_grad():
978
+ embed = self.clap.get_text_embedding(text, tokenizer=self._tokenizer, use_tensor=True)
979
+ return embed.view(embed.size(0), 1, embed.size(-1))
980
+
981
+ def _get_text_embedding_for_cache(self, path: tp.Union[Path, str],
982
+ x: JointEmbedCondition, idx: int) -> torch.Tensor:
983
+ """Get text embedding function for the cache."""
984
+ text = x.text[idx]
985
+ text = text if text is not None else ""
986
+ return self._compute_text_embedding([text])[0]
987
+
988
+ def _preprocess_wav(self, wav: torch.Tensor, length: torch.Tensor, sample_rates: tp.List[int]) -> torch.Tensor:
989
+ """Preprocess wav to expected format by CLAP model.
990
+
991
+ Args:
992
+ wav (torch.Tensor): Audio wav, of shape [B, C, T].
993
+ length (torch.Tensor): Actual length of the audio for each item in the batch, of shape [B].
994
+ sample_rates (list[int]): Sample rates for each sample in the batch
995
+ Returns:
996
+ torch.Tensor: Audio wav of shape [B, T].
997
+ """
998
+ assert wav.dim() == 3, "Expecting wav to be [B, C, T]"
999
+ if sample_rates is not None:
1000
+ _wav = []
1001
+ for i, audio in enumerate(wav):
1002
+ sr = sample_rates[i]
1003
+ audio = convert_audio(audio, from_rate=sr, to_rate=self.clap_sample_rate, to_channels=1)
1004
+ _wav.append(audio)
1005
+ wav = torch.stack(_wav, dim=0)
1006
+ wav = wav.mean(dim=1)
1007
+ return wav
1008
+
1009
+ def _compute_wav_embedding(self, wav: torch.Tensor, length: torch.Tensor,
1010
+ sample_rates: tp.List[int], reduce_mean: bool = False) -> torch.Tensor:
1011
+ """Compute audio wave embedding from CLAP model.
1012
+
1013
+ Since CLAP operates on a fixed sequence length audio inputs and we need to process longer audio sequences,
1014
+ we calculate the wav embeddings on `clap_max_frames` windows with `clap_stride`-second stride and
1015
+ average the resulting embeddings.
1016
+
1017
+ Args:
1018
+ wav (torch.Tensor): Audio wav, of shape [B, C, T].
1019
+ length (torch.Tensor): Actual length of the audio for each item in the batch, of shape [B].
1020
+ sample_rates (list[int]): Sample rates for each sample in the batch.
1021
+ reduce_mean (bool): Whether to get the average tensor.
1022
+ Returns:
1023
+ torch.Tensor: Audio embedding of shape [B, F, D], F being the number of chunks, D the dimension.
1024
+ """
1025
+ with torch.no_grad():
1026
+ wav = self._preprocess_wav(wav, length, sample_rates)
1027
+ B, T = wav.shape
1028
+ if T >= self.clap_max_frames:
1029
+ wav = wav.unfold(-1, self.clap_max_frames, self.clap_stride) # [B, F, T]
1030
+ else:
1031
+ wav = wav.view(-1, 1, T) # [B, F, T] with F=1
1032
+ wav = einops.rearrange(wav, 'b f t -> (b f) t')
1033
+ embed_list = []
1034
+ for i in range(0, wav.size(0), self.batch_size):
1035
+ _wav = wav[i:i+self.batch_size, ...]
1036
+ _embed = self.clap.get_audio_embedding_from_data(_wav, use_tensor=True)
1037
+ embed_list.append(_embed)
1038
+ embed = torch.cat(embed_list, dim=0)
1039
+ embed = einops.rearrange(embed, '(b f) d -> b f d', b=B)
1040
+ if reduce_mean:
1041
+ embed = embed.mean(dim=1, keepdim=True)
1042
+ return embed # [B, F, D] with F=1 if reduce_mean is True
1043
+
1044
+ def _get_wav_embedding_for_cache(self, path: tp.Union[str, Path],
1045
+ x: JointEmbedCondition, idx: int) -> torch.Tensor:
1046
+ """Compute audio wave embedding for the cache.
1047
+ The embedding is computed on a given audio read from file.
1048
+
1049
+ Args:
1050
+ path (str or Path): Path to the full audio file.
1051
+ Returns:
1052
+ torch.Tensor: Single-item tensor of shape [F, D], F being the number of chunks, D the dimension.
1053
+ """
1054
+ wav, sr = audio_read(path) # [C, T]
1055
+ wav = wav.unsqueeze(0).to(self.device) # [1, C, T]
1056
+ wav_len = torch.LongTensor([wav.shape[-1]]).to(self.device)
1057
+ embed = self._compute_wav_embedding(wav, wav_len, [sr], reduce_mean=False) # [B, F, D]
1058
+ return embed.squeeze(0) # [F, D]
1059
+
1060
+ def _extract_wav_embedding_chunk(self, full_embed: torch.Tensor, x: JointEmbedCondition, idx: int) -> torch.Tensor:
1061
+ """Extract the chunk of embedding matching the seek_time and length from the full CLAP audio embedding.
1062
+
1063
+ Args:
1064
+ full_embed (torch.Tensor): CLAP embedding computed on the full wave, of shape [F, D].
1065
+ x (JointEmbedCondition): Joint embedding condition for the full batch.
1066
+ idx (int): Index considered for the given embedding to extract.
1067
+ Returns:
1068
+ torch.Tensor: Wav embedding averaged on sliding window, of shape [1, D].
1069
+ """
1070
+ sample_rate = x.sample_rate[idx]
1071
+ seek_time = x.seek_time[idx]
1072
+ seek_time = 0. if seek_time is None else seek_time
1073
+ clap_stride = int(self.clap_stride / self.clap_sample_rate) * sample_rate
1074
+ end_seek_time = seek_time + self.clap_max_frames / self.clap_sample_rate
1075
+ start_offset = int(seek_time * sample_rate // clap_stride)
1076
+ end_offset = int(end_seek_time * sample_rate // clap_stride)
1077
+ wav_embed = full_embed[start_offset:end_offset, ...]
1078
+ wav_embed = wav_embed.mean(dim=0, keepdim=True)
1079
+ return wav_embed.to(self.device) # [F, D]
1080
+
1081
+ def _get_text_embedding(self, x: JointEmbedCondition) -> torch.Tensor:
1082
+ """Get CLAP embedding from a batch of text descriptions."""
1083
+ no_nullified_cond = x.wav.shape[-1] > 1 # we don't want to read from cache when condition dropout
1084
+ if self.text_cache is not None and no_nullified_cond:
1085
+ assert all(p is not None for p in x.path), "Cache requires all JointEmbedCondition paths to be provided"
1086
+ paths = [Path(p) for p in x.path if p is not None]
1087
+ embed = self.text_cache.get_embed_from_cache(paths, x)
1088
+ else:
1089
+ text = [xi if xi is not None else "" for xi in x.text]
1090
+ embed = self._compute_text_embedding(text)
1091
+ if self.normalize:
1092
+ embed = torch.nn.functional.normalize(embed, p=2.0, dim=-1)
1093
+ return embed
1094
+
1095
+ def _get_wav_embedding(self, x: JointEmbedCondition) -> torch.Tensor:
1096
+ """Get CLAP embedding from a batch of audio tensors (and corresponding sample rates)."""
1097
+ no_undefined_paths = all(p is not None for p in x.path)
1098
+ no_nullified_cond = x.wav.shape[-1] > 1 # we don't want to read from cache when condition dropout
1099
+ if self.wav_cache is not None and no_undefined_paths and no_nullified_cond:
1100
+ paths = [Path(p) for p in x.path if p is not None]
1101
+ embed = self.wav_cache.get_embed_from_cache(paths, x)
1102
+ else:
1103
+ embed = self._compute_wav_embedding(x.wav, x.length, x.sample_rate, reduce_mean=True)
1104
+ if self.normalize:
1105
+ embed = torch.nn.functional.normalize(embed, p=2.0, dim=-1)
1106
+ return embed
1107
+
1108
+ def tokenize(self, x: JointEmbedCondition) -> JointEmbedCondition:
1109
+ # Trying to limit as much as possible sync points when the cache is warm.
1110
+ no_undefined_paths = all(p is not None for p in x.path)
1111
+ if self.wav_cache is not None and no_undefined_paths:
1112
+ assert all([p is not None for p in x.path]), "Cache requires all JointEmbedCondition paths to be provided"
1113
+ paths = [Path(p) for p in x.path if p is not None]
1114
+ self.wav_cache.populate_embed_cache(paths, x)
1115
+ if self.text_cache is not None and no_undefined_paths:
1116
+ assert all([p is not None for p in x.path]), "Cache requires all JointEmbedCondition paths to be provided"
1117
+ paths = [Path(p) for p in x.path if p is not None]
1118
+ self.text_cache.populate_embed_cache(paths, x)
1119
+ return x
1120
+
1121
+ def _get_embed(self, x: JointEmbedCondition) -> tp.Tuple[torch.Tensor, torch.Tensor]:
1122
+ """Extract shared latent representation from either the wav or the text using CLAP."""
1123
+ # decide whether to use text embedding at train time or not
1124
+ use_text_embed = random.random() < self.text_p
1125
+ if self.training and not use_text_embed:
1126
+ embed = self._get_wav_embedding(x)
1127
+ empty_idx = torch.LongTensor([]) # we assume we always have the audio wav
1128
+ else:
1129
+ embed = self._get_text_embedding(x)
1130
+ empty_idx = torch.LongTensor([i for i, xi in enumerate(x.text) if xi is None or xi == ""])
1131
+ return embed, empty_idx
1132
+
1133
+
1134
+ def dropout_condition(sample: ConditioningAttributes, condition_type: str, condition: str) -> ConditioningAttributes:
1135
+ """Utility function for nullifying an attribute inside an ConditioningAttributes object.
1136
+ If the condition is of type "wav", then nullify it using `nullify_condition` function.
1137
+ If the condition is of any other type, set its value to None.
1138
+ Works in-place.
1139
+ """
1140
+ if condition_type not in ['text', 'wav', 'beat', 'chord', 'joint_embed']:
1141
+ raise ValueError(
1142
+ "dropout_condition got an unexpected condition type!"
1143
+ f" expected 'text', 'wav' or 'joint_embed' but got '{condition_type}'"
1144
+ )
1145
+
1146
+ if condition not in getattr(sample, condition_type):
1147
+ raise ValueError(
1148
+ "dropout_condition received an unexpected condition!"
1149
+ f" expected wav={sample.wav.keys()} and text={sample.text.keys()}"
1150
+ f" but got '{condition}' of type '{condition_type}'!"
1151
+ )
1152
+
1153
+ if condition_type == 'wav':
1154
+ wav_cond = sample.wav[condition]
1155
+ sample.wav[condition] = nullify_wav(wav_cond)
1156
+ elif condition_type == 'beat':
1157
+ beat_cond = sample.beat[condition]
1158
+ sample.beat[condition] = nullify_beat(beat_cond)
1159
+ elif condition_type == 'chord':
1160
+ chord_cond = sample.chord[condition]
1161
+ sample.chord[condition] = nullify_chord(chord_cond)
1162
+ elif condition_type == 'joint_embed':
1163
+ embed = sample.joint_embed[condition]
1164
+ sample.joint_embed[condition] = nullify_joint_embed(embed)
1165
+ else:
1166
+ sample.text[condition] = None
1167
+
1168
+ return sample
1169
+
1170
+
1171
+ class DropoutModule(nn.Module):
1172
+ """Base module for all dropout modules."""
1173
+ def __init__(self, seed: int = 1234):
1174
+ super().__init__()
1175
+ self.rng = torch.Generator()
1176
+ self.rng.manual_seed(seed)
1177
+
1178
+
1179
+ class AttributeDropout(DropoutModule):
1180
+ """Dropout with a given probability per attribute.
1181
+ This is different from the behavior of ClassifierFreeGuidanceDropout as this allows for attributes
1182
+ to be dropped out separately. For example, "artist" can be dropped while "genre" remains.
1183
+ This is in contrast to ClassifierFreeGuidanceDropout where if "artist" is dropped "genre"
1184
+ must also be dropped.
1185
+
1186
+ Args:
1187
+ p (tp.Dict[str, float]): A dict mapping between attributes and dropout probability. For example:
1188
+ ...
1189
+ "genre": 0.1,
1190
+ "artist": 0.5,
1191
+ "wav": 0.25,
1192
+ ...
1193
+ active_on_eval (bool, optional): Whether the dropout is active at eval. Default to False.
1194
+ seed (int, optional): Random seed.
1195
+ """
1196
+ def __init__(self, p: tp.Dict[str, tp.Dict[str, float]], active_on_eval: bool = False, seed: int = 1234):
1197
+ super().__init__(seed=seed)
1198
+ self.active_on_eval = active_on_eval
1199
+ # construct dict that return the values from p otherwise 0
1200
+ self.p = {}
1201
+ for condition_type, probs in p.items():
1202
+ self.p[condition_type] = defaultdict(lambda: 0, probs)
1203
+
1204
+ def forward(self, samples: tp.List[ConditioningAttributes]) -> tp.List[ConditioningAttributes]:
1205
+ """
1206
+ Args:
1207
+ samples (list[ConditioningAttributes]): List of conditions.
1208
+ Returns:
1209
+ list[ConditioningAttributes]: List of conditions after certain attributes were set to None.
1210
+ """
1211
+ if not self.training and not self.active_on_eval:
1212
+ return samples
1213
+
1214
+ samples = deepcopy(samples)
1215
+ for condition_type, ps in self.p.items(): # for condition types [text, wav]
1216
+ for condition, p in ps.items(): # for attributes of each type (e.g., [artist, genre])
1217
+ if torch.rand(1, generator=self.rng).item() < p:
1218
+ for sample in samples:
1219
+ dropout_condition(sample, condition_type, condition)
1220
+ return samples
1221
+
1222
+ def __repr__(self):
1223
+ return f"AttributeDropout({dict(self.p)})"
1224
+
1225
+
1226
+ class ClassifierFreeGuidanceDropout(DropoutModule):
1227
+ """Classifier Free Guidance dropout.
1228
+ All attributes are dropped with the same probability.
1229
+
1230
+ Args:
1231
+ p (float): Probability to apply condition dropout during training.
1232
+ seed (int): Random seed.
1233
+ """
1234
+ def __init__(self, p: float, seed: int = 1234):
1235
+ super().__init__(seed=seed)
1236
+ self.p = p
1237
+
1238
+ def forward(self, samples: tp.List[ConditioningAttributes]) -> tp.List[ConditioningAttributes]:
1239
+ """
1240
+ Args:
1241
+ samples (list[ConditioningAttributes]): List of conditions.
1242
+ Returns:
1243
+ list[ConditioningAttributes]: List of conditions after all attributes were set to None.
1244
+ """
1245
+ if not self.training:
1246
+ return samples
1247
+
1248
+ # decide on which attributes to drop in a batched fashion
1249
+ drop = torch.rand(1, generator=self.rng).item() < self.p
1250
+ if not drop:
1251
+ return samples
1252
+
1253
+ # nullify conditions of all attributes
1254
+ samples = deepcopy(samples)
1255
+ for condition_type in ["wav", "text", "beat", "chord"]:
1256
+ for sample in samples:
1257
+ for condition in sample.attributes[condition_type]:
1258
+ dropout_condition(sample, condition_type, condition)
1259
+ return samples
1260
+
1261
+ def __repr__(self):
1262
+ return f"ClassifierFreeGuidanceDropout(p={self.p})"
1263
+
1264
+
1265
+ class ConditioningProvider(nn.Module):
1266
+ """Prepare and provide conditions given all the supported conditioners.
1267
+
1268
+ Args:
1269
+ conditioners (dict): Dictionary of conditioners.
1270
+ device (torch.device or str, optional): Device for conditioners and output condition types.
1271
+ """
1272
+ def __init__(self, conditioners: tp.Dict[str, BaseConditioner], device: tp.Union[torch.device, str] = "cpu"):
1273
+ super().__init__()
1274
+ self.device = device
1275
+ self.conditioners = nn.ModuleDict(conditioners)
1276
+
1277
+ @property
1278
+ def joint_embed_conditions(self):
1279
+ return [m.attribute for m in self.conditioners.values() if isinstance(m, JointEmbeddingConditioner)]
1280
+
1281
+ @property
1282
+ def has_joint_embed_conditions(self):
1283
+ return len(self.joint_embed_conditions) > 0
1284
+
1285
+ @property
1286
+ def text_conditions(self):
1287
+ return [k for k, v in self.conditioners.items() if isinstance(v, TextConditioner)]
1288
+
1289
+ @property
1290
+ def wav_conditions(self):
1291
+ return [k for k, v in self.conditioners.items() if isinstance(v, WaveformConditioner)]
1292
+
1293
+ @property
1294
+ def beat_conditions(self):
1295
+ return [k for k, v in self.conditioners.items() if isinstance(v, BeatConditioner)]
1296
+
1297
+ @property
1298
+ def chord_conditions(self):
1299
+ return [k for k, v in self.conditioners.items() if isinstance(v, ChordProgressionConditioner)]
1300
+
1301
+ @property
1302
+ def has_wav_condition(self):
1303
+ return len(self.wav_conditions) > 0
1304
+
1305
+ def tokenize(self, inputs: tp.List[ConditioningAttributes]) -> tp.Dict[str, tp.Any]:
1306
+ """Match attributes/wavs with existing conditioners in self, and compute tokenize them accordingly.
1307
+ This should be called before starting any real GPU work to avoid synchronization points.
1308
+ This will return a dict matching conditioner names to their arbitrary tokenized representations.
1309
+
1310
+ Args:
1311
+ inputs (list[ConditioningAttributes]): List of ConditioningAttributes objects containing
1312
+ text and wav conditions.
1313
+ """
1314
+ assert all([isinstance(x, ConditioningAttributes) for x in inputs]), (
1315
+ "Got unexpected types input for conditioner! should be tp.List[ConditioningAttributes]",
1316
+ f" but types were {set([type(x) for x in inputs])}"
1317
+ )
1318
+
1319
+ output = {}
1320
+ text = self._collate_text(inputs)
1321
+ beats = self._collate_beats(inputs)
1322
+ chords = self._collate_chords(inputs)
1323
+ wavs = self._collate_wavs(inputs)
1324
+ joint_embeds = self._collate_joint_embeds(inputs)
1325
+
1326
+ assert set(text.keys() | wavs.keys() | chords.keys() | beats.keys() | joint_embeds.keys()).issubset(set(self.conditioners.keys())), (
1327
+ f"Got an unexpected attribute! Expected {self.conditioners.keys()}, ",
1328
+ f"got {text.keys(), wavs.keys(), chords.keys(), beats.keys(), joint_embeds.keys()}"
1329
+ )
1330
+
1331
+ for attribute, batch in chain(text.items(), wavs.items(), chords.items(), beats.items(), joint_embeds.items()):
1332
+ output[attribute] = self.conditioners[attribute].tokenize(batch)
1333
+ return output
1334
+
1335
+ def forward(self, tokenized: tp.Dict[str, tp.Any]) -> tp.Dict[str, ConditionType]:
1336
+ """Compute pairs of `(embedding, mask)` using the configured conditioners and the tokenized representations.
1337
+ The output is for example:
1338
+ {
1339
+ "genre": (torch.Tensor([B, 1, D_genre]), torch.Tensor([B, 1])),
1340
+ "description": (torch.Tensor([B, T_desc, D_desc]), torch.Tensor([B, T_desc])),
1341
+ ...
1342
+ }
1343
+
1344
+ Args:
1345
+ tokenized (dict): Dict of tokenized representations as returned by `tokenize()`.
1346
+ """
1347
+ output = {}
1348
+ for attribute, inputs in tokenized.items():
1349
+ condition, mask = self.conditioners[attribute](inputs)
1350
+ output[attribute] = (condition, mask)
1351
+ return output
1352
+
1353
+ def _collate_text(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, tp.List[tp.Optional[str]]]:
1354
+ """Given a list of ConditioningAttributes objects, compile a dictionary where the keys
1355
+ are the attributes and the values are the aggregated input per attribute.
1356
+ For example:
1357
+ Input:
1358
+ [
1359
+ ConditioningAttributes(text={"genre": "Rock", "description": "A rock song with a guitar solo"}, wav=...),
1360
+ ConditioningAttributes(text={"genre": "Hip-hop", "description": "A hip-hop verse"}, wav=...),
1361
+ ]
1362
+ Output:
1363
+ {
1364
+ "genre": ["Rock", "Hip-hop"],
1365
+ "description": ["A rock song with a guitar solo", "A hip-hop verse"]
1366
+ }
1367
+
1368
+ Args:
1369
+ samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
1370
+ Returns:
1371
+ dict[str, list[str, optional]]: A dictionary mapping an attribute name to text batch.
1372
+ """
1373
+ out: tp.Dict[str, tp.List[tp.Optional[str]]] = defaultdict(list)
1374
+ texts = [x.text for x in samples]
1375
+ for text in texts:
1376
+ for condition in self.text_conditions:
1377
+ out[condition].append(text[condition])
1378
+ return out
1379
+
1380
+ def _collate_wavs(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, WavCondition]:
1381
+ """Generate a dict where the keys are attributes by which we fetch similar wavs,
1382
+ and the values are Tensors of wavs according to said attributes.
1383
+
1384
+ *Note*: by the time the samples reach this function, each sample should have some waveform
1385
+ inside the "wav" attribute. It should be either:
1386
+ 1. A real waveform
1387
+ 2. A null waveform due to the sample having no similar waveforms (nullified by the dataset)
1388
+ 3. A null waveform due to it being dropped in a dropout module (nullified by dropout)
1389
+
1390
+ Args:
1391
+ samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
1392
+ Returns:
1393
+ dict[str, WavCondition]: A dictionary mapping an attribute name to wavs.
1394
+ """
1395
+ wavs = defaultdict(list)
1396
+ lengths = defaultdict(list)
1397
+ sample_rates = defaultdict(list)
1398
+ paths = defaultdict(list)
1399
+ seek_times = defaultdict(list)
1400
+ out: tp.Dict[str, WavCondition] = {}
1401
+
1402
+ for sample in samples:
1403
+ for attribute in self.wav_conditions:
1404
+ wav, length, sample_rate, path, seek_time = sample.wav[attribute]
1405
+ assert wav.dim() == 3, f"Got wav with dim={wav.dim()}, but expected 3 [1, C, T]"
1406
+ assert wav.size(0) == 1, f"Got wav [B, C, T] with shape={wav.shape}, but expected B == 1"
1407
+ # mono-channel conditioning
1408
+ wav = wav.mean(1, keepdim=True) # [1, 1, T]
1409
+ wavs[attribute].append(wav.flatten()) # [T]
1410
+ lengths[attribute].append(length)
1411
+ sample_rates[attribute].extend(sample_rate)
1412
+ paths[attribute].extend(path)
1413
+ seek_times[attribute].extend(seek_time)
1414
+
1415
+ # stack all wavs to a single tensor
1416
+ for attribute in self.wav_conditions:
1417
+ stacked_wav, _ = collate(wavs[attribute], dim=0)
1418
+ out[attribute] = WavCondition(
1419
+ stacked_wav.unsqueeze(1), torch.cat(lengths[attribute]), sample_rates[attribute],
1420
+ paths[attribute], seek_times[attribute])
1421
+
1422
+ return out
1423
+
1424
+ def _collate_chords(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, ChordCondition]:
1425
+ """Generate a dict where the keys are attributes by which we fetch similar wavs,
1426
+ and the values are Tensors of wavs according to said attributes.
1427
+
1428
+ *Note*: by the time the samples reach this function, each sample should have some waveform
1429
+ inside the "wav" attribute. It should be either:
1430
+ 1. A real waveform
1431
+ 2. A null waveform due to the sample having no similar waveforms (nullified by the dataset)
1432
+ 3. A null waveform due to it being dropped in a dropout module (nullified by dropout)
1433
+
1434
+ Args:
1435
+ samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
1436
+ Returns:
1437
+ dict[str, WavCondition]: A dictionary mapping an attribute name to wavs.
1438
+ """
1439
+ chords = defaultdict(list)
1440
+ lengths = defaultdict(list)
1441
+ bpms = defaultdict(list)
1442
+ paths = defaultdict(list)
1443
+ seek_frames = defaultdict(list)
1444
+ out: tp.Dict[str, ChordCondition] = {}
1445
+
1446
+ for sample in samples: # sample = ConditioningAttributes(text={"genre": "Rock", "description": "A rock song with a guitar solo"}, wav=...)
1447
+ for attribute in self.chord_conditions: # self.chord_conditions = ['chord']
1448
+ chord, length, bpm, path, seek_frame = sample.chord[attribute]
1449
+ assert chord.dim() == 3, f"Got chord with dim={chord.dim()}, but expected 3 [1, C, T]"
1450
+ assert chord.size(0) == 1, f"Got chord [B, C, T] with shape={chord.shape}, but expected B == 1"
1451
+ chords[attribute].append(chord.squeeze(0)) # [1, C, T] -> [N * [C, T]]
1452
+ lengths[attribute].append(length) # [N, 1]
1453
+ bpms[attribute].extend(bpm) # [N]
1454
+ paths[attribute].extend(path) # [N]
1455
+ seek_frames[attribute].extend(seek_frame) # [N]
1456
+
1457
+ # stack all chords to a single tensor
1458
+ for attribute in self.chord_conditions:
1459
+ stacked_chord, _ = collate(chords[attribute], dim=1) # tensor padded here
1460
+ out[attribute] = ChordCondition(
1461
+ stacked_chord, torch.cat(lengths[attribute]), bpms[attribute],
1462
+ paths[attribute], seek_frames[attribute])
1463
+ # print(f"chords shape: {chords[attribute][0].shape}")
1464
+ # print(f"stack chords shape: {stacked_chord.shape}")
1465
+ return out
1466
+
1467
+ def _collate_beats(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, ChordCondition]:
1468
+ """Generate a dict where the keys are attributes by which we fetch similar wavs,
1469
+ and the values are Tensors of wavs according to said attributes.
1470
+
1471
+ Args:
1472
+ samples (list of ConditioningAttributes): List of ConditioningAttributes samples.
1473
+ Returns:
1474
+ dict[str, WavCondition]: A dictionary mapping an attribute name to wavs.
1475
+ """
1476
+ beats = defaultdict(list)
1477
+ lengths = defaultdict(list)
1478
+ bpms = defaultdict(list)
1479
+ paths = defaultdict(list)
1480
+ seek_frames = defaultdict(list)
1481
+ out: tp.Dict[str, ChordCondition] = {}
1482
+
1483
+ for sample in samples: # sample = ConditioningAttributes(text={"genre": "Rock", "description": "A rock song with a guitar solo"}, wav=...)
1484
+ for attribute in self.beat_conditions: # self.chord_conditions = ['chord']
1485
+ beat, length, bpm, path, seek_frame = sample.beat[attribute]
1486
+ assert beat.dim() == 3, f"Got chord with dim={beat.dim()}, but expected 3 [1, C, T]"
1487
+ assert beat.size(0) == 1, f"Got chord [B, C, T] with shape={beat.shape}, but expected B == 1"
1488
+ beats[attribute].append(beat.squeeze(0)) # [1, C, T] -> [N * [C, T]]
1489
+ lengths[attribute].append(length) # [N, 1]
1490
+ bpms[attribute].extend(bpm) # [N]
1491
+ paths[attribute].extend(path) # [N]
1492
+ seek_frames[attribute].extend(seek_frame) # [N]
1493
+
1494
+ # stack all chords to a single tensor
1495
+ for attribute in self.beat_conditions:
1496
+ stacked_beat, _ = collate(beats[attribute], dim=1) # tensor padded here
1497
+ out[attribute] = BeatCondition(
1498
+ stacked_beat, torch.cat(lengths[attribute]), bpms[attribute],
1499
+ paths[attribute], seek_frames[attribute])
1500
+ # print(f"chords shape: {chords[attribute][0].shape}")
1501
+ # print(f"stack chords shape: {stacked_chord.shape}")
1502
+ return out
1503
+
1504
+ def _collate_joint_embeds(self, samples: tp.List[ConditioningAttributes]) -> tp.Dict[str, JointEmbedCondition]:
1505
+ """Generate a dict where the keys are attributes by which we compute joint embeddings,
1506
+ and the values are Tensors of pre-computed embeddings and the corresponding text attributes.
1507
+
1508
+ Args:
1509
+ samples (list[ConditioningAttributes]): List of ConditioningAttributes samples.
1510
+ Returns:
1511
+ A dictionary mapping an attribute name to joint embeddings.
1512
+ """
1513
+ texts = defaultdict(list)
1514
+ wavs = defaultdict(list)
1515
+ lengths = defaultdict(list)
1516
+ sample_rates = defaultdict(list)
1517
+ paths = defaultdict(list)
1518
+ seek_times = defaultdict(list)
1519
+ channels: int = 0
1520
+
1521
+ out = {}
1522
+ for sample in samples:
1523
+ for attribute in self.joint_embed_conditions:
1524
+ wav, text, length, sample_rate, path, seek_time = sample.joint_embed[attribute]
1525
+ assert wav.dim() == 3
1526
+ if channels == 0:
1527
+ channels = wav.size(1)
1528
+ else:
1529
+ assert channels == wav.size(1), "not all audio has same number of channels in batch"
1530
+ assert wav.size(0) == 1, "Expecting single-wav batch in the collate method"
1531
+ wav = einops.rearrange(wav, "b c t -> (b c t)") # [1, C, T] => [C * T]
1532
+ wavs[attribute].append(wav)
1533
+ texts[attribute].extend(text)
1534
+ lengths[attribute].append(length)
1535
+ sample_rates[attribute].extend(sample_rate)
1536
+ paths[attribute].extend(path)
1537
+ seek_times[attribute].extend(seek_time)
1538
+
1539
+ for attribute in self.joint_embed_conditions:
1540
+ stacked_texts = texts[attribute]
1541
+ stacked_paths = paths[attribute]
1542
+ stacked_seek_times = seek_times[attribute]
1543
+ stacked_wavs = pad_sequence(wavs[attribute]).to(self.device)
1544
+ stacked_wavs = einops.rearrange(stacked_wavs, "(c t) b -> b c t", c=channels)
1545
+ stacked_sample_rates = sample_rates[attribute]
1546
+ stacked_lengths = torch.cat(lengths[attribute]).to(self.device)
1547
+ assert stacked_lengths.size(0) == stacked_wavs.size(0)
1548
+ assert len(stacked_sample_rates) == stacked_wavs.size(0)
1549
+ assert len(stacked_texts) == stacked_wavs.size(0)
1550
+ out[attribute] = JointEmbedCondition(
1551
+ text=stacked_texts, wav=stacked_wavs,
1552
+ length=stacked_lengths, sample_rate=stacked_sample_rates,
1553
+ path=stacked_paths, seek_time=stacked_seek_times)
1554
+
1555
+ return out
1556
+
1557
+
1558
+ class ConditionFuser(StreamingModule):
1559
+ """Condition fuser handles the logic to combine the different conditions
1560
+ to the actual model input.
1561
+
1562
+ Args:
1563
+ fuse2cond (tp.Dict[str, str]): A dictionary that says how to fuse
1564
+ each condition. For example:
1565
+ {
1566
+ "prepend": ["description"],
1567
+ "sum": ["genre", "bpm"],
1568
+ "cross": ["description"],
1569
+ }
1570
+ cross_attention_pos_emb (bool, optional): Use positional embeddings in cross attention.
1571
+ cross_attention_pos_emb_scale (int): Scale for positional embeddings in cross attention if used.
1572
+ """
1573
+ FUSING_METHODS = ["sum", "prepend", "cross", "input_interpolate", "concat"]
1574
+
1575
+ def __init__(self, fuse2cond: tp.Dict[str, tp.List[str]], cross_attention_pos_emb: bool = False,
1576
+ cross_attention_pos_emb_scale: float = 1.0, in_attn: bool = False):
1577
+ super().__init__()
1578
+ assert all(
1579
+ [k in self.FUSING_METHODS for k in fuse2cond.keys()]
1580
+ ), f"Got invalid fuse method, allowed methods: {self.FUSING_METHODS}"
1581
+ self.cross_attention_pos_emb = cross_attention_pos_emb
1582
+ self.cross_attention_pos_emb_scale = cross_attention_pos_emb_scale
1583
+ self.fuse2cond: tp.Dict[str, tp.List[str]] = fuse2cond
1584
+ self.cond2fuse: tp.Dict[str, str] = {}
1585
+ self.in_attn = in_attn
1586
+
1587
+ for fuse_method, conditions in fuse2cond.items():
1588
+ for condition in conditions:
1589
+ if not condition in self.cond2fuse.keys():
1590
+ self.cond2fuse[condition] = [fuse_method]
1591
+ else:
1592
+ self.cond2fuse[condition].append(fuse_method)
1593
+
1594
+
1595
+ def forward(
1596
+ self,
1597
+ input: torch.Tensor,
1598
+ conditions: tp.Dict[str, ConditionType]
1599
+ ) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
1600
+ """Fuse the conditions to the provided model input.
1601
+
1602
+ Args:
1603
+ input (torch.Tensor): Transformer input.
1604
+ conditions (dict[str, ConditionType]): Dict of conditions.
1605
+ Returns:
1606
+ tuple[torch.Tensor, torch.Tensor]: The first tensor is the transformer input
1607
+ after the conditions have been fused. The second output tensor is the tensor
1608
+ used for cross-attention or None if no cross attention inputs exist.
1609
+ """
1610
+
1611
+ B, T, _ = input.shape # [B, T, C]
1612
+ if self.in_attn:
1613
+ in_attn_cond = torch.zeros_like(input)
1614
+ else:
1615
+ in_attn_cond = None
1616
+
1617
+ if 'offsets' in self._streaming_state:
1618
+ first_step = False
1619
+ offsets = self._streaming_state['offsets']
1620
+ else:
1621
+ first_step = True
1622
+ offsets = torch.zeros(B, dtype=torch.long, device=input.device)
1623
+
1624
+ assert set(conditions.keys()).issubset(set(self.cond2fuse.keys())), \
1625
+ f"given conditions contain unknown attributes for fuser, " \
1626
+ f"expected {self.cond2fuse.keys()}, got {conditions.keys()}"
1627
+ cross_attention_output = None
1628
+
1629
+ for cond_type, (cond, cond_mask) in conditions.items():
1630
+ fuse_methods = self.cond2fuse[cond_type]
1631
+ for op in fuse_methods:
1632
+ if op == 'sum':
1633
+ cond_sum = cond[:, offsets[0]:offsets[0]+T]
1634
+ if cond_sum.shape[1] != 0:
1635
+ if cond_sum.shape[1] < T:
1636
+ cond_sum = F.pad(cond_sum, (0, 0, 0, T-cond_sum.shape[1]), "constant", 0) # pad last special token dim
1637
+ input[:, -cond_sum.shape[1]:, :] = input[:, -cond_sum.shape[1]:, :] + cond_sum
1638
+ if self.in_attn:
1639
+ in_attn_cond += cond_sum
1640
+
1641
+ elif op == 'input_interpolate':
1642
+ cond = einops.rearrange(cond, "b t d -> b d t")
1643
+ cond = F.interpolate(cond, size=input.shape[1])
1644
+ input += einops.rearrange(cond, "b d t -> b t d")
1645
+
1646
+ elif op == 'prepend':
1647
+ if cond_type == 'chord':
1648
+ cond_prepend = torch.zeros(cond.shape[0], 235, cond.shape[2], device=cond.device) # original musicgen melody has 235 length chroma
1649
+ if cond.shape[1] == 1500: # if condition not dropout
1650
+ for i in range(235):
1651
+ cond_prepend[:, i, :] = cond[:, round(i * (1500/235)), :] # n_frame of chord = 30*50 into 235 time steps
1652
+ else:
1653
+ cond_prepend = cond
1654
+
1655
+ if first_step:
1656
+ input = torch.cat([cond_prepend, input], dim=1)
1657
+
1658
+ elif op == 'cross':
1659
+ if cross_attention_output is not None:
1660
+ cross_attention_output = torch.cat([cross_attention_output, cond], dim=1)
1661
+ else:
1662
+ cross_attention_output = cond
1663
+ else:
1664
+ raise ValueError(f"unknown op ({op})")
1665
+
1666
+
1667
+ if self.cross_attention_pos_emb and cross_attention_output is not None:
1668
+ positions = torch.arange(
1669
+ cross_attention_output.shape[1],
1670
+ device=cross_attention_output.device
1671
+ ).view(1, -1, 1)
1672
+ pos_emb = create_sin_embedding(positions, cross_attention_output.shape[-1])
1673
+ cross_attention_output = cross_attention_output + self.cross_attention_pos_emb_scale * pos_emb
1674
+
1675
+ if self._is_streaming:
1676
+ self._streaming_state['offsets'] = offsets + T
1677
+
1678
+ return input, in_attn_cond, cross_attention_output
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
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)
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
audiocraft/modules/rope.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, invert_decay: bool = False):
85
+ """Apply rope rotation to query or key tensor."""
86
+ T = x.shape[1]
87
+ rotation = self.get_rotation(start, start + T).unsqueeze(0).unsqueeze(2)
88
+
89
+ if self.xpos:
90
+ decay = self.xpos.get_decay(start, start + T).unsqueeze(0).unsqueeze(2)
91
+ else:
92
+ decay = 1.0
93
+
94
+ if invert_decay:
95
+ decay = decay ** -1
96
+
97
+ x_complex = torch.view_as_complex(x.to(self.dtype).reshape(*x.shape[:-1], -1, 2))
98
+ scaled_rotation = (rotation * decay) * self.scale + (1.0 - self.scale)
99
+ x_out = torch.view_as_real(x_complex * scaled_rotation).flatten(-2)
100
+
101
+ return x_out.type_as(x)
102
+
103
+ def rotate_qk(self, query: torch.Tensor, key: torch.Tensor, start: int = 0):
104
+ """ Apply rope rotation to both query and key tensors.
105
+ Supports streaming mode, in which query and key are not expected to have the same shape.
106
+ In streaming mode, key will be of length [P + C] with P the cached past timesteps, but
107
+ query will be [C] (typically C == 1).
108
+
109
+ Args:
110
+ query (torch.Tensor): Query to rotate.
111
+ key (torch.Tensor): Key to rotate.
112
+ start (int): Start index of the sequence for time offset.
113
+ """
114
+ query_timesteps = query.shape[1]
115
+ key_timesteps = key.shape[1]
116
+ streaming_offset = key_timesteps - query_timesteps
117
+
118
+ query_out = self.rotate(query, start + streaming_offset)
119
+ key_out = self.rotate(key, start, invert_decay=True)
120
+
121
+ return query_out, key_out
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
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
audiocraft/modules/transformer.py ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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() -> int:
39
+ if _efficient_attention_backend == 'torch':
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) -> 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':
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()
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()
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
+ # TODO: fix and verify layout.
302
+ assert _efficient_attention_backend == 'xformers', "Rope not supported with torch attn."
303
+ # Apply rope embeddings to query and key tensors.
304
+ assert self.rope is not None
305
+ if 'past_keys' in self._streaming_state:
306
+ past_keys_offset = self._streaming_state['past_keys'].shape[1]
307
+ else:
308
+ past_keys_offset = 0
309
+ if 'offset' in self._streaming_state:
310
+ past_context_offset = int(self._streaming_state['offset'].item())
311
+ else:
312
+ past_context_offset = 0
313
+ streaming_offset = past_context_offset + past_keys_offset
314
+ return self.rope.rotate_qk(query, key, start=streaming_offset)
315
+
316
+ def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
317
+ key_padding_mask=None, need_weights=False, attn_mask=None,
318
+ average_attn_weights=True, is_causal=False):
319
+ assert attn_mask is None
320
+ assert not is_causal, ("New param added in torch 2.0.1 not supported, "
321
+ "use the causal args in the constructor.")
322
+
323
+ time_dim = _get_attention_time_dimension()
324
+ if time_dim == 2:
325
+ layout = "b h t d"
326
+ else:
327
+ layout = "b t h d"
328
+ dtype = query.dtype
329
+ if self._is_streaming:
330
+ assert self.causal or self.cross_attention, \
331
+ "Streaming only available for causal or cross attention"
332
+
333
+ if self.causal:
334
+ # At the moment we specialize only for the self-attention case.
335
+ assert query.shape[1] == key.shape[1], "Causal only for same length query / key / value"
336
+ assert value.shape[1] == key.shape[1], "Causal only for same length query / key / value"
337
+ attn_mask = self._get_mask(query.shape[1], query.device, query.dtype)
338
+
339
+ if self.custom:
340
+ # custom implementation
341
+ assert need_weights is False
342
+ assert key_padding_mask is None
343
+ if self.cross_attention:
344
+ # Different queries, keys, values, we have to spit manually the weights
345
+ # before applying the linear.
346
+ dim = self.in_proj_weight.shape[0] // 3
347
+ if self.in_proj_bias is None:
348
+ bias_q, bias_k, bias_v = None, None, None
349
+ else:
350
+ bias_q = self.in_proj_bias[:dim]
351
+ bias_k = self.in_proj_bias[dim: 2 * dim]
352
+ bias_v = self.in_proj_bias[2 * dim:]
353
+ q = nn.functional.linear(query, self.in_proj_weight[:dim], bias_q)
354
+ # todo: when streaming, we could actually save k, v and check the shape actually match.
355
+ k = nn.functional.linear(key, self.in_proj_weight[dim: 2 * dim], bias_k)
356
+ v = nn.functional.linear(value, self.in_proj_weight[2 * dim:], bias_v)
357
+ if self.qk_layer_norm is True:
358
+ q = self.q_layer_norm(q)
359
+ k = self.k_layer_norm(k)
360
+ q, k, v = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k, v]]
361
+ else:
362
+ if not _is_profiled():
363
+ # profiling breaks that propertysomehow.
364
+ assert query is key, "specialized implementation"
365
+ assert value is key, "specialized implementation"
366
+ projected = nn.functional.linear(query, self.in_proj_weight, self.in_proj_bias)
367
+ if self.kv_repeat == 1:
368
+ if time_dim == 2:
369
+ bound_layout = "b h p t d"
370
+ else:
371
+ bound_layout = "b t p h d"
372
+ packed = rearrange(projected, f"b t (p h d) -> {bound_layout}", p=3, h=self.num_heads)
373
+ q, k, v = ops.unbind(packed, dim=2)
374
+ else:
375
+ embed_dim = self.embed_dim
376
+ per_head_dim = (embed_dim // self.num_heads)
377
+ kv_heads = self.num_heads // self.kv_repeat
378
+ q = projected[:, :, :embed_dim]
379
+ start = embed_dim
380
+ end = start + per_head_dim * kv_heads
381
+ k = projected[:, :, start: end]
382
+ v = projected[:, :, end:]
383
+ q = rearrange(q, f"b t (h d) -> {layout}", h=self.num_heads)
384
+ k = rearrange(k, f"b t (h d) -> {layout}", h=kv_heads)
385
+ v = rearrange(v, f"b t (h d) -> {layout}", h=kv_heads)
386
+
387
+ if self.qk_layer_norm is True:
388
+ assert self.kv_repeat == 1
389
+ q, k = [rearrange(x, f"{layout} -> b t (h d)") for x in [q, k]]
390
+ q = self.q_layer_norm(q)
391
+ k = self.k_layer_norm(k)
392
+ q, k = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k]]
393
+ if self.rope:
394
+ q, k = self._apply_rope(q, k)
395
+ k, v = self._complete_kv(k, v)
396
+ if self.kv_repeat > 1:
397
+ k = expand_repeated_kv(k, self.kv_repeat)
398
+ v = expand_repeated_kv(v, self.kv_repeat)
399
+ if self.attention_as_float32:
400
+ q, k, v = [x.float() for x in [q, k, v]]
401
+ if self.memory_efficient:
402
+ p = self.dropout if self.training else 0
403
+ if _efficient_attention_backend == 'torch':
404
+ x = torch.nn.functional.scaled_dot_product_attention(
405
+ q, k, v, is_causal=attn_mask is not None, dropout_p=p)
406
+ else:
407
+ x = ops.memory_efficient_attention(q, k, v, attn_mask, p=p)
408
+ else:
409
+ # We include the dot product as float32, for consistency
410
+ # with the other implementations that include that step
411
+ # as part of the attention. Note that when using `autocast`,
412
+ # the einsums would be done as bfloat16, but the softmax
413
+ # would be done as bfloat16, so `attention_as_float32` will
414
+ # extend a bit the range of operations done in float32,
415
+ # although this should make no difference.
416
+ q = q / q.shape[-1] ** 0.5
417
+ key_layout = layout.replace('t', 'k')
418
+ query_layout = layout
419
+ if self._is_streaming and self.safe_streaming and q.device.type == 'cuda':
420
+ with torch.autocast(device_type=q.device.type, dtype=torch.float32):
421
+ pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
422
+ else:
423
+ pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
424
+ if attn_mask is not None:
425
+ pre_w = pre_w + attn_mask
426
+ w = torch.softmax(pre_w, dim=-1)
427
+ w = F.dropout(w, self.dropout, training=self.training).to(v)
428
+ # Key and value have the same format.
429
+ x = torch.einsum(f"b h t k, {key_layout} -> {layout}", w, v)
430
+ x = x.to(dtype)
431
+ x = rearrange(x, f"{layout} -> b t (h d)", h=self.num_heads)
432
+ x = self.out_proj(x)
433
+ else:
434
+ key, value = self._complete_kv(key, value)
435
+ if self.attention_as_float32:
436
+ query, key, value = [x.float() for x in [query, key, value]]
437
+ x, _ = self.mha(
438
+ query, key, value, key_padding_mask,
439
+ need_weights, attn_mask, average_attn_weights)
440
+ x = x.to(dtype)
441
+
442
+ return x, None
443
+
444
+
445
+ class StreamingTransformerLayer(nn.TransformerEncoderLayer):
446
+ """TransformerLayer with Streaming / Causal support.
447
+ This also integrates cross_attention, when passing `cross_attention=True`,
448
+ rather than having two separate classes like in PyTorch.
449
+
450
+ Args:
451
+ d_model (int): Dimension of the data.
452
+ num_heads (int): Number of heads.
453
+ dim_feedforward (int): Intermediate dimension of FF module.
454
+ dropout (float): Dropout both for MHA and FF.
455
+ bias_ff (bool): Use bias for FF.
456
+ bias_attn (bool): Use bias for MHA.
457
+ causal (bool): Causal mask applied automatically.
458
+ past_context (int, optional): Receptive field for the causal mask, infinite if None.
459
+ custom (bool): Use custom MHA implementation, for testing / benchmarking.
460
+ memory_efficient (bool): Use xformers based memory efficient attention.
461
+ attention_as_float32 (bool): Perform the attention as float32
462
+ (especially important with memory_efficient as autocast won't do this automatically).
463
+ qk_layer_norm (bool): Layer normalization applied to queries and keys before dot product in attention.
464
+ qk_layer_norm_cross (bool): Same for the cross attention.
465
+ cross_attention (bool): If True, expect to get secondary input for cross-attention.
466
+ Cross attention will use the default MHA, as it typically won't require
467
+ special treatment.
468
+ layer_scale (float, optional): If not None, LayerScale will be used with
469
+ the given value as initial scale.
470
+ rope (`RotaryEmbedding`, optional): Rope embedding to use.
471
+ attention_dropout (float, optional): If not None, separate the value of the dimension dropout
472
+ in FFN and of the attention dropout.
473
+ kv_repeat (int): If > 1, will repeat keys and queries multiple times (need to divide num_heads).
474
+ This will lead to faster decoding time on A100 or other GPUs with tensorcore.
475
+ device (torch.device, optional): Device on which to initialize.
476
+ dtype (torch.dtype, optional): dtype to use.
477
+ **kwargs: See `nn.TransformerEncoderLayer`.
478
+ """
479
+ def __init__(self, d_model: int, num_heads: int, dim_feedforward: int = 2048, dropout: float = 0.1,
480
+ bias_ff: bool = True, bias_attn: bool = True, causal: bool = False,
481
+ past_context: tp.Optional[int] = None, custom: bool = False,
482
+ memory_efficient: bool = False, attention_as_float32: bool = False,
483
+ qk_layer_norm: bool = False, qk_layer_norm_cross: bool = False,
484
+ cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
485
+ rope: tp.Optional[RotaryEmbedding] = None, attention_dropout: tp.Optional[float] = None,
486
+ kv_repeat: int = 1, norm: str = 'layer_norm', device=None, dtype=None, **kwargs):
487
+ super().__init__(d_model, num_heads, dim_feedforward, dropout,
488
+ device=device, dtype=dtype, batch_first=True, **kwargs)
489
+ factory_kwargs = {'device': device, 'dtype': dtype}
490
+ # Redefine self_attn to our streaming multi-head attention
491
+ attn_kwargs: tp.Dict[str, tp.Any] = {
492
+ 'embed_dim': d_model,
493
+ 'num_heads': num_heads,
494
+ 'dropout': dropout if attention_dropout is None else attention_dropout,
495
+ 'bias': bias_attn,
496
+ 'custom': custom,
497
+ 'memory_efficient': memory_efficient,
498
+ 'attention_as_float32': attention_as_float32,
499
+ }
500
+ self.self_attn: StreamingMultiheadAttention = StreamingMultiheadAttention(
501
+ causal=causal, past_context=past_context, rope=rope, qk_layer_norm=qk_layer_norm,
502
+ kv_repeat=kv_repeat, **attn_kwargs, **factory_kwargs) # type: ignore
503
+ # Redefine feedforward layers to expose bias parameter
504
+ self.linear1 = nn.Linear(d_model, dim_feedforward, bias=bias_ff, **factory_kwargs)
505
+ self.linear2 = nn.Linear(dim_feedforward, d_model, bias=bias_ff, **factory_kwargs)
506
+
507
+ self.layer_scale_1: nn.Module
508
+ self.layer_scale_2: nn.Module
509
+ if layer_scale is None:
510
+ self.layer_scale_1 = nn.Identity()
511
+ self.layer_scale_2 = nn.Identity()
512
+ else:
513
+ self.layer_scale_1 = LayerScale(d_model, layer_scale, **factory_kwargs)
514
+ self.layer_scale_2 = LayerScale(d_model, layer_scale, **factory_kwargs)
515
+
516
+ self.cross_attention: tp.Optional[nn.Module] = None
517
+ if cross_attention:
518
+ self.cross_attention = StreamingMultiheadAttention(
519
+ cross_attention=True, qk_layer_norm=qk_layer_norm_cross,
520
+ **attn_kwargs, **factory_kwargs)
521
+ # Norm and dropout
522
+ self.dropout_cross = nn.Dropout(dropout)
523
+ # eps value matching that used in PyTorch reference implementation.
524
+ self.norm_cross = nn.LayerNorm(d_model, eps=1e-5, **factory_kwargs)
525
+ self.layer_scale_cross: nn.Module
526
+ if layer_scale is None:
527
+ self.layer_scale_cross = nn.Identity()
528
+ else:
529
+ self.layer_scale_cross = LayerScale(d_model, layer_scale, **factory_kwargs)
530
+ self.norm1 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
531
+ self.norm2 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
532
+
533
+ def _cross_attention_block(self, src: torch.Tensor,
534
+ cross_attention_src: torch.Tensor) -> torch.Tensor:
535
+ assert self.cross_attention is not None
536
+ # queries are from src, keys and values from cross_attention_src.
537
+ x = self.cross_attention(
538
+ src, cross_attention_src, cross_attention_src, need_weights=False)[0]
539
+ return self.dropout_cross(x) # type: ignore
540
+
541
+ def forward(self, src: torch.Tensor, src_mask: tp.Optional[torch.Tensor] = None, # type: ignore
542
+ src_key_padding_mask: tp.Optional[torch.Tensor] = None,
543
+ cross_attention_src: tp.Optional[torch.Tensor] = None):
544
+ if self.cross_attention is None:
545
+ assert cross_attention_src is None
546
+ else:
547
+ assert cross_attention_src is not None
548
+ x = src
549
+ if self.norm_first:
550
+ x = x + self.layer_scale_1(
551
+ self._sa_block(self.norm1(x), src_mask, src_key_padding_mask))
552
+ if cross_attention_src is not None:
553
+ x = x + self.layer_scale_cross(
554
+ self._cross_attention_block(
555
+ self.norm_cross(x), cross_attention_src))
556
+ x = x + self.layer_scale_2(self._ff_block(self.norm2(x)))
557
+ else:
558
+ x = self.norm1(x + self.layer_scale_1(
559
+ self._sa_block(x, src_mask, src_key_padding_mask)))
560
+ if cross_attention_src is not None:
561
+ x = self.norm_cross(
562
+ x + self.layer_scale_cross(
563
+ self._cross_attention_block(src, cross_attention_src)))
564
+ x = self.norm2(x + self.layer_scale_2(self._ff_block(x)))
565
+ return x
566
+
567
+
568
+ class StreamingTransformer(StreamingModule):
569
+ """Transformer with Streaming / Causal support.
570
+
571
+ Args:
572
+ d_model (int): Dimension of the data.
573
+ num_heads (int): Number of heads.
574
+ dim_feedforward (int): Intermediate dimension of FF module.
575
+ dropout (float): Dropout both for MHA and FF.
576
+ bias_ff (bool): Use bias for FF.
577
+ bias_attn (bool): Use bias for MHA.
578
+ causal (bool): Causal mask applied automatically.
579
+ past_context (int, optional): Receptive field for the causal mask, infinite if None.
580
+ custom (bool): Use custom MHA implementation, for testing / benchmarking.
581
+ memory_efficient (bool): Use xformers based memory efficient attention.
582
+ attention_as_float32 (bool): Perform the attention as float32
583
+ (especially important with memory_efficient as autocast won't do this automatically).
584
+ cross_attention (bool): If True, expect to get secondary input for cross-attention.
585
+ layer_scale (float, optional): If not None, LayerScale will be used
586
+ with the given value as initial scale.
587
+ positional_embedding (str): Positional embedding strategy (sin, rope, or sin_rope).
588
+ max_period (float): Maximum period of the time embedding.
589
+ positional_scale (float): Scale of positional embedding, set to 0 to deactivate.
590
+ xpos (bool): Apply xpos exponential decay to positional embedding (rope only).
591
+ lr (float, optional): learning rate override through the `make_optim_group` API.
592
+ weight_decay (float, optional): Weight_decay override through the `make_optim_group` API.
593
+ layer_class: (subclass of `StreamingTransformerLayer): class to use
594
+ to initialize the layers, allowing further customization outside of AudioCraft.
595
+ checkpointing (str): Checkpointing strategy to reduce memory usage.
596
+ No checkpointing if set to 'none'. Per layer checkpointing using PyTorch
597
+ if set to 'torch' (entire layer checkpointed, i.e. linears are evaluated twice,
598
+ minimal memory usage, but maximal runtime). Finally, `xformers_default` provide
599
+ a policy for opting-out some operations of the checkpointing like
600
+ linear layers and attention, providing a middle ground between speed and memory.
601
+ device (torch.device, optional): Device on which to initialize.
602
+ dtype (torch.dtype, optional): dtype to use.
603
+ **kwargs: See `nn.TransformerEncoderLayer`.
604
+ """
605
+ def __init__(self, d_model: int, num_heads: int, num_layers: int, dim_feedforward: int = 2048,
606
+ dropout: float = 0.1, bias_ff: bool = True, bias_attn: bool = True,
607
+ causal: bool = False, past_context: tp.Optional[int] = None,
608
+ custom: bool = False, memory_efficient: bool = False, attention_as_float32: bool = False,
609
+ cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
610
+ positional_embedding: str = 'sin', max_period: float = 10_000, positional_scale: float = 1.,
611
+ xpos: bool = False, lr: tp.Optional[float] = None, weight_decay: tp.Optional[float] = None,
612
+ layer_class: tp.Type[StreamingTransformerLayer] = StreamingTransformerLayer,
613
+ checkpointing: str = 'none', device=None, dtype=None, **kwargs):
614
+ super().__init__()
615
+ assert d_model % num_heads == 0
616
+
617
+ self.positional_embedding = positional_embedding
618
+ self.max_period = max_period
619
+ self.positional_scale = positional_scale
620
+ self.weight_decay = weight_decay
621
+ self.lr = lr
622
+
623
+ assert positional_embedding in ['sin', 'rope', 'sin_rope']
624
+ self.rope: tp.Optional[RotaryEmbedding] = None
625
+ if self.positional_embedding in ['rope', 'sin_rope']:
626
+ assert _is_custom(custom, memory_efficient)
627
+ self.rope = RotaryEmbedding(d_model // num_heads, max_period=max_period,
628
+ xpos=xpos, scale=positional_scale, device=device)
629
+
630
+ self.checkpointing = checkpointing
631
+
632
+ assert checkpointing in ['none', 'torch', 'xformers_default', 'xformers_mm']
633
+ if self.checkpointing.startswith('xformers'):
634
+ _verify_xformers_internal_compat()
635
+
636
+ self.layers = nn.ModuleList()
637
+ for idx in range(num_layers):
638
+ self.layers.append(
639
+ layer_class(
640
+ d_model=d_model, num_heads=num_heads, dim_feedforward=dim_feedforward,
641
+ dropout=dropout, bias_ff=bias_ff, bias_attn=bias_attn,
642
+ causal=causal, past_context=past_context, custom=custom,
643
+ memory_efficient=memory_efficient, attention_as_float32=attention_as_float32,
644
+ cross_attention=cross_attention, layer_scale=layer_scale, rope=self.rope,
645
+ device=device, dtype=dtype, **kwargs))
646
+
647
+ if self.checkpointing != 'none':
648
+ for layer in self.layers:
649
+ # see audiocraft/optim/fsdp.py, magic signal to indicate this requires fixing the
650
+ # backward hook inside of FSDP...
651
+ layer._magma_checkpointed = True # type: ignore
652
+ assert layer.layer_drop == 0., "Need further checking" # type: ignore
653
+
654
+ def _apply_layer(self, layer, *args, **kwargs):
655
+ method = self.checkpointing
656
+ if method == 'none':
657
+ return layer(*args, **kwargs)
658
+ elif method == 'torch':
659
+ return torch_checkpoint(layer, *args, use_reentrant=False, **kwargs)
660
+ elif method.startswith('xformers'):
661
+ from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy
662
+ if method == 'xformers_default':
663
+ # those operations will be saved, and not recomputed.
664
+ # According to Francisco we can get smarter policies but this is a good start.
665
+ allow_list = [
666
+ "xformers.efficient_attention_forward_cutlass.default",
667
+ "xformers_flash.flash_fwd.default",
668
+ "aten.addmm.default",
669
+ "aten.mm.default",
670
+ ]
671
+ elif method == 'xformers_mm':
672
+ # those operations will be saved, and not recomputed.
673
+ # According to Francisco we can get smarter policies but this is a good start.
674
+ allow_list = [
675
+ "aten.addmm.default",
676
+ "aten.mm.default",
677
+ ]
678
+ else:
679
+ raise ValueError(f"xformers checkpointing xformers policy {method} is not known.")
680
+ policy_fn = _get_default_policy(allow_list)
681
+ return checkpoint(layer, *args, policy_fn=policy_fn, **kwargs)
682
+ else:
683
+ raise ValueError(f"Checkpointing method {method} is unknown.")
684
+
685
+ def forward(self, x: torch.Tensor, in_attn_src: torch.Tensor, *args, **kwargs):
686
+ B, T, C = x.shape
687
+ if in_attn_src is not None:
688
+ _, in_attn_t, _ = in_attn_src.shape
689
+
690
+ if 'offsets' in self._streaming_state:
691
+ offsets = self._streaming_state['offsets']
692
+ else:
693
+ offsets = torch.zeros(B, dtype=torch.long, device=x.device)
694
+
695
+ if self.positional_embedding in ['sin', 'sin_rope']:
696
+ positions = torch.arange(T, device=x.device).view(1, -1, 1)
697
+ positions = positions + offsets.view(-1, 1, 1)
698
+ pos_emb = create_sin_embedding(positions, C, max_period=self.max_period, dtype=x.dtype)
699
+ x = x + self.positional_scale * pos_emb
700
+
701
+ for idx, layer in enumerate(self.layers):
702
+ if (idx % 4 == 0) and (idx < 36) and (idx != 0):
703
+ if in_attn_src is not None:
704
+ x[:, -in_attn_t:, :] += in_attn_src
705
+ x = self._apply_layer(layer, x, *args, **kwargs)
706
+
707
+ if self._is_streaming:
708
+ self._streaming_state['offsets'] = offsets + T
709
+
710
+ return x
711
+
712
+ def make_optim_group(self):
713
+ group = {"params": list(self.parameters())}
714
+ if self.lr is not None:
715
+ group["lr"] = self.lr
716
+ if self.weight_decay is not None:
717
+ group["weight_decay"] = self.weight_decay
718
+ return group
719
+
720
+
721
+ # special attention related function
722
+
723
+ def _verify_xformers_memory_efficient_compat():
724
+ try:
725
+ from xformers.ops import memory_efficient_attention, LowerTriangularMask # noqa
726
+ except ImportError:
727
+ raise ImportError(
728
+ "xformers is not installed. Please install it and try again.\n"
729
+ "To install on AWS and Azure, run \n"
730
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
731
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
732
+ "To install on FAIR Cluster, run \n"
733
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
734
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
735
+
736
+
737
+ def _verify_xformers_internal_compat():
738
+ try:
739
+ from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy # noqa
740
+ except ImportError:
741
+ raise ImportError(
742
+ "Francisco's fairinternal xformers is not installed. Please install it and try again.\n"
743
+ "To install on AWS and Azure, run \n"
744
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
745
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
746
+ "To install on FAIR Cluster, run \n"
747
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
748
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
749
+
750
+
751
+ def _is_custom(custom: bool, memory_efficient: bool):
752
+ return custom or memory_efficient
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
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")
audiocraft/quantization/core_vq.py ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ residual = residual - quantized
375
+ quantized_out = quantized_out + quantized
376
+ all_indices.append(indices)
377
+ all_losses.append(loss)
378
+
379
+ out_losses, out_indices = map(torch.stack, (all_losses, all_indices))
380
+ return quantized_out, out_indices, out_losses
381
+
382
+ def encode(self, x: torch.Tensor, n_q: tp.Optional[int] = None) -> torch.Tensor:
383
+ residual = x
384
+ all_indices = []
385
+ n_q = n_q or len(self.layers)
386
+ for layer in self.layers[:n_q]:
387
+ indices = layer.encode(residual)
388
+ quantized = layer.decode(indices)
389
+ residual = residual - quantized
390
+ all_indices.append(indices)
391
+ out_indices = torch.stack(all_indices)
392
+ return out_indices
393
+
394
+ def decode(self, q_indices: torch.Tensor) -> torch.Tensor:
395
+ quantized_out = torch.tensor(0.0, device=q_indices.device)
396
+ for i, indices in enumerate(q_indices):
397
+ layer = self.layers[i]
398
+ quantized = layer.decode(indices)
399
+ quantized_out = quantized_out + quantized
400
+ return quantized_out
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
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."""
audiocraft/utils/autocast.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+
9
+
10
+ class TorchAutocast:
11
+ """TorchAutocast utility class.
12
+ Allows you to enable and disable autocast. This is specially useful
13
+ when dealing with different architectures and clusters with different
14
+ levels of support.
15
+
16
+ Args:
17
+ enabled (bool): Whether to enable torch.autocast or not.
18
+ args: Additional args for torch.autocast.
19
+ kwargs: Additional kwargs for torch.autocast
20
+ """
21
+ def __init__(self, enabled: bool, *args, **kwargs):
22
+ self.autocast = torch.autocast(*args, **kwargs) if enabled else None
23
+
24
+ def __enter__(self):
25
+ if self.autocast is None:
26
+ return
27
+ try:
28
+ self.autocast.__enter__()
29
+ except RuntimeError:
30
+ device = self.autocast.device
31
+ dtype = self.autocast.fast_dtype
32
+ raise RuntimeError(
33
+ f"There was an error autocasting with dtype={dtype} device={device}\n"
34
+ "If you are on the FAIR Cluster, you might need to use autocast_dtype=float16"
35
+ )
36
+
37
+ def __exit__(self, *args, **kwargs):
38
+ if self.autocast is None:
39
+ return
40
+ self.autocast.__exit__(*args, **kwargs)
audiocraft/utils/cache.py ADDED
@@ -0,0 +1,323 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 concurrent.futures import ThreadPoolExecutor
8
+ from collections import deque
9
+ from functools import partial
10
+ from hashlib import sha1
11
+ import logging
12
+ from pathlib import Path
13
+ import sys
14
+ import typing as tp
15
+ import zipfile
16
+
17
+ import flashy
18
+ import torch
19
+
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ def get_full_embed(full_embed: torch.Tensor, x: tp.Any, idx: int, device: tp.Union[str, torch.device]) -> torch.Tensor:
25
+ """Utility function for the EmbeddingCache, returning the full embedding without any chunking.
26
+ This method can be used in case there is no need in extracting a chunk of the full embedding
27
+ read from the cache.
28
+
29
+ Args:
30
+ full_embed (torch.Tensor): The full embedding.
31
+ x (any): Batch object from which the full embedding is derived.
32
+ idx (torch.Tensor): Index of object to consider in the batch object.
33
+ Returns:
34
+ full_embed (torch.Tensor): The full embedding
35
+ """
36
+ return full_embed.to(device)
37
+
38
+
39
+ class EmbeddingCache:
40
+ """Cache around embeddings computation for faster execution.
41
+ The EmbeddingCache is storing pre-computed embeddings on disk and provides a simple API
42
+ to retrieve the pre-computed embeddings on full inputs and extract only a given chunk
43
+ using a user-provided function. When the cache is warm (all embeddings are pre-computed),
44
+ the EmbeddingCache allows for faster training as it removes the need of computing the embeddings.
45
+ Additionally, it provides in-memory cache around the loaded embeddings to limit IO footprint
46
+ and synchronization points in the forward calls.
47
+
48
+ Args:
49
+ cache_path (Path): Path to folder where all pre-computed embeddings are saved on disk.
50
+ device (str or torch.device): Device on which the embedding is returned.
51
+ compute_embed_fn (callable[[Path, any, int], torch.Tensor], optional): Function to compute
52
+ the embedding from a given object and path. This user provided function can compute the
53
+ embedding from the provided object or using the provided path as entry point. The last parameter
54
+ specify the index corresponding to the current embedding in the object that can represent batch metadata.
55
+ extract_embed_fn (callable[[torch.Tensor, any, int], torch.Tensor], optional): Function to extract
56
+ the desired embedding chunk from the full embedding loaded from the cache. The last parameter
57
+ specify the index corresponding to the current embedding in the object that can represent batch metadata.
58
+ If not specified, will return the full embedding unmodified.
59
+ """
60
+ def __init__(self, cache_path: tp.Union[str, Path], device: tp.Union[str, torch.device],
61
+ compute_embed_fn: tp.Callable[[Path, tp.Any, int], torch.Tensor],
62
+ extract_embed_fn: tp.Optional[tp.Callable[[torch.Tensor, tp.Any, int], torch.Tensor]] = None):
63
+ self.cache_path = Path(cache_path)
64
+ self.device = device
65
+ self._compute_embed_fn = compute_embed_fn
66
+ self._extract_embed_fn: tp.Callable[[torch.Tensor, tp.Any, int], torch.Tensor]
67
+ if extract_embed_fn is not None:
68
+ self._extract_embed_fn = extract_embed_fn
69
+ else:
70
+ self._extract_embed_fn = partial(get_full_embed, device=device)
71
+ if self.cache_path is not None:
72
+ self.cache_path.mkdir(exist_ok=True, parents=True)
73
+ logger.info(f"Cache instantiated at: {self.cache_path}")
74
+ self.pool = ThreadPoolExecutor(8)
75
+ self.pool.__enter__()
76
+ self._current_batch_cache: dict = {}
77
+ self._memory_cache: dict = {}
78
+
79
+ def _get_cache_path(self, path: tp.Union[Path, str]):
80
+ """Get cache path for the given file path."""
81
+ sig = sha1(str(path).encode()).hexdigest()
82
+ return self.cache_path / sig
83
+
84
+ @staticmethod
85
+ def _get_full_embed_from_cache(cache: Path):
86
+ """Loads full pre-computed embedding from the cache."""
87
+ try:
88
+ embed = torch.load(cache, 'cpu')
89
+ except Exception as exc:
90
+ logger.error("Error loading %s: %r", cache, exc)
91
+ embed = None
92
+ return embed
93
+
94
+ def get_embed_from_cache(self, paths: tp.List[Path], x: tp.Any) -> torch.Tensor:
95
+ """Get embedding from cache, computing and storing it to cache if not already cached.
96
+ The EmbeddingCache first tries to load the embedding from the in-memory cache
97
+ containing the pre-computed chunks populated through `populate_embed_cache`.
98
+ If not found, the full embedding is computed and stored on disk to be later accessed
99
+ to populate the in-memory cache, and the desired embedding chunk is extracted and returned.
100
+
101
+ Args:
102
+ paths (list[Path or str]): List of paths from where the embeddings can be loaded.
103
+ x (any): Object from which the embedding is extracted.
104
+ """
105
+ embeds = []
106
+ for idx, path in enumerate(paths):
107
+ cache = self._get_cache_path(path)
108
+ if cache in self._current_batch_cache:
109
+ embed = self._current_batch_cache[cache]
110
+ else:
111
+ full_embed = self._compute_embed_fn(path, x, idx)
112
+ try:
113
+ with flashy.utils.write_and_rename(cache, pid=True) as f:
114
+ torch.save(full_embed.cpu(), f)
115
+ except Exception as exc:
116
+ logger.error('Error saving embed %s (%s): %r', cache, full_embed.shape, exc)
117
+ else:
118
+ logger.info('New embed cache saved: %s (%s)', cache, full_embed.shape)
119
+ embed = self._extract_embed_fn(full_embed, x, idx)
120
+ embeds.append(embed)
121
+ embed = torch.stack(embeds, dim=0)
122
+ return embed
123
+
124
+ def populate_embed_cache(self, paths: tp.List[Path], x: tp.Any) -> None:
125
+ """Populate in-memory caches for embeddings reading from the embeddings stored on disk.
126
+ The in-memory caches consist in a cache for the full embedding and another cache for the
127
+ final embedding chunk. Such caches are used to limit the IO access when computing the actual embeddings
128
+ and reduce the IO footprint and synchronization points during forward passes.
129
+
130
+ Args:
131
+ paths (list[Path]): List of paths from where the embeddings can be loaded.
132
+ x (any): Object from which the embedding is extracted.
133
+ """
134
+ self._current_batch_cache.clear()
135
+ if self.cache_path is not None:
136
+ futures: list = []
137
+ for path in paths:
138
+ assert path is not None, "Path is required for computation from cache"
139
+ cache = self._get_cache_path(path)
140
+ if cache in self._memory_cache or not cache.exists():
141
+ futures.append(None)
142
+ else:
143
+ futures.append(self.pool.submit(EmbeddingCache._get_full_embed_from_cache, cache))
144
+ for idx, (path, future) in enumerate(zip(paths, futures)):
145
+ assert path is not None
146
+ cache = self._get_cache_path(path)
147
+ full_embed = None
148
+ if future is None:
149
+ if cache in self._memory_cache:
150
+ full_embed = self._memory_cache[cache]
151
+ else:
152
+ full_embed = future.result()
153
+ if full_embed is not None:
154
+ self._memory_cache[cache] = full_embed
155
+ full_embed = full_embed.to(self.device)
156
+ if full_embed is not None:
157
+ embed = self._extract_embed_fn(full_embed, x, idx)
158
+ self._current_batch_cache[cache] = embed
159
+
160
+
161
+ class CachedBatchWriter:
162
+ """Write pre computed caches for mini batches. This can
163
+ make loading a lot more efficient depending on your filesystem.
164
+
165
+ Args:
166
+ cache_folder (Path): folder in which the cached minibatches
167
+ will be stored.
168
+
169
+ Inside cache folder, the structure is the following:
170
+ `epoch_number / update_number.zip`
171
+ And the zip file contains one entry per batch item.
172
+
173
+ It is possible to use the cache with a batch size smaller than
174
+ created with but obviously not larger. Make sure to call the
175
+ `start_epoch(epoch)` method for indicating changes of epochs.
176
+
177
+ See the grid `audiocraft/grids/musicgen/musicgen_warmup_cache.py`
178
+ for an example of how to warmup the cache.
179
+ """
180
+ def __init__(self, cache_folder: Path):
181
+ self.cache_folder = cache_folder
182
+ self._current_epoch: tp.Optional[int] = None
183
+ self._current_index = 0
184
+
185
+ def start_epoch(self, epoch: int):
186
+ """Call at the beginning of each epoch.
187
+ """
188
+ self._current_epoch = epoch
189
+ self._current_index = 0
190
+ self._zip_path.parent.mkdir(exist_ok=True, parents=True)
191
+
192
+ @staticmethod
193
+ def _get_zip_path(cache_folder: Path, epoch: int, index: int):
194
+ return cache_folder / f"{epoch:05d}" / f"{index:06d}.zip"
195
+
196
+ @property
197
+ def _zip_path(self):
198
+ assert self._current_epoch is not None
199
+ return CachedBatchWriter._get_zip_path(self.cache_folder, self._current_epoch, self._current_index)
200
+
201
+ def save(self, *content):
202
+ """Save one mini batch. This function is distributed-aware
203
+ and will automatically merge all the items from the different
204
+ workers.
205
+ """
206
+ all_contents = []
207
+ for rank in range(flashy.distrib.world_size()):
208
+ their_content = flashy.distrib.broadcast_object(content, src=rank)
209
+ all_contents.append(their_content)
210
+
211
+ if flashy.distrib.is_rank_zero():
212
+ idx = 0
213
+ with flashy.utils.write_and_rename(self._zip_path) as tmp:
214
+ with zipfile.ZipFile(tmp, 'w') as zf:
215
+ for content in all_contents:
216
+ for vals in zip(*content):
217
+ with zf.open(f'{idx}', 'w') as f: # type: ignore
218
+ torch.save(vals, f)
219
+ idx += 1
220
+ flashy.distrib.barrier()
221
+ self._current_index += 1
222
+
223
+
224
+ class CachedBatchLoader:
225
+ """Loader for cached mini-batches dumped with `CachedBatchWriter`.
226
+
227
+ Args:
228
+ cache_folder (Path): folder in which the cached minibatches are stored.
229
+ batch_size (int): batch size (per GPU) expected.
230
+ num_workers (int): number of workers to use for loading.
231
+ min_length (int): minimum expected length for each epoch. If some
232
+ mini-batches are missing, and error is raised.
233
+
234
+ This is iterable just like a regular DataLoader.
235
+ """
236
+
237
+ def __init__(self, cache_folder: Path, batch_size: int,
238
+ num_workers: int = 10, min_length: int = 1):
239
+ self.cache_folder = cache_folder
240
+ self.batch_size = batch_size
241
+ self.num_workers = num_workers
242
+ self.min_length = min_length
243
+ self._current_epoch: tp.Optional[int] = None
244
+ self.sampler = None # for compatibility with the regular DataLoader
245
+
246
+ def __len__(self):
247
+ path = CachedBatchWriter._get_zip_path(self.cache_folder, self._current_epoch or 0, 0).parent
248
+ return len([p for p in path.iterdir() if p.suffix == ".zip"])
249
+
250
+ def start_epoch(self, epoch: int):
251
+ """Call at the beginning of each epoch.
252
+ """
253
+ self._current_epoch = epoch
254
+
255
+ def _zip_path(self, index: int):
256
+ assert self._current_epoch is not None
257
+ return CachedBatchWriter._get_zip_path(self.cache_folder, self._current_epoch, index)
258
+
259
+ def _load_one(self, index: int):
260
+ zip_path = self._zip_path(index)
261
+ if not zip_path.exists():
262
+ if index < self.min_length:
263
+ raise RuntimeError(f"Cache should have at least {self.min_length} batches, but {index} doesn't exist")
264
+
265
+ return None
266
+ mode = "rb" if sys.version_info >= (3, 9) else "r"
267
+ try:
268
+ with zipfile.ZipFile(zip_path, 'r') as zf:
269
+ rank = flashy.distrib.rank()
270
+ world_size = flashy.distrib.world_size()
271
+ root = zipfile.Path(zf)
272
+ items = list(root.iterdir())
273
+ total_batch_size = self.batch_size * world_size
274
+ if len(items) < total_batch_size:
275
+ raise RuntimeError(
276
+ f"The cache can handle a max batch size of {len(items)}, "
277
+ f"but {total_batch_size} is needed.")
278
+ start = rank * self.batch_size
279
+ items = items[start: start + self.batch_size]
280
+ assert len(items) == self.batch_size
281
+ entries = []
282
+ entries = [torch.load(item.open(mode), 'cpu') for item in items] # type: ignore
283
+ transposed = zip(*entries)
284
+ out = []
285
+ for part in transposed:
286
+ assert len(part) > 0
287
+ if isinstance(part[0], torch.Tensor):
288
+ out.append(torch.stack(part))
289
+ else:
290
+ out.append(part)
291
+ return out
292
+ except Exception:
293
+ logger.error("Error when reading zip path %s", zip_path)
294
+ raise
295
+
296
+ def __iter__(self):
297
+ """This will yields tuples, exactly as provided to the
298
+ `CachedBatchWriter.save` method.
299
+ """
300
+ pool = ThreadPoolExecutor(self.num_workers)
301
+ next_index = 0
302
+ queue = deque()
303
+
304
+ def _get_next():
305
+ nonlocal next_index
306
+ r = queue.popleft().result()
307
+ if r is None:
308
+ return None
309
+ else:
310
+ queue.append(pool.submit(self._load_one, next_index))
311
+ next_index += 1
312
+ return r
313
+
314
+ with pool:
315
+ # fill the buffer of fetching jobs.
316
+ for _ in range(2 * self.num_workers):
317
+ queue.append(pool.submit(self._load_one, next_index))
318
+ next_index += 1
319
+ while True:
320
+ batch = _get_next()
321
+ if batch is None:
322
+ return
323
+ yield batch
audiocraft/utils/cluster.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 for SLURM configuration and cluster settings.
9
+ """
10
+
11
+ from enum import Enum
12
+ import os
13
+ import socket
14
+ import typing as tp
15
+
16
+ import omegaconf
17
+
18
+
19
+ class ClusterType(Enum):
20
+ AWS = "aws"
21
+ FAIR = "fair"
22
+ RSC = "rsc"
23
+ LOCAL_DARWIN = "darwin"
24
+ DEFAULT = "default" # used for any other cluster.
25
+
26
+
27
+ def _guess_cluster_type() -> ClusterType:
28
+ uname = os.uname()
29
+ fqdn = socket.getfqdn()
30
+ if uname.sysname == "Linux" and (uname.release.endswith("-aws") or ".ec2" in fqdn):
31
+ return ClusterType.AWS
32
+
33
+ if fqdn.endswith(".fair"):
34
+ return ClusterType.FAIR
35
+
36
+ if fqdn.endswith(".facebook.com"):
37
+ return ClusterType.RSC
38
+
39
+ if uname.sysname == "Darwin":
40
+ return ClusterType.LOCAL_DARWIN
41
+
42
+ return ClusterType.DEFAULT
43
+
44
+
45
+ def get_cluster_type(
46
+ cluster_type: tp.Optional[ClusterType] = None,
47
+ ) -> tp.Optional[ClusterType]:
48
+ if cluster_type is None:
49
+ return _guess_cluster_type()
50
+
51
+ return cluster_type
52
+
53
+
54
+ def get_slurm_parameters(
55
+ cfg: omegaconf.DictConfig, cluster_type: tp.Optional[ClusterType] = None
56
+ ) -> omegaconf.DictConfig:
57
+ """Update SLURM parameters in configuration based on cluster type.
58
+ If the cluster type is not specify, it infers it automatically.
59
+ """
60
+ from ..environment import AudioCraftEnvironment
61
+ cluster_type = get_cluster_type(cluster_type)
62
+ # apply cluster-specific adjustments
63
+ if cluster_type == ClusterType.AWS:
64
+ cfg["mem_per_gpu"] = None
65
+ cfg["constraint"] = None
66
+ cfg["setup"] = []
67
+ elif cluster_type == ClusterType.RSC:
68
+ cfg["mem_per_gpu"] = None
69
+ cfg["setup"] = []
70
+ cfg["constraint"] = None
71
+ cfg["partition"] = "learn"
72
+ slurm_exclude = AudioCraftEnvironment.get_slurm_exclude()
73
+ if slurm_exclude is not None:
74
+ cfg["exclude"] = slurm_exclude
75
+ return cfg
audiocraft/utils/utils.py ADDED
@@ -0,0 +1,298 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 concurrent.futures import ProcessPoolExecutor
8
+ from contextlib import contextmanager
9
+ from functools import wraps, lru_cache
10
+ import hashlib
11
+ import json
12
+ import logging
13
+ from pathlib import Path
14
+ import typing as tp
15
+
16
+ import flashy
17
+ import flashy.distrib
18
+ import omegaconf
19
+ import torch
20
+ from torch.nn.utils.rnn import pad_sequence
21
+
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ def model_hash(model: torch.nn.Module) -> str:
27
+ """Return a model hash. This should allow us to track regressions in model init
28
+ from the logs of past experiments.
29
+ """
30
+ hasher = hashlib.sha1()
31
+ for p in model.parameters():
32
+ hasher.update(p.data.cpu().numpy().tobytes())
33
+ return hasher.hexdigest()
34
+
35
+
36
+ def dict_from_config(cfg: omegaconf.DictConfig) -> dict:
37
+ """Convenience function to map an omegaconf configuration to a dictionary.
38
+
39
+ Args:
40
+ cfg (omegaconf.DictConfig): Original configuration to map to dict.
41
+ Returns:
42
+ dict: Config as dictionary object.
43
+ """
44
+ dct = omegaconf.OmegaConf.to_container(cfg, resolve=True)
45
+ assert isinstance(dct, dict)
46
+ return dct
47
+
48
+
49
+ def random_subset(dataset, max_samples: int, seed: int = 42) -> torch.utils.data.Subset:
50
+ if max_samples >= len(dataset):
51
+ return dataset
52
+
53
+ generator = torch.Generator().manual_seed(seed)
54
+ perm = torch.randperm(len(dataset), generator=generator)
55
+ return torch.utils.data.Subset(dataset, perm[:max_samples].tolist())
56
+
57
+
58
+ def get_loader(dataset, num_samples: tp.Optional[int], batch_size: int,
59
+ num_workers: int, seed: int, **kwargs) -> torch.utils.data.DataLoader:
60
+ """Convenience function to load dataset into a dataloader with optional subset sampling.
61
+
62
+ Args:
63
+ dataset: Dataset to load.
64
+ num_samples (Optional[int]): Number of samples to limit subset size.
65
+ batch_size (int): Batch size.
66
+ num_workers (int): Number of workers for data loading.
67
+ seed (int): Random seed.
68
+ """
69
+ if num_samples is not None:
70
+ dataset = random_subset(dataset, num_samples, seed)
71
+
72
+ dataloader = flashy.distrib.loader(
73
+ dataset,
74
+ batch_size=batch_size,
75
+ num_workers=num_workers,
76
+ **kwargs
77
+ )
78
+ return dataloader
79
+
80
+
81
+ def get_dataset_from_loader(dataloader):
82
+ dataset = dataloader.dataset
83
+ if isinstance(dataset, torch.utils.data.Subset):
84
+ return dataset.dataset
85
+ else:
86
+ return dataset
87
+
88
+
89
+ def multinomial(input: torch.Tensor, num_samples: int, replacement=False, *, generator=None):
90
+ """torch.multinomial with arbitrary number of dimensions, and number of candidates on the last dimension.
91
+
92
+ Args:
93
+ input (torch.Tensor): The input tensor containing probabilities.
94
+ num_samples (int): Number of samples to draw.
95
+ replacement (bool): Whether to draw with replacement or not.
96
+ Keywords args:
97
+ generator (torch.Generator): A pseudorandom number generator for sampling.
98
+ Returns:
99
+ torch.Tensor: Last dimension contains num_samples indices
100
+ sampled from the multinomial probability distribution
101
+ located in the last dimension of tensor input.
102
+ """
103
+ input_ = input.reshape(-1, input.shape[-1])
104
+ output_ = torch.multinomial(input_, num_samples=num_samples, replacement=replacement, generator=generator)
105
+ output = output_.reshape(*list(input.shape[:-1]), -1)
106
+ return output
107
+
108
+
109
+ def sample_top_k(probs: torch.Tensor, k: int) -> torch.Tensor:
110
+ """Sample next token from top K values along the last dimension of the input probs tensor.
111
+
112
+ Args:
113
+ probs (torch.Tensor): Input probabilities with token candidates on the last dimension.
114
+ k (int): The k in “top-k”.
115
+ Returns:
116
+ torch.Tensor: Sampled tokens.
117
+ """
118
+ top_k_value, _ = torch.topk(probs, k, dim=-1)
119
+ min_value_top_k = top_k_value[..., [-1]]
120
+ probs *= (probs >= min_value_top_k).float()
121
+ probs.div_(probs.sum(dim=-1, keepdim=True))
122
+ next_token = multinomial(probs, num_samples=1)
123
+ return next_token
124
+
125
+
126
+ def sample_top_p(probs: torch.Tensor, p: float) -> torch.Tensor:
127
+ """Sample next token from top P probabilities along the last dimension of the input probs tensor.
128
+
129
+ Args:
130
+ probs (torch.Tensor): Input probabilities with token candidates on the last dimension.
131
+ p (int): The p in “top-p”.
132
+ Returns:
133
+ torch.Tensor: Sampled tokens.
134
+ """
135
+ probs_sort, probs_idx = torch.sort(probs, dim=-1, descending=True)
136
+ probs_sum = torch.cumsum(probs_sort, dim=-1)
137
+ mask = probs_sum - probs_sort > p
138
+ probs_sort *= (~mask).float()
139
+ probs_sort.div_(probs_sort.sum(dim=-1, keepdim=True))
140
+ next_token = multinomial(probs_sort, num_samples=1)
141
+ next_token = torch.gather(probs_idx, -1, next_token)
142
+ return next_token
143
+
144
+
145
+ class DummyPoolExecutor:
146
+ """Dummy pool executor to use when we actually have only 1 worker.
147
+ (e.g. instead of ProcessPoolExecutor).
148
+ """
149
+ class DummyResult:
150
+ def __init__(self, func, *args, **kwargs):
151
+ self.func = func
152
+ self.args = args
153
+ self.kwargs = kwargs
154
+
155
+ def result(self):
156
+ return self.func(*self.args, **self.kwargs)
157
+
158
+ def __init__(self, workers, mp_context=None):
159
+ pass
160
+
161
+ def submit(self, func, *args, **kwargs):
162
+ return DummyPoolExecutor.DummyResult(func, *args, **kwargs)
163
+
164
+ def __enter__(self):
165
+ return self
166
+
167
+ def __exit__(self, exc_type, exc_value, exc_tb):
168
+ return
169
+
170
+
171
+ def get_pool_executor(num_workers: int, mp_context=None):
172
+ return ProcessPoolExecutor(num_workers, mp_context) if num_workers > 1 else DummyPoolExecutor(1)
173
+
174
+
175
+ def length_to_mask(lengths: torch.Tensor, max_len: tp.Optional[int] = None) -> torch.Tensor:
176
+ """Utility function to convert a tensor of sequence lengths to a mask (useful when working on padded sequences).
177
+ For example: [3, 5] => [[1, 1, 1, 0, 0], [1, 1, 1, 1, 1]]
178
+
179
+ Args:
180
+ lengths (torch.Tensor): tensor with lengths
181
+ max_len (int): can set the max length manually. Defaults to None.
182
+ Returns:
183
+ torch.Tensor: mask with 0s where there is pad tokens else 1s
184
+ """
185
+ assert len(lengths.shape) == 1, "Length shape should be 1 dimensional."
186
+ final_length = lengths.max().item() if not max_len else max_len
187
+ final_length = max(final_length, 1) # if all seqs are of len zero we don't want a zero-size tensor
188
+ return torch.arange(final_length)[None, :].to(lengths.device) < lengths[:, None]
189
+
190
+
191
+ def hash_trick(word: str, vocab_size: int) -> int:
192
+ """Hash trick to pair each word with an index
193
+
194
+ Args:
195
+ word (str): word we wish to convert to an index
196
+ vocab_size (int): size of the vocabulary
197
+ Returns:
198
+ int: index of the word in the embedding LUT
199
+ """
200
+ hash = int(hashlib.sha256(word.encode("utf-8")).hexdigest(), 16)
201
+ return hash % vocab_size
202
+
203
+
204
+ def with_rank_rng(base_seed: int = 1234):
205
+ """Decorator for a function so that the function will use a Random Number Generator
206
+ whose state depend on the GPU rank. The original RNG state is restored upon returning.
207
+
208
+ Args:
209
+ base_seed (int): Random seed.
210
+ """
211
+ def _decorator(fun: tp.Callable):
212
+ @wraps(fun)
213
+ def _decorated(*args, **kwargs):
214
+ state = torch.get_rng_state()
215
+ seed = base_seed ^ flashy.distrib.rank()
216
+ torch.manual_seed(seed)
217
+ logger.debug('Rank dependent seed set to %d', seed)
218
+ try:
219
+ return fun(*args, **kwargs)
220
+ finally:
221
+ torch.set_rng_state(state)
222
+ logger.debug('RNG state restored.')
223
+ return _decorated
224
+ return _decorator
225
+
226
+
227
+ def collate(tensors: tp.List[torch.Tensor], dim: int = 0) -> tp.Tuple[torch.Tensor, torch.Tensor]:
228
+ """Get a list of tensors and collate them to a single tensor. according to the following logic:
229
+ - `dim` specifies the time dimension which will be stacked and padded.
230
+ - The output will contain 1 new dimension (dimension index 0) which will be the size of
231
+ of the original list.
232
+
233
+ Args:
234
+ tensors (tp.List[torch.Tensor]): List of tensors to collate.
235
+ dim (int): Dimension which will be stacked and padded.
236
+ Returns:
237
+ tp.Tuple[torch.Tensor, torch.Tensor]:
238
+ torch.Tensor: Stacked and padded tensor. The output will contain 1 new dimension
239
+ (dimension index 0) which will be the size of the original list.
240
+ torch.Tensor: Tensor containing length of original tensor sizes (without padding).
241
+ """
242
+ tensors = [x.transpose(0, dim) for x in tensors]
243
+ lens = torch.LongTensor([len(x) for x in tensors])
244
+ padded_tensors = pad_sequence(tensors)
245
+ padded_tensors = padded_tensors.transpose(0, 1)
246
+ padded_tensors = padded_tensors.transpose(1, dim + 1)
247
+ return padded_tensors, lens
248
+
249
+
250
+ # TODO: Move to flashy?
251
+ def copy_state(state: tp.Any, device: tp.Union[torch.device, str] = 'cpu',
252
+ dtype: tp.Optional[torch.dtype] = None) -> tp.Any:
253
+ if isinstance(state, torch.Tensor):
254
+ if dtype is None or not state.is_floating_point():
255
+ dtype = state.dtype
256
+ return state.detach().to(device=device, dtype=dtype, copy=True)
257
+ elif isinstance(state, dict):
258
+ return {k: copy_state(v, device, dtype) for k, v in state.items()}
259
+ elif isinstance(state, list):
260
+ return [copy_state(v, device, dtype) for v in state]
261
+
262
+
263
+ # TODO: Move to flashy?
264
+ @contextmanager
265
+ def swap_state(model, state, **kwargs):
266
+ old_state = copy_state(model.state_dict())
267
+ model.load_state_dict(state, **kwargs)
268
+ try:
269
+ yield
270
+ finally:
271
+ model.load_state_dict(old_state)
272
+
273
+
274
+ @lru_cache(None)
275
+ def warn_once(logger, msg):
276
+ """Warn about a given message only once."""
277
+ logger.warning(msg)
278
+
279
+
280
+ def is_jsonable(x: tp.Any):
281
+ """Check if an object can be serialized into a json:"""
282
+ try:
283
+ json.dumps(x)
284
+ return True
285
+ except (TypeError, OverflowError):
286
+ return False
287
+
288
+
289
+ def load_clap_state_dict(clap_model, path: tp.Union[str, Path]):
290
+ """Wrapper around state dict loading of CLAP model
291
+ addressing compatibility issues between CLAP and AudioCraft
292
+ HuggingFace transformer version.
293
+ See: https://github.com/LAION-AI/CLAP/issues/118
294
+ """
295
+ from clap_module.factory import load_state_dict # type: ignore
296
+ pkg = load_state_dict(path)
297
+ pkg.pop('text_branch.embeddings.position_ids', None)
298
+ clap_model.model.load_state_dict(pkg)
requirements.txt ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ git+https://github.com/TEAMuP-dev/pyharp.git@v0.3.0
2
+ git+https://github.com/facebookresearch/flashy@main
3
+ torch==2.0.1
4
+ torchaudio==2.0.0
5
+ numpy==1.26.4
6
+ einops
7
+ transformers==4.31.0
8
+ sentencepiece
9
+ num2words
10
+ spacy
11
+ pretty_midi
12
+ omegaconf
13
+ huggingface_hub
14
+ soundfile
15
+ av==11.0.0
16
+ julius
17
+ pandas
18
+ xformers==0.0.22
19
+ librosa