Vansh Chugh commited on
Commit
2e1dc7f
·
1 Parent(s): c2b0125

initial deploy

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +4 -0
  2. README.md +4 -6
  3. __init__.py +2 -0
  4. app.py +218 -0
  5. conditioning/__init__.py +6 -0
  6. conditioning/beat_embedder.py +249 -0
  7. conditioning/condition_dispatcher.py +157 -0
  8. conditioning/condition_provider.py +91 -0
  9. conditioning/condition_type.py +26 -0
  10. conditioning/conditioning_method.py +14 -0
  11. conditioning/embedded_condition.py +13 -0
  12. conditioning/embedder.py +50 -0
  13. conditioning/prompt_processor.py +625 -0
  14. conditioning/t5embedder.py +211 -0
  15. config.py +77 -0
  16. data/auto_labelling.py +147 -0
  17. data/dataset_mixed.py +79 -0
  18. data/labels.py +424 -0
  19. data/stem.py +62 -0
  20. data/stemmed_datamodule.py +190 -0
  21. data/stemmed_dataset.py +801 -0
  22. hyperparameters.py +244 -0
  23. inference.py +54 -0
  24. loader.py +42 -0
  25. models/__init__.py +1 -0
  26. models/encodec.py +436 -0
  27. models/lightning_musicgen.py +622 -0
  28. models/loss.py +48 -0
  29. models/modules/__init__.py +23 -0
  30. models/modules/codebooks_patterns.py +548 -0
  31. models/modules/conv.py +346 -0
  32. models/modules/decoder.py +155 -0
  33. models/modules/lstm.py +28 -0
  34. models/modules/norm.py +28 -0
  35. models/modules/rope.py +125 -0
  36. models/modules/seanet.py +257 -0
  37. models/modules/streaming.py +131 -0
  38. models/modules/transformer.py +755 -0
  39. models/musicgen_lm.py +367 -0
  40. models/quantization/__init__.py +9 -0
  41. models/quantization/base.py +104 -0
  42. models/quantization/core_vq.py +420 -0
  43. models/quantization/vq.py +121 -0
  44. requirements.txt +15 -0
  45. train_stage_drums.py +87 -0
  46. training/callback.py +333 -0
  47. training/train.py +260 -0
  48. utils/__init__.py +0 -0
  49. utils/audio.py +293 -0
  50. utils/aws.py +119 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ .DS_Store
4
+ STAGE-repo/
README.md CHANGED
@@ -1,14 +1,12 @@
1
  ---
2
  title: STAGE
3
- emoji: 🔥
4
  colorFrom: blue
5
- colorTo: yellow
6
  sdk: gradio
7
- sdk_version: 6.20.0
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
11
  short_description: Single Stem Accompaniment Generation
12
  ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: STAGE
3
+ emoji: 🏟️
4
  colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: 5.28.0
8
+ python_version: '3.11'
9
  app_file: app.py
10
  pinned: false
11
  short_description: Single Stem Accompaniment Generation
12
  ---
 
 
__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ def main() -> None:
2
+ print("Hello from stage!")
app.py ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ sys.stdout.reconfigure(line_buffering=True)
3
+
4
+ try:
5
+ import spaces
6
+ except ImportError:
7
+ class spaces:
8
+ class GPU:
9
+ def __init__(self, func=None, duration=60):
10
+ self.func = func
11
+
12
+ def __call__(self, *args, **kwargs):
13
+ if self.func is not None:
14
+ return self.func(*args, **kwargs)
15
+ func = args[0]
16
+ return func
17
+
18
+ import tempfile
19
+ import threading
20
+ from pathlib import Path
21
+ import torch
22
+ import torchaudio
23
+ import soundfile as sf
24
+ import gradio as gr
25
+ from huggingface_hub import hf_hub_download
26
+ from safetensors import torch as sft
27
+
28
+ import hyperparameters as hp
29
+ from conditioning.condition_type import ConditionType
30
+ from conditioning.conditioning_method import ConditioningMethod
31
+ from conditioning.prompt_processor import InterleavedContextPromptProcessor
32
+ from conditioning.t5embedder import T5EmbedderGPU
33
+ from models.lightning_musicgen import LightningMusicgen
34
+ from pyharp import ModelCard, build_endpoint
35
+
36
+ REPO_ID = "teamup-tech/STAGE-checkpoints"
37
+ SAMPLE_RATE = 32_000
38
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
39
+
40
+ drums_model: LightningMusicgen | None = None
41
+ bass_model: LightningMusicgen | None = None
42
+ models_on_device = False
43
+ model_loading = True
44
+ model_error: str | None = None
45
+
46
+
47
+ def build_params(encodec_weights: str, lm_weights: str) -> hp.MusicgenParams:
48
+ """Assemble MusicgenParams with explicit weight paths."""
49
+ return hp.MusicgenParams(
50
+ encodec_params=hp.EncodecParams(
51
+ sample_rate=32_000,
52
+ seanet_params=hp.SeaNetParams(128, 64, (8, 5, 4, 4), False, True),
53
+ quantizer_params=hp.QuantizerParams(128, 4, 2048),
54
+ sum_loss_mulitiplier=0,
55
+ weights=encodec_weights,
56
+ ),
57
+ prompt_processor_params=hp.PromptProcessorParams(
58
+ keep_only_valid_steps=True,
59
+ model_class=InterleavedContextPromptProcessor,
60
+ context_dropout=0.1,
61
+ ),
62
+ conditioning_params=hp.ConditioningParams(
63
+ embedder_types={ConditionType.DESCRIPTION: T5EmbedderGPU},
64
+ conditioning_methods={
65
+ ConditionType.DESCRIPTION: ConditioningMethod.CROSS_ATTENTION
66
+ },
67
+ conditioning_dropout=0.5,
68
+ ),
69
+ lm_params=hp.PretrainedSmallLmParams(sep_token=2049, weights=lm_weights),
70
+ )
71
+
72
+
73
+ def load_checkpoint(params: hp.MusicgenParams, ckp_path: str) -> LightningMusicgen:
74
+ """Instantiate model from params and load fine-tuned checkpoint onto CPU."""
75
+ model: LightningMusicgen = params.instantiate()
76
+ sft.load_model(model, ckp_path)
77
+ return model.cpu().eval()
78
+
79
+
80
+ def load_models():
81
+ """Download all weights and build both models on CPU. Background thread only — no CUDA."""
82
+ global drums_model, bass_model, model_loading, model_error
83
+ try:
84
+ print("Downloading shared weights...")
85
+ encodec_path = hf_hub_download(REPO_ID, "encodec_32khz.pt")
86
+ lm_path = hf_hub_download(REPO_ID, "lm-small-weights.pt")
87
+ params = build_params(encodec_path, lm_path)
88
+
89
+ print("Building drums model (CPU)...")
90
+ drums_ckp = hf_hub_download(REPO_ID, "stage-drums.safetensors")
91
+ drums_model = load_checkpoint(params, drums_ckp)
92
+ print("Drums model ready.")
93
+
94
+ print("Building bass model (CPU)...")
95
+ bass_ckp = hf_hub_download(REPO_ID, "stage-bass.safetensors")
96
+ bass_model = load_checkpoint(params, bass_ckp)
97
+ print("Bass model ready.")
98
+ except Exception as e:
99
+ model_error = str(e)
100
+ print(f"Load error: {e}")
101
+ finally:
102
+ model_loading = False
103
+
104
+
105
+ threading.Thread(target=load_models, daemon=True).start()
106
+
107
+
108
+ model_card = ModelCard(
109
+ name="STAGE",
110
+ description=(
111
+ "Single Stem Accompaniment Generation. Provide an audio mix or click track "
112
+ "and STAGE generates a coherent drums or bass stem to accompany it."
113
+ ),
114
+ author="Giorgio Strano, Vansh Chaudhary, Derek Tran",
115
+ tags=["music-generation", "accompaniment", "stems"],
116
+ )
117
+
118
+
119
+ @spaces.GPU(duration=120)
120
+ @torch.inference_mode()
121
+ def process_fn(
122
+ input_audio_path: str,
123
+ instrument: str,
124
+ gen_seconds: int,
125
+ description: str,
126
+ ) -> str:
127
+ """Load context audio, run STAGE autoregressive generation, return the generated stem."""
128
+ global drums_model, bass_model, models_on_device
129
+
130
+ if model_loading:
131
+ raise gr.Error("Model is still loading — please wait a moment and try again.")
132
+ if model_error:
133
+ raise gr.Error(f"Model failed to load: {model_error}")
134
+
135
+ model = drums_model if instrument == "Drums" else bass_model
136
+ if model is None:
137
+ raise gr.Error(f"Model for {instrument} not available.")
138
+
139
+ if not models_on_device:
140
+ drums_model = drums_model.to(DEVICE) # type: ignore[union-attr]
141
+ bass_model = bass_model.to(DEVICE) # type: ignore[union-attr]
142
+ models_on_device = True
143
+
144
+ # load and prepare context audio
145
+ audio_np, orig_sr = sf.read(input_audio_path, always_2d=True)
146
+ context = torch.from_numpy(audio_np.T).float()
147
+ context = torchaudio.functional.resample(context, orig_sr, SAMPLE_RATE)
148
+ if context.shape[0] > 1:
149
+ context = context.mean(dim=0, keepdim=True)
150
+ context = context.reshape(1, 1, -1).to(DEVICE)
151
+
152
+ desc = [description.strip()] if description and description.strip() else [None]
153
+
154
+ out = model.generate(
155
+ n_samples=1,
156
+ gen_seconds=gen_seconds,
157
+ prompt=None,
158
+ context=context,
159
+ style=None,
160
+ beat=None,
161
+ description=desc,
162
+ )
163
+
164
+ # out: (1, 1, T) or (1, 2, T) — save as stereo wav
165
+ audio_out = out.squeeze(0).cpu().float()
166
+ if audio_out.shape[0] == 1:
167
+ audio_out = audio_out.repeat(2, 1)
168
+
169
+ with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
170
+ out_path = f.name
171
+ torchaudio.save(out_path, audio_out, sample_rate=SAMPLE_RATE)
172
+ return out_path
173
+
174
+
175
+ with gr.Blocks() as demo:
176
+ input_components = [
177
+ gr.Audio(
178
+ type="filepath",
179
+ label="Context Track",
180
+ ).harp_required(True),
181
+ gr.Radio(
182
+ choices=["Drums", "Bass"],
183
+ value="Drums",
184
+ label="Instrument",
185
+ info="Which accompaniment stem to generate.",
186
+ ),
187
+ gr.Slider(
188
+ minimum=5,
189
+ maximum=20,
190
+ step=1,
191
+ value=10,
192
+ label="Length (seconds)",
193
+ info="Duration of the generated stem (default: 10 sec, per paper).",
194
+ ),
195
+ gr.Textbox(
196
+ value="",
197
+ label="Style Description",
198
+ info="Optional — describe the mood or style (e.g. 'lo-fi chill groove').",
199
+ placeholder="lo-fi chill groove with soft kick...",
200
+ ),
201
+ ]
202
+ output_components = [
203
+ gr.Audio(
204
+ type="filepath",
205
+ label="Generated Stem",
206
+ ).set_info(
207
+ "The generated accompaniment stem. Mix it back with your context track."
208
+ ),
209
+ ]
210
+
211
+ build_endpoint(
212
+ model_card=model_card,
213
+ input_components=input_components,
214
+ output_components=output_components,
215
+ process_fn=process_fn,
216
+ )
217
+
218
+ demo.queue().launch(pwa=True)
conditioning/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ from typing import Union
2
+ from conditioning.beat_embedder import DirectSinusoidalBeatEmbedder, SinusoidalBeatEmbedder
3
+ from conditioning.t5embedder import T5EmbedderCPU, T5EmbedderGPU
4
+
5
+ ConcreteEmbedder = Union[T5EmbedderCPU, T5EmbedderGPU, SinusoidalBeatEmbedder,
6
+ DirectSinusoidalBeatEmbedder]
conditioning/beat_embedder.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import List, Sequence
2
+ from torch import Tensor
3
+ import numpy as np
4
+ import torch
5
+ from torch import nn
6
+ from dataclasses import dataclass
7
+
8
+ from utils.audio import load_audio, make_variable_frequency_sinewave
9
+ from utils.plotting import plot_waveforms
10
+ from conditioning.embedded_condition import EmbeddedCondition
11
+ from conditioning.embedder import Embedder, LinearProjectionEmbedder
12
+ import config as cfg
13
+
14
+
15
+ @dataclass
16
+ class Beat:
17
+ """
18
+ beats / downbeats:
19
+ Tensor of Int or Long type, containing indices of samples where
20
+ beats / downbeats occur
21
+ seq_len: int representing the total length of the audio in samples
22
+ """
23
+ beats: Tensor
24
+ downbeats: Tensor
25
+ seq_len: int
26
+
27
+
28
+ class DirectSinusoidalBeatEmbedder(Embedder):
29
+
30
+ def __init__(self, embedding_dim: int):
31
+ super().__init__(input_dim=2, embedding_dim=embedding_dim)
32
+ self.encodec_framerate: int = 50
33
+ self.sample_rate: int = 32_000
34
+
35
+ def forward(self,
36
+ x: Sequence[Beat],
37
+ duplicate_for_cfg: bool = False) -> EmbeddedCondition:
38
+ """
39
+ Returns: A tensor of shape [B, S, H], where B is batch, H is
40
+ embedding dim, and S is the length of the sequence
41
+ """
42
+
43
+ assert len(set([t.seq_len for t in x])) == 1
44
+
45
+ emb_list: List[Tensor] = [None] * len(x) # type: ignore
46
+
47
+ ratio = self.encodec_framerate / self.sample_rate
48
+ for i, track in enumerate(x):
49
+
50
+ # convert indices from explicit to latent space
51
+ beats = torch.floor(track.beats.float() * ratio).long()
52
+ downbeats = torch.floor(track.downbeats.float() * ratio).long()
53
+ seq_len = round(track.seq_len * ratio)
54
+
55
+ # make a variable-frequency sinewave lined with the beats
56
+ beat_emb = make_variable_frequency_sinewave(seq_len, beats)
57
+ downbeat_emb = make_variable_frequency_sinewave(seq_len, downbeats)
58
+
59
+ emb_list[i] = torch.stack((beat_emb, downbeat_emb), dim=-1)
60
+
61
+ embeds: Tensor = torch.stack(emb_list)
62
+
63
+ # create an embedding that's just the beat sinewave in every dimension
64
+ embeds = torch.cat(
65
+ (embeds[..., :1].repeat(1, 1, self.embedding_dim // 2),
66
+ embeds[..., 1:].repeat(1, 1, self.embedding_dim // 2)),
67
+ dim=-1)
68
+ mask = torch.ones(embeds.shape[:-1],
69
+ device=embeds.device,
70
+ dtype=torch.bool)
71
+
72
+ # concatenate an empty condition
73
+ if duplicate_for_cfg:
74
+ embeds = torch.cat((embeds, torch.zeros_like(embeds)), dim=0)
75
+ mask = torch.cat((mask, torch.zeros_like(mask)), dim=0)
76
+
77
+ return EmbeddedCondition(embeds, mask)
78
+
79
+ def null_condition(self, batch_size: int) -> EmbeddedCondition:
80
+ return EmbeddedCondition(
81
+ torch.zeros(batch_size,
82
+ 1,
83
+ self.embedding_dim,
84
+ dtype=torch.float32,
85
+ device=self.output_proj.weight.device),
86
+ torch.zeros(batch_size,
87
+ 1,
88
+ dtype=torch.bool,
89
+ device=self.output_proj.weight.device))
90
+
91
+
92
+ class SinusoidalBeatEmbedder(LinearProjectionEmbedder):
93
+
94
+ def __init__(self, embedding_dim: int):
95
+ super().__init__(input_dim=2, embedding_dim=embedding_dim)
96
+ self.encodec_framerate: int = 50
97
+ self.sample_rate: int = 32_000
98
+
99
+ def forward(self,
100
+ x: Sequence[Beat],
101
+ duplicate_for_cfg: bool = False) -> EmbeddedCondition:
102
+ """
103
+ Returns: A tensor of shape [B, S, H], where B is batch, H is
104
+ embedding dim, and S is the length of the sequence
105
+ """
106
+
107
+ assert len(set([t.seq_len for t in x])) == 1
108
+
109
+ emb_list: List[Tensor] = [None] * len(x) # type: ignore
110
+
111
+ for i, track in enumerate(x):
112
+
113
+ beats = (track.beats / self.sample_rate *
114
+ self.encodec_framerate).round().int()
115
+ downbeats = (track.downbeats / self.sample_rate *
116
+ self.encodec_framerate).round().int()
117
+
118
+ seq_len = round(track.seq_len / self.sample_rate *
119
+ self.encodec_framerate)
120
+
121
+ beat_emb = make_variable_frequency_sinewave(seq_len, beats)
122
+ downbeat_emb = make_variable_frequency_sinewave(seq_len, downbeats)
123
+
124
+ emb_list[i] = torch.stack((beat_emb, downbeat_emb), dim=-1)
125
+
126
+ embeds: Tensor = torch.stack(emb_list)
127
+
128
+ embeds = self.output_proj(embeds.to(self.output_proj.weight))
129
+ mask = torch.ones(embeds.shape[:-1],
130
+ device=embeds.device,
131
+ dtype=torch.bool)
132
+
133
+ if duplicate_for_cfg:
134
+ embeds = torch.cat((embeds, torch.zeros_like(embeds)), dim=0)
135
+ mask = torch.cat((mask, torch.zeros_like(mask)), dim=0)
136
+
137
+ return EmbeddedCondition(embeds, mask)
138
+
139
+ def null_condition(self, batch_size: int) -> EmbeddedCondition:
140
+ return EmbeddedCondition(
141
+ torch.zeros(batch_size,
142
+ 1,
143
+ self.embedding_dim,
144
+ dtype=torch.float32,
145
+ device=self.output_proj.weight.device),
146
+ torch.zeros(batch_size,
147
+ 1,
148
+ dtype=torch.bool,
149
+ device=self.output_proj.weight.device))
150
+
151
+
152
+ class SinusoidalBeatEmbedderMLP(Embedder):
153
+
154
+ def __init__(self, embedding_dim: int):
155
+ super().__init__(input_dim=2, embedding_dim=embedding_dim)
156
+ # self.embedding_dim: int = embedding_dim
157
+ self.encodec_framerate: int = 50
158
+ self.sample_rate: int = 32_000
159
+
160
+ self.output_proj: nn.Sequential = nn.Sequential(
161
+ nn.Linear(self.input_dim, 512), nn.ReLU(),
162
+ nn.Linear(512, self.embedding_dim))
163
+
164
+ def forward(self,
165
+ x: Sequence[Beat],
166
+ duplicate_for_cfg: bool = False) -> EmbeddedCondition:
167
+ """
168
+ Returns: A tensor of shape [B, S, H], where B is batch, H is
169
+ embedding dim, and S is the length of the sequence
170
+ """
171
+
172
+ assert len(set([t.seq_len for t in x])) == 1
173
+
174
+ emb_list: List[Tensor] = [None] * len(x) # type: ignore
175
+
176
+ for i, track in enumerate(x):
177
+
178
+ beats = (track.beats / self.sample_rate *
179
+ self.encodec_framerate).round().int()
180
+ downbeats = (track.downbeats / self.sample_rate *
181
+ self.encodec_framerate).round().int()
182
+
183
+ seq_len = round(track.seq_len / self.sample_rate *
184
+ self.encodec_framerate)
185
+
186
+ beat_emb = make_variable_frequency_sinewave(seq_len, beats)
187
+ downbeat_emb = make_variable_frequency_sinewave(seq_len, downbeats)
188
+
189
+ emb_list[i] = torch.stack((beat_emb, downbeat_emb), dim=-1)
190
+
191
+ embeds: Tensor = torch.stack(emb_list)
192
+
193
+ embeds = self.output_proj(embeds.to(self.output_proj[-1].weight))
194
+ mask = torch.ones(embeds.shape[:-1],
195
+ device=embeds.device,
196
+ dtype=torch.bool)
197
+
198
+ if duplicate_for_cfg:
199
+ embeds = torch.cat((embeds, torch.zeros_like(embeds)), dim=0)
200
+ mask = torch.cat((mask, torch.zeros_like(mask)), dim=0)
201
+
202
+ return EmbeddedCondition(embeds, mask)
203
+
204
+ def null_condition(self, batch_size: int) -> EmbeddedCondition:
205
+ return EmbeddedCondition(
206
+ torch.zeros(batch_size,
207
+ 1,
208
+ self.embedding_dim,
209
+ dtype=torch.float32,
210
+ device=self.output_proj[-1].weight.device),
211
+ torch.zeros(batch_size,
212
+ 1,
213
+ dtype=torch.bool,
214
+ device=self.output_proj[-1].weight.device))
215
+
216
+
217
+ if __name__ == "__main__":
218
+ song_path = cfg.moises_path(
219
+ ) / "0d528a19-cb0f-4421-b250-444f9343e51c/mixed.wav"
220
+ song = load_audio(song_path)
221
+ n_samples = song.shape[-1]
222
+ loaded = np.load(
223
+ "/home/tkol/dev/datasets/moisesdb/moisesdb_v0.1/0d528a19-cb0f-4421-b250-444f9343e51c/beatthis.npz"
224
+ )
225
+ beats = torch.tensor([int(b * 32_000) for b in loaded["beats"]],
226
+ dtype=torch.int32)
227
+ downbeats = torch.tensor([int(b * 32_000) for b in loaded["downbeats"]],
228
+ dtype=torch.int32)
229
+ assert n_samples > beats.max()
230
+
231
+ b = Beat(beats=beats, downbeats=downbeats, seq_len=n_samples)
232
+ embedder = SinusoidalBeatEmbedder(1024)
233
+ emb = embedder([b])
234
+
235
+ directembedder = DirectSinusoidalBeatEmbedder(1024)
236
+ directemb = directembedder([b])
237
+ # from matplotlib import pyplot as plt
238
+ # plt.style.use(cfg.ROOT / "tkol.mplstyle")
239
+ # plt.figure(figsize=(100, 10))
240
+ # plt.scatter(x=torch.arange(emb.shape[-1]) / 50 * 32_000, y=emb)
241
+ # plt.show()
242
+
243
+ # plot_waveforms(
244
+ # song,
245
+ # emb,
246
+ # # savepath=cfg.ROOT / "plots/beatontrack.png",
247
+ # figsize=(50, 3),
248
+ # dpi=100,
249
+ # )
conditioning/condition_dispatcher.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Callable, List, Sequence, Dict, Type
2
+ from torch import nn, Tensor
3
+ import torch
4
+
5
+ from conditioning.condition_type import ConditionType
6
+ from conditioning.conditioning_method import ConditioningMethod
7
+ from conditioning.embedded_condition import EmbeddedCondition
8
+
9
+
10
+ class ConditionDispatcher(nn.Module):
11
+
12
+ def __init__(self, cond_to_method: Dict[ConditionType, ConditioningMethod],
13
+ embedding_dim: int, condition_dropout: float):
14
+ super().__init__()
15
+ self.embedding_dim: int = embedding_dim
16
+ self.condition_dropout: float = condition_dropout
17
+
18
+ # what fusing method to use for each conditioning type
19
+ self.cond_to_method: Dict[ConditionType,
20
+ ConditioningMethod] = cond_to_method
21
+
22
+ # what condition types is each method applied to
23
+ self.method_to_conds: Dict[ConditioningMethod, List[ConditionType]] = {}
24
+ for c, m in self.cond_to_method.items():
25
+ self.method_to_conds[m] = self.method_to_conds.get(m, []) + [c]
26
+
27
+ # instantiate fuser classes
28
+ self.method_to_fuser: nn.ModuleDict = nn.ModuleDict({
29
+ m.value:
30
+ METHOD_TO_FUSER_TYPE[m](len(self.method_to_conds[m]),
31
+ self.embedding_dim)
32
+ for m in self.method_to_conds.keys()
33
+ })
34
+
35
+ def dropout(self, x: EmbeddedCondition):
36
+ if self.training and self.condition_dropout > 0:
37
+ drop = torch.rand(x.data.shape[0]) < self.condition_dropout
38
+ # x.data[drop] = torch.zeros_like(x.data[drop]) #.detach()
39
+ x.data[drop] = x.data[drop] * 0
40
+ if x.mask is not None:
41
+ x.mask[drop] = torch.zeros_like(x.mask[drop]) #.detach()
42
+ return x
43
+
44
+ def forward(
45
+ self, embedded_conditions: Dict[ConditionType, EmbeddedCondition]
46
+ ) -> Dict[ConditioningMethod, EmbeddedCondition]:
47
+ assert embedded_conditions.keys() == self.cond_to_method.keys()
48
+
49
+ # figure out where does each conditioning go, between:
50
+ # - summed to the input of the lm (after embedding)
51
+ # - prepended to the input of the lm (after embedding)
52
+ # - in cross-attention of the lm
53
+ method_to_embeddings: Dict[ConditioningMethod,
54
+ List[EmbeddedCondition]] = {}
55
+
56
+ # for condition_type, emb in embedded_conditions.items():
57
+ for condition_type in sorted(embedded_conditions.keys()):
58
+ emb = embedded_conditions[condition_type]
59
+
60
+ # apply dropout to each condition
61
+ emb = self.dropout(emb)
62
+
63
+ method: ConditioningMethod = self.cond_to_method[condition_type]
64
+ method_to_embeddings[method] = method_to_embeddings.get(method,
65
+ []) + [emb]
66
+
67
+ # has to return a dict that associates one embedded condition to each
68
+ # conditioning method
69
+ method_to_fused_embedding: Dict[ConditioningMethod,
70
+ EmbeddedCondition] = {}
71
+
72
+ # if multiple conditions are dispatched to the same method, we have to
73
+ # fuse them
74
+ for m, emb_list in method_to_embeddings.items():
75
+ fused_embedding: EmbeddedCondition = self.method_to_fuser[m.value](
76
+ emb_list)
77
+ method_to_fused_embedding[m] = fused_embedding
78
+
79
+ return method_to_fused_embedding
80
+
81
+
82
+ class CrossAttentionFuser(nn.Module):
83
+
84
+ def __init__(self, n_conditions: int, embedding_dim: int):
85
+ super().__init__()
86
+ self.n_conditions: int = n_conditions
87
+ self.embedding_dim: int = embedding_dim
88
+
89
+ # use concatenation with segment embedding to merge conditions
90
+ if self.n_conditions > 1:
91
+ self.segment_embedding = nn.Embedding(self.n_conditions - 1,
92
+ self.embedding_dim)
93
+
94
+ def forward(self, conds: Sequence[EmbeddedCondition]) -> EmbeddedCondition:
95
+ if len(conds) == 0:
96
+ raise RuntimeError("Received a list of 0 length. There is a bug.")
97
+ if len(conds) == 1:
98
+ return conds[0]
99
+
100
+ # apply segment embedding
101
+ if len(conds) == 2:
102
+ assert self.n_conditions == 2
103
+ assert conds[0].mask is not None and conds[1].mask is not None
104
+ cond1_emb = conds[1].data
105
+ cond1_ids = torch.zeros(cond1_emb.shape[0],
106
+ cond1_emb.shape[1],
107
+ dtype=torch.long,
108
+ device=conds[1].data.device)
109
+ cond1_segembed = self.segment_embedding(cond1_ids)
110
+ cond1_emb = cond1_emb + cond1_segembed
111
+ total_cond = torch.cat((conds[0].data, cond1_emb), dim=1)
112
+ total_mask = torch.cat((conds[0].mask, conds[1].mask), dim=1)
113
+ return EmbeddedCondition(total_cond, total_mask)
114
+
115
+ # multiple conditions in cross-atteniton are not implemented
116
+ raise NotImplementedError()
117
+
118
+
119
+ class PrependFuser(nn.Module):
120
+
121
+ def __init__(self, n_conditions: int, embedding_dim: int):
122
+ super().__init__()
123
+ self.n_conditions: int = n_conditions
124
+ self.embedding_dim: int = embedding_dim
125
+
126
+ def forward(self, conds: Sequence[EmbeddedCondition]):
127
+ if len(conds) == 0:
128
+ raise RuntimeError("Received a list of 0 length. There is a bug.")
129
+ if len(conds) == 1:
130
+ return conds[0]
131
+
132
+ # multiple conditions prepended to input are not implemented
133
+ raise NotImplementedError()
134
+
135
+
136
+ class SumFuser(nn.Module):
137
+
138
+ def __init__(self, n_conditions: int, embedding_dim: int):
139
+ super().__init__()
140
+ self.n_conditions: int = n_conditions
141
+ self.embedding_dim: int = embedding_dim
142
+
143
+ def forward(self, conds: Sequence[EmbeddedCondition]):
144
+ if len(conds) == 0:
145
+ raise RuntimeError("Received a list of 0 length. There is a bug.")
146
+ if len(conds) == 1:
147
+ return conds[0]
148
+
149
+ # multiple conditions summed to input are not implemented
150
+ raise NotImplementedError()
151
+
152
+
153
+ METHOD_TO_FUSER_TYPE: Dict[ConditioningMethod, Type[nn.Module]] = {
154
+ ConditioningMethod.CROSS_ATTENTION: CrossAttentionFuser,
155
+ ConditioningMethod.INPUT_PREPEND: PrependFuser,
156
+ ConditioningMethod.INPUT_SUM: SumFuser,
157
+ }
conditioning/condition_provider.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Dict, Optional, Type, Any, Union
2
+ from torch import Tensor, nn
3
+ import torch
4
+
5
+ from conditioning.condition_type import ConditionType
6
+ from conditioning.embedded_condition import EmbeddedCondition
7
+ from conditioning.t5embedder import T5EmbedderCPU
8
+ from conditioning import ConcreteEmbedder
9
+
10
+
11
+ class ConditionProvider(nn.Module):
12
+
13
+ def __init__(self, embedding_dim: int,
14
+ embedder_types: Dict[ConditionType, Type[ConcreteEmbedder]]):
15
+ super().__init__()
16
+ self.embedding_dim: int = embedding_dim
17
+ self.embedders: nn.ModuleDict = nn.ModuleDict()
18
+
19
+ # instantiate embedders for all condition types
20
+ for condition, embedder_type in embedder_types.items():
21
+ self.embedders[condition.value] = embedder_type(
22
+ embedding_dim=self.embedding_dim)
23
+
24
+ # def duplicate_conditions_for_cfg(self, conditions: Dict[str, Any]):
25
+ # for cond_name, cond_data in conditions.items():
26
+ # if isinstance(cond_data, list) and isinstance(cond_data[0], str):
27
+ # conditions[cond_name] += [""] * len(cond_data)
28
+ # elif isinstance(cond_data, Tensor):
29
+ # conditions[cond_name] = torch.cat(
30
+ # (cond_data, torch.zeros_like(cond_data)), dim=0)
31
+ # else:
32
+ # raise RuntimeError(
33
+ # f"I don't know what an emtpy condition for type: {type}")
34
+
35
+ # embed condition into tensors with the corresponding embedders
36
+ def process_conditions(
37
+ self,
38
+ conditions: Dict[str, Any],
39
+ duplicate_for_cfg: bool = False,
40
+ batch_size: Optional[int] = None,
41
+ ) -> Dict[ConditionType, EmbeddedCondition]:
42
+
43
+ processed_conditions: Dict[ConditionType, EmbeddedCondition] = {}
44
+
45
+ # for each condition in the dict, try to find the corresponding embedder
46
+ # for cond_name, cond_data in conditions.items():
47
+ # condtype: ConditionType = ConditionType(cond_name)
48
+ # if condtype.value not in self.embedders:
49
+ # raise RuntimeError(f"I don't have an embedder for a condition "
50
+ # f"named {cond_name}")
51
+ # embedded: EmbeddedCondition = self.embedders[condtype.value](
52
+ # cond_data, duplicate_for_cfg=duplicate_for_cfg)
53
+ # processed_conditions[condtype] = embedded
54
+
55
+ # for each of my embedders, find the corresponding condition in the dict
56
+ for cond_name, embedder in self.embedders.items():
57
+ condtype: ConditionType = ConditionType(cond_name)
58
+ if (condtype.value in conditions and
59
+ conditions[condtype.value] is not None):
60
+ embedded: EmbeddedCondition = embedder(
61
+ conditions[condtype.value],
62
+ duplicate_for_cfg=duplicate_for_cfg)
63
+ processed_conditions[condtype] = embedded
64
+ else:
65
+ if batch_size is None:
66
+ raise RuntimeError(
67
+ f"Condition {cond_name} was not provided in batch. "
68
+ f"I need the batch size to generate a null condition")
69
+ embedded: EmbeddedCondition = embedder.null_condition(
70
+ batch_size + (batch_size * duplicate_for_cfg))
71
+ processed_conditions[condtype] = embedded
72
+
73
+ return processed_conditions
74
+
75
+
76
+ if __name__ == "__main__":
77
+ import torch
78
+
79
+ conditions = {
80
+ "description": "The quick brown fox jumps over the lazy dog.",
81
+ # "context": torch.rand(4, 32_000),
82
+ # "style": torch.rand(4, 32_000),
83
+ }
84
+
85
+ provider = ConditionProvider(embedding_dim=2048,
86
+ embedder_types={
87
+ ConditionType.DESCRIPTION: T5EmbedderCPU,
88
+ })
89
+
90
+ with torch.no_grad():
91
+ processed_conditions = provider.process_conditions(conditions)
conditioning/condition_type.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+ from functools import total_ordering
3
+
4
+
5
+ class ConditionType(Enum):
6
+ DESCRIPTION = "description"
7
+ CONTEXT = "context"
8
+ STYLE = "style"
9
+ BEAT = "beat"
10
+
11
+ def __lt__(self, other):
12
+
13
+ order = ["DESCRIPTION", "BEAT", "CONTEXT", "STYLE"]
14
+ if not isinstance(other, ConditionType):
15
+ raise NotImplementedError()
16
+ return order.index(self.name) < order.index(other.name)
17
+
18
+
19
+ if __name__ == "__main__":
20
+ c = ConditionType("description")
21
+ print(f'{c=}')
22
+ print(f'{c.value=}')
23
+
24
+ c2 = ConditionType("beat")
25
+
26
+ print(c < c2)
conditioning/conditioning_method.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+
4
+ class ConditioningMethod(Enum):
5
+
6
+ INPUT_PREPEND = "input_prepend"
7
+ INPUT_SUM = "input_sum"
8
+ CROSS_ATTENTION = "cross_attention"
9
+
10
+
11
+ if __name__ == "__main__":
12
+ c = ConditioningMethod.INPUT_PREPEND
13
+ print(f'{c=}')
14
+ print(f'{c.value=}')
conditioning/embedded_condition.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from torch import Tensor
2
+ from dataclasses import dataclass
3
+ from typing import Optional
4
+
5
+
6
+ @dataclass(repr=False)
7
+ class EmbeddedCondition:
8
+ data: Tensor
9
+ mask: Optional[Tensor]
10
+
11
+ def __repr__(self) -> str:
12
+ return (f"data: {list(self.data.shape)}; mask: "
13
+ f"{list(self.mask.shape) if self.mask is not None else 'None'}")
conditioning/embedder.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Abstract base classes for conditioning embedders.
3
+ An embedder takes some conditioning data and embeds/encodes it into vectors
4
+ that are then fed to the LM according to the condition strategy specified
5
+ in the config.
6
+
7
+ Embedding vectors should have the same latent dimensionality of the
8
+ transformer, so every embedder takes as input to the constructor a
9
+ parameter `embedding_dim`, that should be the same as the hidden dim of
10
+ the transformer.
11
+ """
12
+
13
+ from torch import nn
14
+ from abc import ABC, abstractmethod
15
+
16
+ from conditioning.embedded_condition import EmbeddedCondition
17
+
18
+
19
+ class Embedder(ABC, nn.Module):
20
+
21
+ def __init__(self, input_dim: int, embedding_dim: int):
22
+ super().__init__()
23
+ self.input_dim: int = input_dim
24
+ self.embedding_dim: int = embedding_dim
25
+
26
+ @abstractmethod
27
+ def forward(self, x, duplicate_for_cfg: bool) -> EmbeddedCondition:
28
+ ...
29
+
30
+ @abstractmethod
31
+ def null_condition(self, batch_size: int) -> EmbeddedCondition:
32
+ ...
33
+
34
+
35
+ class LinearProjectionEmbedder(ABC, nn.Module):
36
+
37
+ def __init__(self, input_dim: int, embedding_dim: int):
38
+ super().__init__()
39
+ self.input_dim: int = input_dim
40
+ self.embedding_dim: int = embedding_dim
41
+ self.output_proj: nn.Linear = nn.Linear(self.input_dim,
42
+ self.embedding_dim)
43
+
44
+ @abstractmethod
45
+ def forward(self, x, duplicate_for_cfg: bool) -> EmbeddedCondition:
46
+ ...
47
+
48
+ @abstractmethod
49
+ def null_condition(self, batch_size: int) -> EmbeddedCondition:
50
+ ...
conditioning/prompt_processor.py ADDED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Processing the prompt of the language model.
3
+ With "prompt", we always refer to the sequence that is fed as input to
4
+ the transformer model.
5
+
6
+ This sequence might contain conditioning data depending on the architecture
7
+ of the model.
8
+
9
+ Every prompt processor should handle at least:
10
+ - Encoding of the input tokens with encodec
11
+ - Interleaving the EnCodec tokens according to the proper pattern
12
+
13
+ All classes should extend the abstract PromptProcessor class and implement
14
+ their custom preprocessing strategy
15
+ """
16
+ from torch import Tensor, nn
17
+ import torch
18
+ from typing import Callable, Dict, Any, List, Optional, Tuple
19
+ from abc import ABC, abstractmethod
20
+ from config import ConfigurationError
21
+ from models.modules.codebooks_patterns import DelayedPatternProvider, Pattern
22
+ from models.encodec import EncodecModel
23
+ from utils.audio import pad_stack
24
+
25
+
26
+ class PromptProcessor(ABC, nn.Module):
27
+ uses_sep_token: bool = False
28
+ """
29
+ Abstract base class for any prompt processor.
30
+ """
31
+
32
+ def __init__(self, encodec_model: EncodecModel, special_token: int,
33
+ keep_only_valid_steps: bool, **kwargs):
34
+ super().__init__()
35
+
36
+ self.__dict__["encodec_model"] = encodec_model
37
+ # self.encodec_model: EncodecModel = encodec_model
38
+ self.special_token: int = special_token
39
+ self.keep_only_valid_steps = keep_only_valid_steps
40
+ self.pattern_provider = DelayedPatternProvider(
41
+ self.encodec_model.num_codebooks)
42
+
43
+ def interleave(
44
+ self, prompt: Tensor,
45
+ keep_only_valid_steps: bool) -> Tuple[Tensor, Tensor, Pattern]:
46
+ # interleave codes
47
+ prompt = prompt.contiguous()
48
+ pattern: Pattern = self.pattern_provider.get_pattern(prompt.shape[-1])
49
+ prompt, indices, mask = pattern.build_pattern_sequence(
50
+ prompt,
51
+ self.special_token,
52
+ keep_only_valid_steps=keep_only_valid_steps)
53
+
54
+ return prompt, mask, pattern
55
+
56
+ def deinterleave_logits(self, logits: Tensor,
57
+ pattern: Pattern) -> Tuple[Tensor, Tensor]:
58
+ logits = logits.permute(0, 3, 1, 2)
59
+ logits, _, logits_mask = pattern.revert_pattern_logits(
60
+ logits,
61
+ float("nan"),
62
+ keep_only_valid_steps=self.keep_only_valid_steps)
63
+ logits = logits.permute(0, 2, 3, 1)
64
+ logits_mask = logits_mask[None, :, :].expand(logits.shape[0], -1, -1)
65
+ return logits, logits_mask
66
+
67
+ def encode_and_pad_list(self, tensors: List[Tensor],
68
+ pad_value: int) -> Tensor:
69
+ encoded = [self.encode(t.view(1, 1, -1)).squeeze(0) for t in tensors]
70
+
71
+ max_len = max(t.shape[-1] for t in encoded)
72
+ padded_tensors = [
73
+ torch.nn.functional.pad(t, (max_len - t.shape[-1], 0),
74
+ value=pad_value) for t in encoded
75
+ ]
76
+ stack = torch.stack(padded_tensors, dim=0)
77
+ return stack
78
+
79
+ def encode(self, x: Tensor) -> Tensor:
80
+ if x.shape[-1] < 7:
81
+ return torch.empty(x.shape[1],
82
+ self.encodec_model.num_codebooks,
83
+ 0,
84
+ dtype=torch.long,
85
+ device=x.device)
86
+ self.encodec_model.eval()
87
+ with torch.no_grad():
88
+ return self.encodec_model.encode(x)
89
+
90
+ @abstractmethod
91
+ def preprocess(
92
+ self, batch: Dict[str, Any]
93
+ ) -> Tuple[Tensor, Tensor, Tensor, Callable[[Tensor], Tuple[Tensor,
94
+ Tensor]]]:
95
+ ...
96
+
97
+
98
+ class ContextPromptProcessor(PromptProcessor):
99
+ """
100
+ Base class for a prompt processor that encodes target and
101
+ context with encodec.
102
+ """
103
+
104
+ def __init__(self, encodec_model: EncodecModel, special_token: int,
105
+ keep_only_valid_steps: bool, context_dropout: float):
106
+ super().__init__(encodec_model, special_token, keep_only_valid_steps)
107
+ self.context_dropout: float = context_dropout
108
+
109
+ def encode_target_and_context_sequential(
110
+ self, target: Tensor, context: Tensor) -> Tuple[Tensor, Tensor]:
111
+ target_codes = self.encode(target)
112
+ context_codes = self.encode(context)
113
+ return target_codes, context_codes
114
+
115
+ def encode_target_and_context_parallel(
116
+ self, target: Tensor, context: Tensor) -> Tuple[Tensor, Tensor]:
117
+
118
+ batch_size = target.shape[0]
119
+ assert context.shape[0] == batch_size
120
+
121
+ # concatenate along batch target and context to encode in parallel
122
+ encodec_input = torch.cat((target, context), dim=0)
123
+
124
+ # encode with encodec
125
+ encodec_output: Tensor = self.encode(encodec_input)
126
+
127
+ target_codes: Tensor = encodec_output[:batch_size, ...]
128
+ context_codes: Tensor = encodec_output[-batch_size:, ...]
129
+ return target_codes, context_codes
130
+
131
+ def encode_target_and_context(
132
+ self, target: Tensor,
133
+ context: Tensor | List[Tensor]) -> Tuple[Tensor, Tensor]:
134
+
135
+ if isinstance(context, list):
136
+ context_codes: Tensor = self.encode_and_pad_list(
137
+ context, self.special_token)
138
+ target_codes: Tensor = self.encode(target)
139
+ return target_codes, context_codes
140
+
141
+ assert isinstance(context, Tensor)
142
+
143
+ target_len = target.shape[-1]
144
+ context_len = context.shape[-1]
145
+
146
+ if target_len == context_len:
147
+ return self.encode_target_and_context_parallel(target, context)
148
+ else:
149
+ return self.encode_target_and_context_sequential(target, context)
150
+
151
+ @abstractmethod
152
+ def prepare_for_generation(
153
+ self, prompt: Optional[Tensor], context: Optional[Tensor],
154
+ gen_sequence: Tensor, use_cfg: bool,
155
+ context_dropout_mask: Optional[Tensor]
156
+ ) -> Tuple[Tensor, Tensor, int, Callable[[Tensor], Tuple[Tensor, Tensor]]]:
157
+ """
158
+ Take prompt and context, prepare them, apply cfg, and insert them
159
+ in dummy sequence full of -1 for generation. Return:
160
+ - gen_sequence
161
+ - gen_mask
162
+ - start_offset
163
+ - closure to decode sequence once filled with gen tokens
164
+ """
165
+
166
+
167
+ class DefaultPromptProcessor(PromptProcessor):
168
+ """
169
+ A prompt processor that only encodes the target with encodec, without
170
+ conditioning on context.
171
+ """
172
+
173
+ def preprocess(self, batch: Dict[str, Any]):
174
+ target = batch["target"]
175
+
176
+ # encode target
177
+ target_codes: Tensor = self.encode(target)
178
+
179
+ # interleave
180
+ prompt, _, pattern = self.interleave(target_codes,
181
+ self.keep_only_valid_steps)
182
+
183
+ def decode_logits_fn(logits: Tensor) -> Tuple[Tensor, Tensor]:
184
+ return self.deinterleave_logits(logits, pattern)
185
+
186
+ mask = (prompt != 2048)
187
+
188
+ return prompt, mask, target_codes, decode_logits_fn
189
+
190
+ def prepare_for_generation(
191
+ self,
192
+ prompt: Optional[Tensor],
193
+ context: Optional[Tensor],
194
+ gen_sequence: Tensor,
195
+ use_cfg: bool,
196
+ context_dropout_mask: Optional[Tensor],
197
+ ) -> Tuple[Tensor, Tensor, int, Callable[[Tensor], Tuple[Tensor, Tensor]]]:
198
+
199
+ if context is not None or context_dropout_mask is not None:
200
+ raise ConfigurationError(
201
+ "This model does not support context conditioning in the prompt"
202
+ )
203
+ if prompt is None:
204
+ prompt_codes = torch.empty(gen_sequence.shape[0],
205
+ gen_sequence.shape[1],
206
+ 0,
207
+ dtype=torch.long,
208
+ device=gen_sequence.device)
209
+ else:
210
+ prompt_codes = self.encode(prompt)
211
+
212
+ # TODO: make sure that duplicating for cfg is needed here
213
+ if use_cfg:
214
+ gen_sequence = torch.cat((gen_sequence, gen_sequence), dim=0)
215
+ prompt_codes = torch.cat((prompt_codes, prompt_codes), dim=0)
216
+
217
+ # insert encoded prompt into sequence
218
+ gen_sequence[..., :prompt_codes.shape[-1]] = prompt_codes
219
+
220
+ # interleave sequence (keep_only_valid_steps always false for inference)
221
+ gen_sequence, gen_mask, pattern = self.interleave(
222
+ gen_sequence, keep_only_valid_steps=False)
223
+
224
+ # compute first offset of the sequence to generate
225
+ start_offset: int = pattern.get_first_step_with_timesteps( # type: ignore
226
+ prompt_codes.shape[-1])
227
+
228
+ mask: Tensor = (gen_sequence != 2048)
229
+
230
+ def decode_sequence_closure(sequence) -> Tuple[Tensor, Tensor]:
231
+ out_codes, _, out_mask = pattern.revert_pattern_sequence(
232
+ sequence, special_token=-1, keep_only_valid_steps=False)
233
+
234
+ return out_codes, out_mask
235
+
236
+ return gen_sequence, mask, start_offset, decode_sequence_closure
237
+
238
+
239
+ class StraightContextPromptProcessor(ContextPromptProcessor):
240
+ """
241
+ Corresponding implementation to Bart and Azir models.
242
+
243
+ Interleaves only the target. Concatenates straight context to
244
+ interleaved target.
245
+ """
246
+
247
+ def preprocess(self, batch: Dict[str, Any]):
248
+ target = batch["target"]
249
+ context = batch["context"]
250
+
251
+ # encode target and context
252
+ target_codes, context_codes = self.encode_target_and_context(
253
+ target, context)
254
+
255
+ # apply dropout to context
256
+ if self.training and self.context_dropout > 0:
257
+ drop = torch.rand(context_codes.shape[0]) < self.context_dropout
258
+ context_codes[drop] = torch.full_like(context_codes[drop],
259
+ self.special_token)
260
+
261
+ # interleave target only
262
+ prompt, mask, pattern = self.interleave(target_codes,
263
+ self.keep_only_valid_steps)
264
+
265
+ # concatenate context and prompt
266
+ prompt = torch.cat((context_codes, prompt), dim=-1)
267
+
268
+ def decode_logits_fn(logits: Tensor) -> Tuple[Tensor, Tensor]:
269
+ # remove context
270
+ n_context_codes = context_codes.shape[-1]
271
+ logits = logits[:, :, n_context_codes:, :]
272
+
273
+ # deinterleave
274
+ return self.deinterleave_logits(logits, pattern)
275
+
276
+ raise NotImplementedError("mask implementation missing")
277
+
278
+ return prompt, target_codes, decode_logits_fn
279
+
280
+ def prepare_for_generation(
281
+ self,
282
+ prompt: Optional[Tensor],
283
+ context: Optional[Tensor],
284
+ gen_sequence: Tensor,
285
+ use_cfg: bool,
286
+ context_dropout_mask: Optional[Tensor],
287
+ ) -> Tuple[Tensor, Tensor, int, Callable[[Tensor], Tuple[Tensor, Tensor]]]:
288
+ raise NotImplementedError("Missing context dropout mask implmentation")
289
+ # dummy prompt and context
290
+ prompt_codes = torch.empty(gen_sequence.shape[0],
291
+ gen_sequence.shape[1],
292
+ 0,
293
+ dtype=torch.long,
294
+ device=gen_sequence.device)
295
+ context_codes = torch.empty(gen_sequence.shape[0],
296
+ gen_sequence.shape[1],
297
+ 0,
298
+ dtype=torch.long,
299
+ device=gen_sequence.device)
300
+
301
+ # encode prompt and context
302
+ if prompt is not None and context is not None:
303
+ prompt_codes, context_codes = self.encode_target_and_context(
304
+ prompt, context)
305
+ elif prompt is not None:
306
+ prompt_codes = self.encode(prompt)
307
+ elif context is not None:
308
+ context_codes = self.encode(context)
309
+
310
+ # duplicate prompt, gen_sequence and context if using cfg
311
+ if use_cfg:
312
+ gen_sequence = torch.cat((gen_sequence, gen_sequence), dim=0)
313
+ prompt_codes = torch.cat((prompt_codes, prompt_codes), dim=0)
314
+ context_codes = torch.cat(
315
+ (context_codes,
316
+ torch.full_like(context_codes, self.special_token)),
317
+ dim=0)
318
+
319
+ # insert encoded prompt into sequence
320
+ gen_sequence[..., :prompt_codes.shape[-1]] = prompt_codes
321
+
322
+ # interleave sequence
323
+ gen_sequence, gen_mask, pattern = self.interleave(
324
+ gen_sequence, keep_only_valid_steps=False)
325
+ start_offset: int = pattern.get_first_step_with_timesteps(
326
+ prompt_codes.shape[-1]) + context_codes.shape[-1] # type: ignore
327
+
328
+ # prepend non-interleaved context codes to the sequence
329
+ gen_sequence = torch.cat((context_codes, gen_sequence), dim=-1)
330
+ gen_mask = torch.cat((torch.zeros(context_codes.shape[1:],
331
+ dtype=torch.bool,
332
+ device=gen_mask.device), gen_mask),
333
+ dim=-1)
334
+
335
+ def decode_sequence_closure(sequence):
336
+ # remove context
337
+ sequence = sequence[..., context_codes.shape[-1]:]
338
+ # de-interleave
339
+ sequence = sequence.contiguous()
340
+ out_codes, _, out_mask = pattern.revert_pattern_sequence(
341
+ sequence, special_token=-1, keep_only_valid_steps=False)
342
+ return out_codes, out_mask
343
+
344
+ raise NotImplementedError("mask implementation missing")
345
+
346
+ return gen_sequence, gen_mask, start_offset, decode_sequence_closure
347
+
348
+
349
+ class InterleavedContextPromptProcessor(ContextPromptProcessor):
350
+ uses_sep_token: bool = True
351
+ """
352
+ Concatenates context and target, separating them with a single timestep
353
+ of a "stop tensor", made of n_q special tokens.
354
+
355
+ The sequence (context, stop_token, target) is then interleaved.
356
+ """
357
+
358
+ def preprocess(self, batch: Dict[str, Any]):
359
+ target = batch["target"]
360
+ context = batch["context"]
361
+
362
+ # encode target and context
363
+ target_codes, context_codes = self.encode_target_and_context(
364
+ target, context)
365
+
366
+ # apply dropout to context
367
+ if self.training and self.context_dropout > 0:
368
+ drop = torch.rand(context_codes.shape[0]) < self.context_dropout
369
+ context_codes[drop] = torch.full_like(context_codes[drop],
370
+ self.special_token)
371
+
372
+ # concatenate context and target with stop token
373
+ stop_tensor = torch.full(
374
+ (target_codes.shape[0], target_codes.shape[1], 1),
375
+ self.special_token + 1,
376
+ dtype=target_codes.dtype,
377
+ device=target_codes.device)
378
+ prompt = torch.cat((context_codes, stop_tensor, target_codes), dim=-1)
379
+
380
+ # interleave prompt
381
+ prompt, prompt_mask, pattern = self.interleave(
382
+ prompt, self.keep_only_valid_steps)
383
+
384
+ # all tokens behind the stop tokens are conditioning
385
+ cond_tokens_mask = torch.zeros_like(prompt, dtype=torch.bool)
386
+ n_q = prompt.shape[1]
387
+ context_len = context_codes.shape[-1]
388
+ for i in range(n_q):
389
+ cond_tokens_mask[:, i, range(context_len + i + 1)] = True
390
+ special_tokens_mask = prompt == self.special_token
391
+ invalid_cond_mask = special_tokens_mask * cond_tokens_mask
392
+ final_mask = (~invalid_cond_mask) * prompt_mask.repeat(
393
+ invalid_cond_mask.shape[0], 1, 1)
394
+
395
+ def decode_logits_closure(logits) -> Tuple[Tensor, Tensor]:
396
+ # remove interleaving pattern
397
+ deinterleaved_logits, logits_mask = self.deinterleave_logits(
398
+ logits, pattern)
399
+
400
+ # remove context
401
+ n_target_codes = target_codes.shape[-1]
402
+ deinterleaved_logits = deinterleaved_logits[:, :,
403
+ -n_target_codes:, :]
404
+ logits_mask = logits_mask[:, :, -n_target_codes:]
405
+
406
+ return deinterleaved_logits, logits_mask
407
+
408
+ return prompt, final_mask, target_codes, decode_logits_closure
409
+
410
+ def prepare_for_generation(
411
+ self, prompt: Optional[Tensor], context: Optional[Tensor],
412
+ gen_sequence: Tensor, use_cfg: bool,
413
+ context_dropout_mask: Optional[Tensor]
414
+ ) -> Tuple[Tensor, Tensor, int, Callable[[Tensor], Tuple[Tensor, Tensor]]]:
415
+ """
416
+ Take prompt and context, prepare them, apply cfg, and insert them
417
+ in dummy sequence full of -1 for generation. Return:
418
+ - gen_sequence
419
+ - gen_mask
420
+ - start_offset
421
+ - closure to decode sequence once filled with gen tokens
422
+ """
423
+ # encode prompt and context
424
+ if prompt is not None and context is not None:
425
+ prompt_codes, context_codes = self.encode_target_and_context(
426
+ prompt, context)
427
+ # context_mask = torch.ones_like(context_codes, dtype=torch.bool)
428
+ else:
429
+ if prompt is None:
430
+ prompt_codes = torch.empty(gen_sequence.shape[0],
431
+ gen_sequence.shape[1],
432
+ 0,
433
+ dtype=torch.long,
434
+ device=gen_sequence.device)
435
+ else:
436
+ prompt_codes = self.encode(prompt)
437
+ if context is None:
438
+ context_codes = torch.full(
439
+ (gen_sequence.shape[0], gen_sequence.shape[1], 0),
440
+ self.special_token,
441
+ dtype=torch.long,
442
+ device=gen_sequence.device)
443
+ # context_mask = torch.zeros_like(context_codes, dtype=torch.bool)
444
+ else:
445
+ if isinstance(context, list):
446
+ context_codes = self.encode_and_pad_list(
447
+ context, self.special_token)
448
+ else:
449
+ assert isinstance(context, Tensor)
450
+ context_codes = self.encode(context)
451
+ # context_mask = torch.ones_like(context_codes, dtype=torch.bool)
452
+
453
+ if context_dropout_mask is not None:
454
+ if context_dropout_mask.shape != torch.Size(
455
+ (context_codes.shape[0],)):
456
+ raise ValueError(f"Context dropout mask should be of shape "
457
+ f"[{context_codes.shape[0]} but it is "
458
+ f"{context_dropout_mask.shape}]")
459
+ for i in range(len(context_dropout_mask)):
460
+ if not context_dropout_mask[i]:
461
+ context_codes[i] = torch.full_like(
462
+ context_codes[i], self.special_token)
463
+ # context_codes = torch.where(
464
+ # context_dropout_mask, context_codes,
465
+ # torch.full_like(context_codes, self.special_token))
466
+
467
+ # duplicate prompt, gen_sequence and context if using cfg
468
+ if use_cfg:
469
+ gen_sequence = torch.cat((gen_sequence, gen_sequence), dim=0)
470
+ prompt_codes = torch.cat((prompt_codes, prompt_codes), dim=0)
471
+ context_codes = torch.cat(
472
+ (context_codes,
473
+ torch.full_like(context_codes, self.special_token)),
474
+ dim=0)
475
+ # context_mask = torch.cat(
476
+ # (context_mask, torch.zeros_like(context_mask,
477
+ # dtype=torch.bool)),
478
+ # dim=0)
479
+
480
+ # insert encoded prompt into sequence
481
+ gen_sequence[..., :prompt_codes.shape[-1]] = prompt_codes
482
+
483
+ # prepend context and stop token to gen_sequence
484
+ stop_tensor = torch.ones(
485
+ (gen_sequence.shape[0], gen_sequence.shape[1], 1),
486
+ dtype=gen_sequence.dtype,
487
+ device=gen_sequence.device) * self.special_token + 1
488
+ # stop_mask = torch.ones_like(stop_tensor, dtype=torch.bool)
489
+ # conditioning_mask = torch.cat(
490
+ # (context_mask, stop_mask,
491
+ # torch.ones_like(gen_sequence, dtype=torch.bool)),
492
+ # dim=-1)
493
+ gen_sequence = torch.cat((context_codes, stop_tensor, gen_sequence),
494
+ dim=-1)
495
+
496
+ # interleave sequence (keep_only_valid_steps always false for inference)
497
+ gen_sequence, gen_mask, pattern = self.interleave(
498
+ gen_sequence, keep_only_valid_steps=False)
499
+
500
+ # interleaved_conditioning_mask, mask_mask, _ = self.interleave(
501
+ # conditioning_mask, keep_only_valid_steps=False)
502
+
503
+ # assert gen_sequence.shape == interleaved_conditioning_mask.shape
504
+
505
+ # interleaved_conditioning_mask[~gen_mask] = 0
506
+ # interleaved_conditioning_mask[~mask_mask] = 0
507
+
508
+ # compute first offset of the sequence to generate
509
+ start_offset: int = pattern.get_first_step_with_timesteps( # type: ignore
510
+ prompt_codes.shape[-1] + context_codes.shape[-1] + 1)
511
+
512
+ # set gen_mask of stop token to false
513
+ n_q = gen_sequence.shape[1]
514
+ # gen_mask[range(n_q),
515
+ # range(start_offset - 1, start_offset - 1 + n_q)] = False
516
+
517
+ # all tokens behind the stop token are conditioning
518
+ cond_tokens_mask = torch.zeros_like(gen_sequence, dtype=torch.bool)
519
+ for i in range(n_q):
520
+ cond_tokens_mask[:, i, range(start_offset + i - 1)] = True
521
+ special_tokens_mask = gen_sequence == self.special_token
522
+ invalid_cond_mask = special_tokens_mask * cond_tokens_mask
523
+
524
+ final_mask = (~invalid_cond_mask) * gen_mask.repeat(
525
+ invalid_cond_mask.shape[0], 1, 1)
526
+
527
+ def decode_sequence_closure(sequence):
528
+ # deinterleave
529
+ sequence = sequence.contiguous()
530
+ out_codes, _, out_mask = pattern.revert_pattern_sequence(
531
+ sequence, special_token=-1, keep_only_valid_steps=False)
532
+ # remove context and stop token
533
+ out_codes = out_codes[..., (context_codes.shape[-1] + 1):]
534
+ return out_codes, out_mask
535
+
536
+ backwards_compatible_mode = False
537
+ if backwards_compatible_mode:
538
+ return gen_sequence, gen_mask, start_offset, decode_sequence_closure
539
+
540
+ return gen_sequence, final_mask, start_offset, decode_sequence_closure
541
+
542
+
543
+ def parallel_vs_sequential_test():
544
+
545
+ from time import time
546
+ import hyperparameters as hp
547
+ from models.encodec import EncodecModel
548
+ device = torch.device("cuda")
549
+
550
+ encodec_params = hp.pretrained_encodec_meta_32khz_params
551
+ encodec = EncodecModel.from_params(encodec_params).eval().to(device)
552
+
553
+ processor = StraightContextPromptProcessor(encodec_model=encodec,
554
+ special_token=2048,
555
+ keep_only_valid_steps=False,
556
+ context_dropout=0.9).eval()
557
+
558
+ # quick benchmark for parallel vs sequential encoding performance
559
+ batch_parallel = {
560
+ "target": torch.rand(4, 1, 320_000).to(device),
561
+ "context": torch.rand(4, 1, 320_000).to(device),
562
+ }
563
+ t0 = time()
564
+ out1 = processor.preprocess(batch_parallel)
565
+ t1 = time()
566
+ print(f"parallel encoding: {round((t1 - t0) * 1000, 2)} ms")
567
+
568
+ batch_sequential = {
569
+ "target": torch.rand(4, 1, 320_000).to(device),
570
+ "context": torch.rand(4, 1, 310_000).to(device),
571
+ }
572
+ t0 = time()
573
+ out2 = processor.preprocess(batch_sequential)
574
+ t1 = time()
575
+ print(f"sequential encoding: {round((t1 - t0) * 1000, 2)} ms")
576
+
577
+ batch_parallel = {
578
+ "target": torch.rand(4, 1, 320_000).to(device),
579
+ "context": torch.rand(4, 1, 320_000).to(device),
580
+ }
581
+ t0 = time()
582
+ out3 = processor.preprocess(batch_parallel)
583
+ t1 = time()
584
+ print(f"parallel encoding: {round((t1 - t0) * 1000, 2)} ms")
585
+
586
+ batch_sequential = {
587
+ "target": torch.rand(4, 1, 320_000).to(device),
588
+ "context": torch.rand(4, 1, 310_000).to(device),
589
+ }
590
+ t0 = time()
591
+ out4 = processor.preprocess(batch_sequential)
592
+ t1 = time()
593
+ print(f"sequential encoding: {round((t1 - t0) * 1000, 2)} ms")
594
+
595
+ batch_parallel = {
596
+ "target": torch.rand(4, 1, 320_000).to(device),
597
+ "context": torch.rand(4, 1, 320_000).to(device),
598
+ }
599
+ t0 = time()
600
+ out5 = processor.preprocess(batch_parallel)
601
+ t1 = time()
602
+ print(f"parallel encoding: {round((t1 - t0) * 1000, 2)} ms")
603
+
604
+ batch_sequential = {
605
+ "target": torch.rand(4, 1, 320_000).to(device),
606
+ "context": torch.rand(4, 1, 310_000).to(device),
607
+ }
608
+ t0 = time()
609
+ out6 = processor.preprocess(batch_sequential)
610
+ t1 = time()
611
+ print(f"sequential encoding: {round((t1 - t0) * 1000, 2)} ms")
612
+
613
+
614
+ if __name__ == "__main__":
615
+ import hyperparameters as hp
616
+ from models.encodec import EncodecModel
617
+ device = torch.device("cuda")
618
+
619
+ encodec_params = hp.pretrained_encodec_meta_32khz_params
620
+ encodec = EncodecModel.from_params(encodec_params).eval().to(device)
621
+
622
+ processor = InterleavedContextPromptProcessor(encodec_model=encodec,
623
+ special_token=2048,
624
+ keep_only_valid_steps=False,
625
+ context_dropout=0.9).eval()
conditioning/t5embedder.py ADDED
@@ -0,0 +1,211 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from transformers import T5Tokenizer, T5EncoderModel
3
+ from typing import List, Optional, Dict, Sequence, Tuple
4
+ import random
5
+ import torch
6
+ from torch import Tensor
7
+
8
+ from conditioning.embedder import Embedder, LinearProjectionEmbedder
9
+ from conditioning.embedded_condition import EmbeddedCondition
10
+
11
+
12
+ class T5Embedder(LinearProjectionEmbedder):
13
+ """T5-based TextConditioner.
14
+
15
+ Args:
16
+ name (str): Name of the T5 model.
17
+ output_dim (int): Output dim of the conditioner.
18
+ finetune (bool): Whether to fine-tune T5 at train time.
19
+ word_dropout (float, optional): Word dropout probability.
20
+ normalize_text (bool, optional): Whether to apply text normalization.
21
+ """
22
+ MODELS = [
23
+ "t5-small", "t5-base", "t5-large", "t5-3b", "t5-11b",
24
+ "google/flan-t5-small", "google/flan-t5-base", "google/flan-t5-large",
25
+ "google/flan-t5-xl", "google/flan-t5-xxl"
26
+ ]
27
+ MODELS_DIMS = {
28
+ "t5-small": 512,
29
+ "t5-base": 768,
30
+ "t5-large": 1024,
31
+ "t5-3b": 1024,
32
+ "t5-11b": 1024,
33
+ "google/flan-t5-small": 512,
34
+ "google/flan-t5-base": 768,
35
+ "google/flan-t5-large": 1024,
36
+ "google/flan-t5-3b": 1024,
37
+ "google/flan-t5-11b": 1024,
38
+ }
39
+
40
+ def __init__(self,
41
+ embedding_dim: int,
42
+ t5_on_cpu: bool,
43
+ name: str = "t5-base",
44
+ finetune: bool = False,
45
+ word_dropout: float = 0.3):
46
+ assert name in self.MODELS, f"Unrecognized t5 model name (should in {self.MODELS})"
47
+ super().__init__(self.MODELS_DIMS[name], embedding_dim)
48
+ self.name = name
49
+ self.finetune = finetune
50
+ self.word_dropout = word_dropout
51
+ self.t5_on_cpu: bool = t5_on_cpu
52
+ if self.t5_on_cpu and finetune:
53
+ raise ValueError("Can't finetune t5 if it's locked on cpu")
54
+
55
+ # Let's disable logging temporarily because T5 will vomit some errors otherwise.
56
+ # thanks https://gist.github.com/simon-weber/7853144
57
+ previous_level = logging.root.manager.disable
58
+ logging.disable(logging.ERROR)
59
+ self.t5_tokenizer = T5Tokenizer.from_pretrained(
60
+ name,
61
+ clean_up_tokenization_spaces=False,
62
+ )
63
+
64
+ t5 = T5EncoderModel.from_pretrained(name).train(mode=finetune)
65
+
66
+ if self.t5_on_cpu:
67
+ t5 = t5.cpu()
68
+ self.__dict__["t5"] = t5
69
+ else:
70
+ self.t5 = t5
71
+
72
+ if not self.finetune:
73
+ for p in self.t5.parameters():
74
+ p.requires_grad = False
75
+
76
+ # if not self.finetune:
77
+ # if self.t5_on_cpu:
78
+ # t5 = t5.cpu()
79
+ # self.__dict__["t5"] = t5.eval()
80
+ # for p in self.t5.parameters():
81
+ # p.requires_grad = False
82
+ # else:
83
+ # self.t5 = t5
84
+
85
+ # with warnings.catch_warnings():
86
+
87
+ # warnings.simplefilter("ignore")
88
+ # try:
89
+ # self.t5_tokenizer = T5Tokenizer.from_pretrained(name)
90
+ # t5 = T5EncoderModel.from_pretrained(name).train(mode=finetune)
91
+ # finally:
92
+ # logging.disable(previous_level)
93
+ # if finetune:
94
+ # self.t5 = t5
95
+ # else:
96
+ # # this makes sure that the t5 models is not part
97
+ # # of the saved checkpoint
98
+ # self.__dict__['t5'] = t5
99
+
100
+ # self.normalize_text = normalize_text
101
+ # if normalize_text:
102
+ # self.text_normalizer = WhiteSpaceTokenizer(1,
103
+ # lemma=True,
104
+ # stopwords=True)
105
+
106
+ # def to(self, *args, **kwargs):
107
+ # return super().to("cpu")
108
+
109
+ # def cuda(self, *args, **kwargs):
110
+ # return self.to("cpu")
111
+
112
+ def tokenize(self, x: Sequence[Optional[str]]) -> Dict[str, torch.Tensor]:
113
+ # if current sample doesn't have a certain attribute, replace with empty string
114
+ entries: List[str] = [xi if xi is not None else "" for xi in x]
115
+ # if self.normalize_text:
116
+ # _, _, entries = self.text_normalizer( # type: ignore
117
+ # entries, return_text=True)
118
+ if self.word_dropout > 0. and self.training:
119
+ new_entries = []
120
+ for entry in entries:
121
+ words = [
122
+ word for word in entry.split(" ")
123
+ if random.random() >= self.word_dropout
124
+ ]
125
+ new_entries.append(" ".join(words))
126
+ entries = new_entries
127
+
128
+ empty_idx = torch.LongTensor(
129
+ [i for i, xi in enumerate(entries) if xi == ""])
130
+
131
+ inputs = self.t5_tokenizer(entries, return_tensors='pt',
132
+ padding=True).to(
133
+ next(iter(self.parameters())).device)
134
+ mask = inputs['attention_mask']
135
+ mask[empty_idx, :] = 0 # zero-out index where the input is non-existant
136
+ return inputs
137
+
138
+ def forward(self,
139
+ descriptions: List[str],
140
+ duplicate_for_cfg: bool = False) -> EmbeddedCondition:
141
+
142
+ if duplicate_for_cfg:
143
+ descriptions = descriptions + ([""] * len(descriptions))
144
+
145
+ tokenized = self.tokenize(descriptions)
146
+ embedding, mask = self.embed(tokenized)
147
+ mask = mask.bool()
148
+ return EmbeddedCondition(embedding, mask)
149
+
150
+ def embed(self, inputs: Dict[str, torch.Tensor]) -> Tuple[Tensor, Tensor]:
151
+ if not self.finetune:
152
+ self.t5.eval()
153
+ mask = inputs['attention_mask']
154
+ with torch.set_grad_enabled(self.finetune):
155
+ if self.t5_on_cpu:
156
+ inputs = {k: v.to("cpu") for k, v in inputs.items()}
157
+ with torch.autocast(device_type="cuda", enabled=False):
158
+ embeds = self.t5(**inputs).last_hidden_state
159
+ embeds = self.output_proj(embeds.to(self.output_proj.weight))
160
+ embeds = (embeds * mask.unsqueeze(-1).to(embeds))
161
+ return embeds, mask
162
+
163
+ def null_condition(self, batch_size: int):
164
+ return EmbeddedCondition(
165
+ torch.zeros(batch_size,
166
+ 1,
167
+ self.embedding_dim,
168
+ dtype=torch.float32,
169
+ device=self.output_proj.weight.device),
170
+ torch.ones(batch_size,
171
+ 1,
172
+ dtype=torch.bool,
173
+ device=self.output_proj.weight.device))
174
+
175
+
176
+ class T5EmbedderCPU(T5Embedder):
177
+
178
+ def __init__(self, embedding_dim: int):
179
+ super().__init__(embedding_dim,
180
+ t5_on_cpu=True,
181
+ name="t5-base",
182
+ finetune=False,
183
+ word_dropout=0.3)
184
+
185
+
186
+ class T5EmbedderGPU(T5Embedder):
187
+
188
+ def __init__(self, embedding_dim: int):
189
+ super().__init__(embedding_dim,
190
+ t5_on_cpu=False,
191
+ name="t5-base",
192
+ finetune=False,
193
+ word_dropout=0.3)
194
+
195
+
196
+ if __name__ == "__main__":
197
+ from utils.inspection import print_params
198
+ from time import time
199
+
200
+ t5 = T5EmbedderCPU(1024).eval().cpu()
201
+ # print_params(t5, 1, False)
202
+
203
+ descriptions = ["the quick"]
204
+
205
+ embedded_desc = t5(descriptions)
206
+ # print(embedded_desc)
207
+ # print(embedded_desc.data)
208
+ # print(embedded_desc.mask)
209
+
210
+ # print(embedded_desc.data[..., -1, ...])
211
+ # print(embedded_desc.data[0, -1, ...])
config.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import os
3
+ from typing import Optional
4
+ import os
5
+
6
+ ENTITY: str = ""
7
+ PROJECT: str = ""
8
+
9
+ ROOT = Path(__file__).parent
10
+ LOCAL_WEIGHTS_DIR: Path = ROOT / "weights"
11
+ LOCAL_CKP_DIR: Path = ROOT / "checkpoints"
12
+ AUDIO_DIR: Path = ROOT / "audio"
13
+ EVAL_DIR: Path = ROOT / "eval"
14
+ EXP_DIR: Path = ROOT / "experiments"
15
+ CKP_DIR = LOCAL_CKP_DIR
16
+
17
+
18
+ class ConfigurationError(ValueError):
19
+ ...
20
+
21
+
22
+ def first_existing(*paths: Path | str) -> Optional[Path]:
23
+ for path in paths:
24
+ if Path(path).exists():
25
+ return Path(path)
26
+
27
+
28
+ def moises_path() -> Path:
29
+ path = first_existing(Path.home() / "dev/dataset/moisesdb/moisesdb_v0.1",
30
+ Path.home() / "dev/datasets/moisesdb/moisesdb_v0.1",
31
+ Path.home() / "lag-data/lag-moisesdb",
32
+ ROOT / "datasets/moisesdb")
33
+ if path is None:
34
+ raise RuntimeError("I can't find moisesdb")
35
+ return path
36
+
37
+
38
+ def mus_path() -> Path:
39
+ path = first_existing(
40
+ Path.home() / "dev/dataset/moisesdb/musdb",
41
+ Path.home() / "datasets/moisesdb/musdb",
42
+ Path.home() / "lag-data/musdb",
43
+ ROOT / "datasets/musdb",
44
+ )
45
+ if path is None:
46
+ raise RuntimeError("I can't find musdb")
47
+ return path
48
+
49
+
50
+ def mixdata_path() -> Path:
51
+ path = first_existing(
52
+ Path.home() / "dev/datasets/moisesdb",
53
+ Path.home() / "datasets/moisesdb",
54
+ Path.home() / "lag-data",
55
+ Path.home() / "datasets/lag-data",
56
+ )
57
+ if path is None:
58
+ raise RuntimeError("I can't find the mixed dataset path")
59
+ return path
60
+
61
+
62
+ def weights_dir() -> Path:
63
+ return LOCAL_WEIGHTS_DIR
64
+
65
+
66
+ def output_dir() -> Path:
67
+ return CKP_DIR
68
+
69
+
70
+ def shutdown():
71
+ print(f"Shutting down myself 💀")
72
+ os.system("sudo shutdown now")
73
+
74
+
75
+ import torch
76
+
77
+ torch.set_float32_matmul_precision("medium")
data/auto_labelling.py ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #%% Imports
2
+ from pathlib import Path
3
+ import subprocess as sp
4
+ import essentia.standard as es
5
+ import config as cfg
6
+ import numpy as np
7
+ import librosa
8
+ from multipledispatch import dispatch
9
+ from torch import Tensor
10
+ import torchaudio
11
+
12
+ from data.labels import GENRE_LABELS, MOOD_THEME_CLASSES, INSTRUMENT_CLASSES
13
+ from utils import audio as audio_utils
14
+ import essentia
15
+
16
+ #%% Download models
17
+ if False:
18
+ sp.call([
19
+ "curl",
20
+ "https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.pb",
21
+ "--output", "genre_discogs400-discogs-effnet-1.pb"
22
+ ])
23
+ sp.call([
24
+ "curl",
25
+ "https://essentia.upf.edu/models/feature-extractors/discogs-effnet/discogs-effnet-bs64-1.pb",
26
+ "--output", "discogs-effnet-bs64-1.pb"
27
+ ])
28
+ sp.call([
29
+ "curl",
30
+ "https://essentia.upf.edu/models/classification-heads/mtg_jamendo_moodtheme/mtg_jamendo_moodtheme-discogs-effnet-1.pb",
31
+ "--output", "mtg_jamendo_moodtheme-discogs-effnet-1.pb"
32
+ ])
33
+ sp.call([
34
+ "curl",
35
+ "https://essentia.upf.edu/models/classification-heads/mtg_jamendo_instrument/mtg_jamendo_instrument-discogs-effnet-1.pb",
36
+ "--output", "mtg_jamendo_instrument-discogs-effnet-1.pb"
37
+ ])
38
+
39
+
40
+ def filter_predictions(predictions, class_list, threshold=0.1):
41
+ predictions_mean = np.mean(predictions, axis=0)
42
+ sorted_indices = np.argsort(predictions_mean)[::-1]
43
+ filtered_indices = [
44
+ i for i in sorted_indices if predictions_mean[i] > threshold
45
+ ]
46
+ filtered_labels = [class_list[i] for i in filtered_indices]
47
+ filtered_values = [predictions_mean[i] for i in filtered_indices]
48
+ return filtered_labels, filtered_values
49
+
50
+
51
+ def make_comma_separated_unique(tags):
52
+ seen_tags = set()
53
+ result = []
54
+ for tag in ', '.join(tags).split(', '):
55
+ if tag not in seen_tags:
56
+ result.append(tag)
57
+ seen_tags.add(tag)
58
+ return ', '.join(result)
59
+
60
+
61
+ @dispatch(Path)
62
+ def get_audio_features(audio_filename: Path): # type: ignore
63
+ audio = audio_utils.load_audio(audio_filename, 16_000, False).squeeze()
64
+ # audio = es.MonoLoader(filename=str(audio_filename),
65
+ # sampleRate=16000,
66
+ # resampleQuality=4)()
67
+
68
+ return get_audio_features(audio, 16_000)
69
+
70
+
71
+ @dispatch(Tensor, int, Path)
72
+ def get_audio_features(audio: Tensor, sr: int,
73
+ models_dir: Path): # type: ignore
74
+
75
+ essentia.log.infoActive = False
76
+ audio = audio_utils.to_mono(audio)
77
+ audio = torchaudio.functional.resample(audio, sr, 16_000).squeeze()
78
+ audio = audio.numpy()
79
+
80
+ embedding_model = es.TensorflowPredictEffnetDiscogs(
81
+ graphFilename=str(models_dir / "discogs-effnet-bs64-1.pb"),
82
+ output="PartitionedCall:1")
83
+ embeddings = embedding_model(audio)
84
+
85
+ result_dict = {}
86
+
87
+ # Predicting genres
88
+ genre_model = es.TensorflowPredict2D(
89
+ graphFilename=str(models_dir / "genre_discogs400-discogs-effnet-1.pb"),
90
+ input="serving_default_model_Placeholder",
91
+ output="PartitionedCall:0")
92
+ predictions = genre_model(embeddings)
93
+
94
+ filtered_labels, _ = filter_predictions(predictions, GENRE_LABELS)
95
+ filtered_labels = ', '.join(filtered_labels).replace("---",
96
+ ", ").split(', ')
97
+ result_dict['genres'] = make_comma_separated_unique(filtered_labels)
98
+
99
+ # Predicting mood/theme
100
+ mood_model = es.TensorflowPredict2D(
101
+ graphFilename=str(models_dir /
102
+ "mtg_jamendo_moodtheme-discogs-effnet-1.pb"))
103
+ predictions = mood_model(embeddings)
104
+ filtered_labels, _ = filter_predictions(predictions,
105
+ MOOD_THEME_CLASSES,
106
+ threshold=0.05)
107
+ result_dict['moods'] = make_comma_separated_unique(filtered_labels)
108
+
109
+ bpm, key = get_bpm_key(audio, sr)
110
+
111
+ result_dict["bpm"] = bpm
112
+ result_dict["key"] = key
113
+
114
+ # Predicting instruments
115
+ # instrument_model = es.TensorflowPredict2D(
116
+ # graphFilename="mtg_jamendo_instrument-discogs-effnet-1.pb")
117
+ # predictions = instrument_model(embeddings)
118
+ # filtered_labels, _ = filter_predictions(predictions, INSTRUMENT_CLASSES)
119
+ # result_dict['instruments'] = filtered_labels
120
+
121
+ return result_dict
122
+
123
+
124
+ @dispatch(Path)
125
+ def get_bpm_key(audio_filename):
126
+ y, sr = librosa.load(str(audio_filename))
127
+ get_bpm_key(y, sr)
128
+
129
+
130
+ @dispatch(np.ndarray, int)
131
+ def get_bpm_key(audio: np.ndarray, sr: int):
132
+ tempo, _ = librosa.beat.beat_track(y=audio, sr=sr)
133
+ tempo = round(tempo[0])
134
+ chroma = librosa.feature.chroma_stft(y=audio, sr=sr)
135
+ key = np.argmax(np.sum(chroma, axis=1))
136
+ key = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'][key]
137
+ length = librosa.get_duration(y=audio, sr=sr)
138
+
139
+ return tempo, key
140
+
141
+
142
+ #%% Test on demo audio
143
+ if __name__ == "__main__":
144
+ audio_filename = cfg.AUDIO_DIR / "cake.wav"
145
+ features = get_audio_features(audio_filename)
146
+
147
+ # %
data/dataset_mixed.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.utils.data
3
+ from pathlib import Path
4
+
5
+ from data.stemmed_dataset import StemmedDataset
6
+ import config as cfg
7
+
8
+
9
+ class MixDataset(torch.utils.data.Dataset):
10
+
11
+ def __init__(self, root_dir: Path, *args, **kwargs):
12
+
13
+ for dirname in [
14
+ "moisesdb_v0.1", "lag-moisesdb", "lag_moisesdb", "moisesdb",
15
+ "moises"
16
+ ]:
17
+ if (root_dir / dirname).exists():
18
+ self.root_dir_moises: Path = root_dir / dirname
19
+ break
20
+ else:
21
+ raise FileNotFoundError(
22
+ f"Couldn't find subdirectory for moisesdb under {root_dir}")
23
+
24
+ self.root_dir_mus: Path = root_dir / "musdb"
25
+
26
+ self.moisesdb_dataset: StemmedDataset = StemmedDataset(
27
+ self.root_dir_moises,
28
+ *args,
29
+ **kwargs,
30
+ )
31
+ self.musdb_dataset: StemmedDataset = StemmedDataset(
32
+ self.root_dir_mus,
33
+ *args,
34
+ **kwargs,
35
+ )
36
+
37
+ # self.n_samples = len(self.moisesdb_dataset) + len(self.musdb_dataset)
38
+ self.n_samples_moises = len(self.moisesdb_dataset)
39
+ self.n_samples_mus = len(self.musdb_dataset)
40
+
41
+ def __len__(self):
42
+ return self.n_samples_moises + self.n_samples_mus
43
+
44
+ def __getitem__(self, x):
45
+ if x < self.n_samples_moises:
46
+ return self.moisesdb_dataset[x]
47
+ else:
48
+ return self.musdb_dataset[x - self.n_samples_moises]
49
+
50
+
51
+ if __name__ == "__main__":
52
+ from data.stem import Stem
53
+
54
+ moises_root = cfg.moises_path()
55
+ mus_root = cfg.mus_path()
56
+
57
+ stems = {
58
+ Stem.DRUMS, Stem.GUITAR, Stem.BASS, Stem.PIANO, Stem.KEYBOARD,
59
+ Stem.STRINGS
60
+ }
61
+
62
+ d = MixDataset(
63
+ cfg.mixdata_path(),
64
+ stems,
65
+ target_stem=Stem.DRUMS,
66
+ single_stem=True,
67
+ add_click=False,
68
+ bpm_in_caption=False,
69
+ sync_chunks=False,
70
+ train=True,
71
+ sample_rate=32_000,
72
+ chunk_size_samples=32_000 * 10,
73
+ speed_transform_p=1,
74
+ pitch_transform_p=1,
75
+ stereo=False,
76
+ n_samples_per_epoch=2000,
77
+ )
78
+
79
+ sample = next(iter(d))
data/labels.py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # @title metadata (labels) for essentia
2
+
3
+ GENRE_LABELS = [
4
+ "Blues---Boogie Woogie",
5
+ "Blues---Chicago Blues",
6
+ "Blues---Country Blues",
7
+ "Blues---Delta Blues",
8
+ "Blues---Electric Blues",
9
+ "Blues---Harmonica Blues",
10
+ "Blues---Jump Blues",
11
+ "Blues---Louisiana Blues",
12
+ "Blues---Modern Electric Blues",
13
+ "Blues---Piano Blues",
14
+ "Blues---Rhythm & Blues",
15
+ "Blues---Texas Blues",
16
+ "Brass & Military---Brass Band",
17
+ "Brass & Military---Marches",
18
+ "Brass & Military---Military",
19
+ "Children's---Educational",
20
+ "Children's---Nursery Rhymes",
21
+ "Children's---Story",
22
+ "Classical---Baroque",
23
+ "Classical---Choral",
24
+ "Classical---Classical",
25
+ "Classical---Contemporary",
26
+ "Classical---Impressionist",
27
+ "Classical---Medieval",
28
+ "Classical---Modern",
29
+ "Classical---Neo-Classical",
30
+ "Classical---Neo-Romantic",
31
+ "Classical---Opera",
32
+ "Classical---Post-Modern",
33
+ "Classical---Renaissance",
34
+ "Classical---Romantic",
35
+ "Electronic---Abstract",
36
+ "Electronic---Acid",
37
+ "Electronic---Acid House",
38
+ "Electronic---Acid Jazz",
39
+ "Electronic---Ambient",
40
+ "Electronic---Bassline",
41
+ "Electronic---Beatdown",
42
+ "Electronic---Berlin-School",
43
+ "Electronic---Big Beat",
44
+ "Electronic---Bleep",
45
+ "Electronic---Breakbeat",
46
+ "Electronic---Breakcore",
47
+ "Electronic---Breaks",
48
+ "Electronic---Broken Beat",
49
+ "Electronic---Chillwave",
50
+ "Electronic---Chiptune",
51
+ "Electronic---Dance-pop",
52
+ "Electronic---Dark Ambient",
53
+ "Electronic---Darkwave",
54
+ "Electronic---Deep House",
55
+ "Electronic---Deep Techno",
56
+ "Electronic---Disco",
57
+ "Electronic---Disco Polo",
58
+ "Electronic---Donk",
59
+ "Electronic---Downtempo",
60
+ "Electronic---Drone",
61
+ "Electronic---Drum n Bass",
62
+ "Electronic---Dub",
63
+ "Electronic---Dub Techno",
64
+ "Electronic---Dubstep",
65
+ "Electronic---Dungeon Synth",
66
+ "Electronic---EBM",
67
+ "Electronic---Electro",
68
+ "Electronic---Electro House",
69
+ "Electronic---Electroclash",
70
+ "Electronic---Euro House",
71
+ "Electronic---Euro-Disco",
72
+ "Electronic---Eurobeat",
73
+ "Electronic---Eurodance",
74
+ "Electronic---Experimental",
75
+ "Electronic---Freestyle",
76
+ "Electronic---Future Jazz",
77
+ "Electronic---Gabber",
78
+ "Electronic---Garage House",
79
+ "Electronic---Ghetto",
80
+ "Electronic---Ghetto House",
81
+ "Electronic---Glitch",
82
+ "Electronic---Goa Trance",
83
+ "Electronic---Grime",
84
+ "Electronic---Halftime",
85
+ "Electronic---Hands Up",
86
+ "Electronic---Happy Hardcore",
87
+ "Electronic---Hard House",
88
+ "Electronic---Hard Techno",
89
+ "Electronic---Hard Trance",
90
+ "Electronic---Hardcore",
91
+ "Electronic---Hardstyle",
92
+ "Electronic---Hi NRG",
93
+ "Electronic---Hip Hop",
94
+ "Electronic---Hip-House",
95
+ "Electronic---House",
96
+ "Electronic---IDM",
97
+ "Electronic---Illbient",
98
+ "Electronic---Industrial",
99
+ "Electronic---Italo House",
100
+ "Electronic---Italo-Disco",
101
+ "Electronic---Italodance",
102
+ "Electronic---Jazzdance",
103
+ "Electronic---Juke",
104
+ "Electronic---Jumpstyle",
105
+ "Electronic---Jungle",
106
+ "Electronic---Latin",
107
+ "Electronic---Leftfield",
108
+ "Electronic---Makina",
109
+ "Electronic---Minimal",
110
+ "Electronic---Minimal Techno",
111
+ "Electronic---Modern Classical",
112
+ "Electronic---Musique Concrète",
113
+ "Electronic---Neofolk",
114
+ "Electronic---New Age",
115
+ "Electronic---New Beat",
116
+ "Electronic---New Wave",
117
+ "Electronic---Noise",
118
+ "Electronic---Nu-Disco",
119
+ "Electronic---Power Electronics",
120
+ "Electronic---Progressive Breaks",
121
+ "Electronic---Progressive House",
122
+ "Electronic---Progressive Trance",
123
+ "Electronic---Psy-Trance",
124
+ "Electronic---Rhythmic Noise",
125
+ "Electronic---Schranz",
126
+ "Electronic---Sound Collage",
127
+ "Electronic---Speed Garage",
128
+ "Electronic---Speedcore",
129
+ "Electronic---Synth-pop",
130
+ "Electronic---Synthwave",
131
+ "Electronic---Tech House",
132
+ "Electronic---Tech Trance",
133
+ "Electronic---Techno",
134
+ "Electronic---Trance",
135
+ "Electronic---Tribal",
136
+ "Electronic---Tribal House",
137
+ "Electronic---Trip Hop",
138
+ "Electronic---Tropical House",
139
+ "Electronic---UK Garage",
140
+ "Electronic---Vaporwave",
141
+ "Folk, World, & Country---African",
142
+ "Folk, World, & Country---Bluegrass",
143
+ "Folk, World, & Country---Cajun",
144
+ "Folk, World, & Country---Canzone Napoletana",
145
+ "Folk, World, & Country---Catalan Music",
146
+ "Folk, World, & Country---Celtic",
147
+ "Folk, World, & Country---Country",
148
+ "Folk, World, & Country---Fado",
149
+ "Folk, World, & Country---Flamenco",
150
+ "Folk, World, & Country---Folk",
151
+ "Folk, World, & Country---Gospel",
152
+ "Folk, World, & Country---Highlife",
153
+ "Folk, World, & Country---Hillbilly",
154
+ "Folk, World, & Country---Hindustani",
155
+ "Folk, World, & Country---Honky Tonk",
156
+ "Folk, World, & Country---Indian Classical",
157
+ "Folk, World, & Country---Laïkó",
158
+ "Folk, World, & Country---Nordic",
159
+ "Folk, World, & Country---Pacific",
160
+ "Folk, World, & Country---Polka",
161
+ "Folk, World, & Country---Raï",
162
+ "Folk, World, & Country---Romani",
163
+ "Folk, World, & Country---Soukous",
164
+ "Folk, World, & Country---Séga",
165
+ "Folk, World, & Country---Volksmusik",
166
+ "Folk, World, & Country---Zouk",
167
+ "Folk, World, & Country---Éntekhno",
168
+ "Funk / Soul---Afrobeat",
169
+ "Funk / Soul---Boogie",
170
+ "Funk / Soul---Contemporary R&B",
171
+ "Funk / Soul---Disco",
172
+ "Funk / Soul---Free Funk",
173
+ "Funk / Soul---Funk",
174
+ "Funk / Soul---Gospel",
175
+ "Funk / Soul---Neo Soul",
176
+ "Funk / Soul---New Jack Swing",
177
+ "Funk / Soul---P.Funk",
178
+ "Funk / Soul---Psychedelic",
179
+ "Funk / Soul---Rhythm & Blues",
180
+ "Funk / Soul---Soul",
181
+ "Funk / Soul---Swingbeat",
182
+ "Funk / Soul---UK Street Soul",
183
+ "Hip Hop---Bass Music",
184
+ "Hip Hop---Boom Bap",
185
+ "Hip Hop---Bounce",
186
+ "Hip Hop---Britcore",
187
+ "Hip Hop---Cloud Rap",
188
+ "Hip Hop---Conscious",
189
+ "Hip Hop---Crunk",
190
+ "Hip Hop---Cut-up/DJ",
191
+ "Hip Hop---DJ Battle Tool",
192
+ "Hip Hop---Electro",
193
+ "Hip Hop---G-Funk",
194
+ "Hip Hop---Gangsta",
195
+ "Hip Hop---Grime",
196
+ "Hip Hop---Hardcore Hip-Hop",
197
+ "Hip Hop---Horrorcore",
198
+ "Hip Hop---Instrumental",
199
+ "Hip Hop---Jazzy Hip-Hop",
200
+ "Hip Hop---Miami Bass",
201
+ "Hip Hop---Pop Rap",
202
+ "Hip Hop---Ragga HipHop",
203
+ "Hip Hop---RnB/Swing",
204
+ "Hip Hop---Screw",
205
+ "Hip Hop---Thug Rap",
206
+ "Hip Hop---Trap",
207
+ "Hip Hop---Trip Hop",
208
+ "Hip Hop---Turntablism",
209
+ "Jazz---Afro-Cuban Jazz",
210
+ "Jazz---Afrobeat",
211
+ "Jazz---Avant-garde Jazz",
212
+ "Jazz---Big Band",
213
+ "Jazz---Bop",
214
+ "Jazz---Bossa Nova",
215
+ "Jazz---Contemporary Jazz",
216
+ "Jazz---Cool Jazz",
217
+ "Jazz---Dixieland",
218
+ "Jazz---Easy Listening",
219
+ "Jazz---Free Improvisation",
220
+ "Jazz---Free Jazz",
221
+ "Jazz---Fusion",
222
+ "Jazz---Gypsy Jazz",
223
+ "Jazz---Hard Bop",
224
+ "Jazz---Jazz-Funk",
225
+ "Jazz---Jazz-Rock",
226
+ "Jazz---Latin Jazz",
227
+ "Jazz---Modal",
228
+ "Jazz---Post Bop",
229
+ "Jazz---Ragtime",
230
+ "Jazz---Smooth Jazz",
231
+ "Jazz---Soul-Jazz",
232
+ "Jazz---Space-Age",
233
+ "Jazz---Swing",
234
+ "Latin---Afro-Cuban",
235
+ "Latin---Baião",
236
+ "Latin---Batucada",
237
+ "Latin---Beguine",
238
+ "Latin---Bolero",
239
+ "Latin---Boogaloo",
240
+ "Latin---Bossanova",
241
+ "Latin---Cha-Cha",
242
+ "Latin---Charanga",
243
+ "Latin---Compas",
244
+ "Latin---Cubano",
245
+ "Latin---Cumbia",
246
+ "Latin---Descarga",
247
+ "Latin---Forró",
248
+ "Latin---Guaguancó",
249
+ "Latin---Guajira",
250
+ "Latin---Guaracha",
251
+ "Latin---MPB",
252
+ "Latin---Mambo",
253
+ "Latin---Mariachi",
254
+ "Latin---Merengue",
255
+ "Latin---Norteño",
256
+ "Latin---Nueva Cancion",
257
+ "Latin---Pachanga",
258
+ "Latin---Porro",
259
+ "Latin---Ranchera",
260
+ "Latin---Reggaeton",
261
+ "Latin---Rumba",
262
+ "Latin---Salsa",
263
+ "Latin---Samba",
264
+ "Latin---Son",
265
+ "Latin---Son Montuno",
266
+ "Latin---Tango",
267
+ "Latin---Tejano",
268
+ "Latin---Vallenato",
269
+ "Non-Music---Audiobook",
270
+ "Non-Music---Comedy",
271
+ "Non-Music---Dialogue",
272
+ "Non-Music---Education",
273
+ "Non-Music---Field Recording",
274
+ "Non-Music---Interview",
275
+ "Non-Music---Monolog",
276
+ "Non-Music---Poetry",
277
+ "Non-Music---Political",
278
+ "Non-Music---Promotional",
279
+ "Non-Music---Radioplay",
280
+ "Non-Music---Religious",
281
+ "Non-Music---Spoken Word",
282
+ "Pop---Ballad",
283
+ "Pop---Bollywood",
284
+ "Pop---Bubblegum",
285
+ "Pop---Chanson",
286
+ "Pop---City Pop",
287
+ "Pop---Europop",
288
+ "Pop---Indie Pop",
289
+ "Pop---J-pop",
290
+ "Pop---K-pop",
291
+ "Pop---Kayōkyoku",
292
+ "Pop---Light Music",
293
+ "Pop---Music Hall",
294
+ "Pop---Novelty",
295
+ "Pop---Parody",
296
+ "Pop---Schlager",
297
+ "Pop---Vocal",
298
+ "Reggae---Calypso",
299
+ "Reggae---Dancehall",
300
+ "Reggae---Dub",
301
+ "Reggae---Lovers Rock",
302
+ "Reggae---Ragga",
303
+ "Reggae---Reggae",
304
+ "Reggae---Reggae-Pop",
305
+ "Reggae---Rocksteady",
306
+ "Reggae---Roots Reggae",
307
+ "Reggae---Ska",
308
+ "Reggae---Soca",
309
+ "Rock---AOR",
310
+ "Rock---Acid Rock",
311
+ "Rock---Acoustic",
312
+ "Rock---Alternative Rock",
313
+ "Rock---Arena Rock",
314
+ "Rock---Art Rock",
315
+ "Rock---Atmospheric Black Metal",
316
+ "Rock---Avantgarde",
317
+ "Rock---Beat",
318
+ "Rock---Black Metal",
319
+ "Rock---Blues Rock",
320
+ "Rock---Brit Pop",
321
+ "Rock---Classic Rock",
322
+ "Rock---Coldwave",
323
+ "Rock---Country Rock",
324
+ "Rock---Crust",
325
+ "Rock---Death Metal",
326
+ "Rock---Deathcore",
327
+ "Rock---Deathrock",
328
+ "Rock---Depressive Black Metal",
329
+ "Rock---Doo Wop",
330
+ "Rock---Doom Metal",
331
+ "Rock---Dream Pop",
332
+ "Rock---Emo",
333
+ "Rock---Ethereal",
334
+ "Rock---Experimental",
335
+ "Rock---Folk Metal",
336
+ "Rock---Folk Rock",
337
+ "Rock---Funeral Doom Metal",
338
+ "Rock---Funk Metal",
339
+ "Rock---Garage Rock",
340
+ "Rock---Glam",
341
+ "Rock---Goregrind",
342
+ "Rock---Goth Rock",
343
+ "Rock---Gothic Metal",
344
+ "Rock---Grindcore",
345
+ "Rock---Grunge",
346
+ "Rock---Hard Rock",
347
+ "Rock---Hardcore",
348
+ "Rock---Heavy Metal",
349
+ "Rock---Indie Rock",
350
+ "Rock---Industrial",
351
+ "Rock---Krautrock",
352
+ "Rock---Lo-Fi",
353
+ "Rock---Lounge",
354
+ "Rock---Math Rock",
355
+ "Rock---Melodic Death Metal",
356
+ "Rock---Melodic Hardcore",
357
+ "Rock---Metalcore",
358
+ "Rock---Mod",
359
+ "Rock---Neofolk",
360
+ "Rock---New Wave",
361
+ "Rock---No Wave",
362
+ "Rock---Noise",
363
+ "Rock---Noisecore",
364
+ "Rock---Nu Metal",
365
+ "Rock---Oi",
366
+ "Rock---Parody",
367
+ "Rock---Pop Punk",
368
+ "Rock---Pop Rock",
369
+ "Rock---Pornogrind",
370
+ "Rock---Post Rock",
371
+ "Rock---Post-Hardcore",
372
+ "Rock---Post-Metal",
373
+ "Rock---Post-Punk",
374
+ "Rock---Power Metal",
375
+ "Rock---Power Pop",
376
+ "Rock---Power Violence",
377
+ "Rock---Prog Rock",
378
+ "Rock---Progressive Metal",
379
+ "Rock---Psychedelic Rock",
380
+ "Rock---Psychobilly",
381
+ "Rock---Pub Rock",
382
+ "Rock---Punk",
383
+ "Rock---Rock & Roll",
384
+ "Rock---Rockabilly",
385
+ "Rock---Shoegaze",
386
+ "Rock---Ska",
387
+ "Rock---Sludge Metal",
388
+ "Rock---Soft Rock",
389
+ "Rock---Southern Rock",
390
+ "Rock---Space Rock",
391
+ "Rock---Speed Metal",
392
+ "Rock---Stoner Rock",
393
+ "Rock---Surf",
394
+ "Rock---Symphonic Rock",
395
+ "Rock---Technical Death Metal",
396
+ "Rock---Thrash",
397
+ "Rock---Twist",
398
+ "Rock---Viking Metal",
399
+ "Rock---Yé-Yé",
400
+ "Stage & Screen---Musical",
401
+ "Stage & Screen---Score",
402
+ "Stage & Screen---Soundtrack",
403
+ "Stage & Screen---Theme",
404
+ ]
405
+ MOOD_THEME_CLASSES = [
406
+ "action", "adventure", "advertising", "background", "ballad", "calm",
407
+ "children", "christmas", "commercial", "cool", "corporate", "dark", "deep",
408
+ "documentary", "drama", "dramatic", "dream", "emotional", "energetic",
409
+ "epic", "fast", "film", "fun", "funny", "game", "groovy", "happy", "heavy",
410
+ "holiday", "hopeful", "inspiring", "love", "meditative", "melancholic",
411
+ "melodic", "motivational", "movie", "nature", "party", "positive",
412
+ "powerful", "relaxing", "retro", "romantic", "sad", "sexy", "slow", "soft",
413
+ "soundscape", "space", "sport", "summer", "trailer", "travel", "upbeat",
414
+ "uplifting"
415
+ ]
416
+ INSTRUMENT_CLASSES = [
417
+ "accordion", "acousticbassguitar", "acousticguitar", "bass", "beat", "bell",
418
+ "bongo", "brass", "cello", "clarinet", "classicalguitar", "computer",
419
+ "doublebass", "drummachine", "drums", "electricguitar", "electricpiano",
420
+ "flute", "guitar", "harmonica", "harp", "horn", "keyboard", "oboe",
421
+ "orchestra", "organ", "pad", "percussion", "piano", "pipeorgan", "rhodes",
422
+ "sampler", "saxophone", "strings", "synthesizer", "trombone", "trumpet",
423
+ "viola", "violin", "voice"
424
+ ]
data/stem.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import IntEnum
2
+
3
+
4
+ class Stem(IntEnum):
5
+ DRUMS = 0
6
+ BASS = 1
7
+ GUITAR = 2
8
+ KEYBOARD = 3
9
+ PIANO = 4
10
+ STRINGS = 5
11
+ OTHER = 6
12
+ VOCALS = 7
13
+ ANY = 8
14
+
15
+ @staticmethod
16
+ def fromstring(s: str):
17
+ match s:
18
+ case "drums":
19
+ return Stem.DRUMS
20
+ case "drums_mixed":
21
+ return Stem.DRUMS
22
+ case "bass":
23
+ return Stem.BASS
24
+ case "guitar":
25
+ return Stem.GUITAR
26
+ case "other_keys":
27
+ return Stem.KEYBOARD
28
+ case "keyboard":
29
+ return Stem.KEYBOARD
30
+ case "piano":
31
+ return Stem.PIANO
32
+ case "bowed_strings":
33
+ return Stem.STRINGS
34
+ case "strings":
35
+ return Stem.STRINGS
36
+ case "other":
37
+ return Stem.OTHER
38
+ case "vocals":
39
+ return Stem.VOCALS
40
+ case _:
41
+ raise ValueError("unknown stem name")
42
+
43
+ def getname(self):
44
+ match self:
45
+ case Stem.DRUMS:
46
+ return "drums_mixed"
47
+ case Stem.BASS:
48
+ return "bass"
49
+ case Stem.GUITAR:
50
+ return "guitar"
51
+ case Stem.KEYBOARD:
52
+ return "other_keys"
53
+ case Stem.PIANO:
54
+ return "piano"
55
+ case Stem.STRINGS:
56
+ return "bowed_strings"
57
+ case Stem.OTHER:
58
+ return "other"
59
+ case Stem.VOCALS:
60
+ return "vocals"
61
+ case Stem.ANY:
62
+ raise ValueError("Stem ANY has no name")
data/stemmed_datamodule.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import lightning as L
2
+ from pathlib import Path
3
+ from typing import Set, Optional
4
+ from config import ConfigurationError
5
+ from data.stem import Stem
6
+ from data.stemmed_dataset import StemmedDataset
7
+ from data.dataset_mixed import MixDataset
8
+ import hyperparameters as hp
9
+ import torch.utils.data
10
+ from torch import Tensor
11
+ import random
12
+
13
+
14
+ class StemmedDatamodule(L.LightningDataModule):
15
+
16
+ def __init__(self, params: hp.StemmedDatasetParams):
17
+ super().__init__()
18
+ # self.stems: Set[Stem] = params.stems
19
+ # self.single_stem: bool = params.single_stem
20
+ # self.root_dir: Path = Path(params.root_dir)
21
+ # self.clip_length_in_seconds: int = params.clip_length_in_seconds
22
+ # self.sample_rate: int = params.sample_rate
23
+ # self.batch_size_train: int = params.batch_size_train
24
+ # self.batch_size_test: int = params.batch_size_test
25
+ # self.num_workers: int = params.num_workers
26
+ # self.speed_transform_p: float = params.speed_transform_p
27
+ # self.pitch_transform_p: float = params.pitch_transform_p
28
+ # self.n_samples_per_epoch: int = params.n_samples_per_epoch
29
+ # self.target_stem: Stem = params.target_stem
30
+ # self.add_click: bool = params.add_click
31
+ # self.sync_chunks: bool = params.sync_chunks
32
+ # self.bpm_in_caption: bool = params.bpm_in_caption
33
+ self.params = params
34
+
35
+ if isinstance(params, hp.MixDatasetParams):
36
+ self.dataset_class = MixDataset
37
+ else:
38
+ self.dataset_class = StemmedDataset
39
+
40
+ self.setup(None)
41
+
42
+ self.lengths = {
43
+ "train": len(self.train_dataloader()),
44
+ "valid": len(self.val_dataloader())
45
+ }
46
+
47
+ def _collate_fn(self, batch):
48
+ if self.params.min_context_seconds > self.params.clip_length_in_seconds:
49
+ raise ConfigurationError(
50
+ "Context has to be smaller than clip length")
51
+ if (self.params.min_context_seconds ==
52
+ self.params.clip_length_in_seconds):
53
+ inputs = {
54
+ k:
55
+ torch.stack([s[k] for s in batch]) if isinstance(
56
+ batch[0][k], Tensor) else [s[k] for s in batch]
57
+ for k in batch[0].keys()
58
+ # if k != "name"
59
+ }
60
+ else:
61
+ inputs = {
62
+ k:
63
+ torch.stack([s[k] for s in batch]) if
64
+ (isinstance(batch[0][k], Tensor) and
65
+ k != "context") else [s[k] for s in batch]
66
+ for k in batch[0].keys()
67
+ # if k != "name"
68
+ }
69
+
70
+ # inputs = {
71
+ # "target": torch.stack([s["target"] for s in batch]),
72
+ # "context": torch.stack([s["context"] for s in batch]),
73
+ # "description": [s["description"] for s in batch],
74
+ # "style": torch.stack([s["style"] for s in batch])
75
+ # }
76
+ return inputs
77
+
78
+ def setup(self, stage: Optional[str]):
79
+ self.train_dataset = self.dataset_class(
80
+ Path(self.params.root_dir),
81
+ self.params.stems,
82
+ train=True,
83
+ target_stem=self.params.target_stem,
84
+ single_stem=self.params.single_stem,
85
+ min_context_seconds=self.params.min_context_seconds,
86
+ use_style_conditioning=self.params.use_style_conditioning,
87
+ use_beat_conditioning=self.params.use_beat_conditioning,
88
+ add_click=self.params.add_click,
89
+ sync_chunks=self.params.sync_chunks,
90
+ bpm_in_caption=self.params.bpm_in_caption,
91
+ sample_rate=self.params.sample_rate,
92
+ type_of_context=self.params.type_of_context,
93
+ chunk_size_samples=self.params.clip_length_in_seconds *
94
+ self.params.sample_rate,
95
+ speed_transform_p=self.params.speed_transform_p,
96
+ pitch_transform_p=self.params.pitch_transform_p,
97
+ n_samples_per_epoch=self.params.n_samples_per_epoch,
98
+ stereo=False,
99
+ max_genres_in_description=3,
100
+ max_moods_in_description=3,
101
+ )
102
+
103
+ self.val_dataset = self.dataset_class(
104
+ Path(self.params.root_dir),
105
+ self.params.stems,
106
+ train=False,
107
+ target_stem=self.params.target_stem,
108
+ single_stem=self.params.single_stem,
109
+ min_context_seconds=self.params.min_context_seconds,
110
+ use_style_conditioning=self.params.use_style_conditioning,
111
+ use_beat_conditioning=self.params.use_beat_conditioning,
112
+ add_click=self.params.add_click,
113
+ sync_chunks=self.params.sync_chunks,
114
+ bpm_in_caption=self.params.bpm_in_caption,
115
+ sample_rate=self.params.sample_rate,
116
+ type_of_context=self.params.type_of_context,
117
+ chunk_size_samples=self.params.clip_length_in_seconds *
118
+ self.params.sample_rate,
119
+ speed_transform_p=self.params.speed_transform_p,
120
+ pitch_transform_p=self.params.pitch_transform_p,
121
+ n_samples_per_epoch=None,
122
+ stereo=False,
123
+ max_genres_in_description=3,
124
+ max_moods_in_description=3,
125
+ )
126
+
127
+ def train_dataloader(self):
128
+ return torch.utils.data.DataLoader(
129
+ dataset=self.train_dataset,
130
+ batch_size=self.params.batch_size_train,
131
+ shuffle=True,
132
+ num_workers=self.params.num_workers,
133
+ collate_fn=self._collate_fn,
134
+ pin_memory=True,
135
+ worker_init_fn=lambda id: random.seed(id),
136
+ # prefetch_factor=1,
137
+ )
138
+
139
+ def val_dataloader(self):
140
+ return torch.utils.data.DataLoader(
141
+ self.val_dataset,
142
+ self.params.batch_size_test,
143
+ shuffle=False,
144
+ num_workers=self.params.num_workers,
145
+ collate_fn=self._collate_fn,
146
+ pin_memory=True,
147
+ worker_init_fn=lambda id: random.seed(id),
148
+ # prefetch_factor=1,
149
+ )
150
+
151
+
152
+ # def main():
153
+ if __name__ == "__main__":
154
+
155
+ from tqdm import tqdm
156
+ import config as cfg
157
+
158
+ # root_dir = cfg.mixdata_path()
159
+ root_dir = cfg.moises_path()
160
+ stems = {
161
+ Stem.DRUMS, Stem.GUITAR, Stem.BASS, Stem.PIANO, Stem.KEYBOARD,
162
+ Stem.STRINGS, Stem.OTHER
163
+ }
164
+ dataset_params = hp.MixDatasetParams(
165
+ root_dir=root_dir,
166
+ stems=stems,
167
+ single_stem=True,
168
+ min_context_seconds=5,
169
+ use_style_conditioning=True,
170
+ use_beat_conditioning=True,
171
+ target_stem=Stem.DRUMS,
172
+ add_click=False,
173
+ sync_chunks=False,
174
+ bpm_in_caption=False,
175
+ batch_size_train=4,
176
+ batch_size_test=4,
177
+ num_workers=8,
178
+ clip_length_in_seconds=10,
179
+ sample_rate=32_000,
180
+ speed_transform_p=1,
181
+ pitch_transform_p=0.5,
182
+ n_samples_per_epoch=2000,
183
+ )
184
+ d = dataset_params.instantiate()
185
+
186
+ vd = d.val_dataloader()
187
+ vbatch = next(iter(vd))
188
+
189
+ td = d.train_dataloader()
190
+ tbatch = next(iter(td))
data/stemmed_dataset.py ADDED
@@ -0,0 +1,801 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple
3
+ import numpy as np
4
+ import torch.utils.data
5
+ from pathlib import Path
6
+ import torch
7
+ import itertools
8
+
9
+ from conditioning.beat_embedder import Beat
10
+ from data.stem import Stem
11
+ from utils import audio as audio_utils
12
+ import json
13
+ from torch import Tensor
14
+ import torchaudio
15
+ # from collections.abc import Sized
16
+ import librosa
17
+
18
+ # import pyrubberband as pyrb
19
+ # import pylibrb
20
+
21
+ N_VALID_SAMPLES = 24
22
+ # 014f37 is removed
23
+ # EXPECTED_N_SONGS = 240
24
+ # EXPECTED_N_SONGS = 150
25
+
26
+
27
+ class StemmedDataset(torch.utils.data.Dataset):
28
+
29
+ def __init__(self,
30
+ root_dir: Path,
31
+ stems: Set[Stem],
32
+ target_stem: Stem,
33
+ single_stem: bool,
34
+ min_context_seconds: int,
35
+ use_style_conditioning: bool,
36
+ use_beat_conditioning: bool,
37
+ type_of_context: str,
38
+ bpm_in_caption: bool,
39
+ add_click: bool,
40
+ sync_chunks: bool,
41
+ train: bool,
42
+ sample_rate: int,
43
+ chunk_size_samples: int,
44
+ speed_transform_p: float,
45
+ pitch_transform_p: float,
46
+ stereo: bool = False,
47
+ max_genres_in_description: int = 3,
48
+ max_moods_in_description: int = 3,
49
+ n_samples_per_epoch: Optional[int] = None,
50
+ verbose: bool = False):
51
+ self.root_dir: Path = root_dir
52
+ self.stems: Set[Stem] = stems
53
+ self.single_stem: bool = single_stem
54
+ self.min_context_seconds: int = min_context_seconds
55
+ self.target_stem: Stem = target_stem
56
+ self.use_style_conditioning: bool = use_style_conditioning
57
+ self.use_beat_conditioning: bool = use_beat_conditioning
58
+ if self.use_style_conditioning and not self.single_stem:
59
+ raise ValueError("You can only use style conditioning if "
60
+ "the target is a single stem")
61
+
62
+ if self.target_stem != Stem.ANY:
63
+ assert self.single_stem
64
+
65
+ self.add_click: bool = add_click
66
+ self.bpm_in_caption: bool = bpm_in_caption
67
+ self.sync_chunks: bool = sync_chunks
68
+ self.stem_names: Set[str] = {s.getname() for s in self.stems}
69
+ self.train: bool = train
70
+ self.sample_rate: int = sample_rate
71
+ self.chunk_size_samples: int = chunk_size_samples
72
+ self.speed_transform_p: float = speed_transform_p
73
+ self.pitch_transform_p: float = pitch_transform_p
74
+ self.stereo: bool = stereo
75
+ self.max_genres_in_description: int = max_genres_in_description
76
+ self.max_moods_in_description: int = max_moods_in_description
77
+ self.verbose: bool = verbose
78
+
79
+ self.type_of_context: str = type_of_context
80
+ assert self.type_of_context in ["stems", "beats", "stems or beats"]
81
+
82
+ if self.add_click or self.sync_chunks:
83
+ if not (self.root_dir / "sync.json").exists():
84
+ raise FileNotFoundError(
85
+ "If you want click or sync, I need a 'sync.json' file in "
86
+ "the top-level dir of the dataset")
87
+
88
+ with open(self.root_dir / "sync.json", "r") as f:
89
+ self.syncdata: Dict[str, List[int]] = json.load(f)
90
+
91
+ # load all song directories
92
+ self.song_names: List[str] = sorted(
93
+ [p.name for p in self.root_dir.iterdir() if p.is_dir()])
94
+ # assert len(self.song_names) == EXPECTED_N_SONGS
95
+
96
+ # if has a single target stem filter out songs that don't have that stem
97
+ if self.target_stem != Stem.ANY:
98
+ toremove: List[str] = []
99
+ for song_name in self.song_names:
100
+ if not (self.root_dir / song_name /
101
+ self.target_stem.getname()).exists():
102
+ toremove.append(song_name)
103
+ self.song_names = [s for s in self.song_names if s not in toremove]
104
+
105
+ # train/valid split
106
+ if self.train:
107
+ self.song_names = self.song_names[:-N_VALID_SAMPLES]
108
+ else:
109
+ self.song_names = self.song_names[-N_VALID_SAMPLES:]
110
+
111
+ # if self.verbose:
112
+ print(f"Loaded {len(self.song_names)} for "
113
+ f"{'train' if self.train else 'valid'} dataset.")
114
+
115
+ # WTF? TODO: remove
116
+ # if self.target_stem != Stem.ANY:
117
+ # # if only interested in a stem, remove songs without it
118
+ # for songname in self.song_names:
119
+ # songdir = self.root_dir / songname
120
+ # if not (songdir / self.target_stem.getname()).exists():
121
+ # self.song_names.remove(songname)
122
+
123
+ # create iterator to run n_sample times
124
+ self.n_samples: int = n_samples_per_epoch or len(self.song_names)
125
+
126
+ self.song_iterator: List[str] = list(
127
+ itertools.islice(itertools.cycle(iter(self.song_names)),
128
+ self.n_samples))
129
+
130
+ def __len__(self):
131
+ return self.n_samples
132
+
133
+ def save_sample(self, sample: Dict, path: Path):
134
+ audio_utils.save_audio(sample["wav"], path / "input.wav",
135
+ self.sample_rate)
136
+ audio_utils.save_audio(sample["conditioning"].wav, path / "cond.wav",
137
+ self.sample_rate)
138
+ audio_utils.save_audio(sample["conditioning"].wav + sample["wav"],
139
+ path / "mix.wav", self.sample_rate)
140
+
141
+ def get_description(self,
142
+ features: Dict[str, str | int],
143
+ instruments: Sequence[Stem],
144
+ speed_factor: Optional[float] = None) -> str:
145
+
146
+ genres: List[str] = str(features["genres"]).split(",")
147
+ moods: List[str] = str(features["moods"]).split(",")
148
+ description = ""
149
+
150
+ # Genre
151
+ if len(genres) > 0:
152
+ # if more than max number of genres, choose first few
153
+ # if len(genres) > self.max_genres:
154
+ # genres = random.sample(genres, self.max_genres)
155
+ genres = [
156
+ s.strip() for s in genres[:self.max_genres_in_description]
157
+ ]
158
+ description += (f"Genre{'s' if len(genres) > 1 else ''}: "
159
+ f"{', '.join(genres)}. ")
160
+
161
+ # Mood
162
+ if len(moods) > 0:
163
+ # if more than max number of moods, choose first few
164
+ # if len(moods) > self.max_moods:
165
+ # moods = random.sample(moods, self.max_moods)
166
+ moods = [s.strip() for s in moods[:self.max_moods_in_description]]
167
+ description += (f"Mood{'s' if len(moods) > 1 else ''}: "
168
+ f"{', '.join(moods)}. ")
169
+
170
+ # Instruments
171
+ if not (self.single_stem and self.target_stem != Stem.ANY):
172
+ instrument_names: List[str] = [s.name.lower() for s in instruments]
173
+ random.shuffle(instrument_names)
174
+ description += f"Instruments: {', '.join(instrument_names)}."
175
+
176
+ # BPM
177
+ if self.bpm_in_caption:
178
+ bpm = features["bpm"]
179
+ if speed_factor:
180
+ bpm = round(int(bpm) / speed_factor)
181
+ description += f" Bpm: {bpm}."
182
+
183
+ # Key
184
+ # key = features["key"]
185
+ # description += f"Key: {key}."
186
+
187
+ return description
188
+
189
+ def _transform_chunk(self, t: Tensor, speed_factor: float,
190
+ pitch_factor: int, target_size: int):
191
+ if self.stereo:
192
+ raise NotImplementedError(
193
+ "No augmentations for stereo audio implemented")
194
+
195
+ stretched = audio_utils.stretch_with_timeout(
196
+ t,
197
+ self.sample_rate,
198
+ speed_factor,
199
+ pitch_factor,
200
+ 2,
201
+ )
202
+
203
+ if stretched.shape[-1] < target_size:
204
+ stretched = torch.nn.functional.pad(
205
+ stretched, (0, target_size - stretched.shape[-1]), "constant",
206
+ 0)
207
+ elif stretched.shape[-1] > target_size:
208
+ stretched = stretched[..., :target_size]
209
+
210
+ return stretched
211
+
212
+ def load_stems(
213
+ self,
214
+ song_path: Path,
215
+ song_stems: Iterable[Stem],
216
+ start_offset: int,
217
+ n_frames: int,
218
+ ) -> Dict[Stem, Tensor]:
219
+
220
+ # load audio chunks for each stem
221
+ stem_tensors: Dict[Stem, Tensor] = {}
222
+ for stem in song_stems:
223
+ stemdir = song_path / stem.getname()
224
+ stem_tensor: Tensor = torch.zeros(2 if self.stereo else 1,
225
+ n_frames,
226
+ dtype=torch.float32)
227
+
228
+ # for each track of a stem
229
+ for trackpath in stemdir.iterdir():
230
+ # load wav
231
+ chunk = audio_utils.load_audio_chunk(trackpath,
232
+ start_offset,
233
+ n_frames,
234
+ stereo=self.stereo)
235
+ stem_tensor += chunk
236
+
237
+ # if not silent, include it in dictionary of stems
238
+ if not audio_utils.is_silent(stem_tensor, threshold=0.01):
239
+ stem_tensors[stem] = stem_tensor
240
+
241
+ return stem_tensors
242
+
243
+ # def choose_conditioning(
244
+ # self,
245
+ # stems: Sequence[Stem]) -> Tuple[Sequence[Stem], Sequence[Stem]]:
246
+ # n_stems: int = len(stems)
247
+ # if n_stems == 1:
248
+ # # if only one stem, use it as input with no conditioning
249
+ # return stems[:], []
250
+ # n_conditioning_stems: int = random.randint(1, n_stems - 1)
251
+ # conditioning_stems: Sequence[Stem] = random.sample(
252
+ # stems, n_conditioning_stems)
253
+ # input_stems = list(filter(lambda x: x not in conditioning_stems, stems))
254
+ # return input_stems, conditioning_stems
255
+
256
+ def choose_input_and_conditioning(
257
+ self,
258
+ stems: Sequence[Stem]) -> Tuple[Sequence[Stem], Sequence[Stem]]:
259
+ n_stems: int = len(stems)
260
+
261
+ if n_stems == 1:
262
+ raise RuntimeError("This song has only one stem")
263
+ return stems[:], []
264
+
265
+ # choose a random number of context stems,
266
+ # leaving at least 1 for the input
267
+ if self.target_stem != Stem.ANY:
268
+ assert self.target_stem in stems
269
+ possible_conditioning_stems = [
270
+ s for s in stems if s != self.target_stem
271
+ ]
272
+ n_conditioning_stems: int = random.randint(
273
+ 1, len(possible_conditioning_stems))
274
+ conditioning_stems: Sequence[Stem] = random.sample(
275
+ possible_conditioning_stems, n_conditioning_stems)
276
+ else:
277
+ n_conditioning_stems: int = random.randint(1, n_stems - 1)
278
+ conditioning_stems: Sequence[Stem] = random.sample(
279
+ stems, n_conditioning_stems)
280
+
281
+ # choose a random number of the remaining stems as input
282
+ n_input_stems: int = 1 if self.single_stem else random.randint(
283
+ 1, n_stems - n_conditioning_stems)
284
+
285
+ # choose input stem
286
+ if self.target_stem != Stem.ANY:
287
+ # if target stem != ANY, that HAS to be the input
288
+ assert self.target_stem in stems
289
+ input_stems: Sequence[Stem] = [self.target_stem]
290
+ else:
291
+ possible_input_stems: Sequence[Stem] = list(
292
+ filter(lambda x: x not in conditioning_stems, stems))
293
+ input_stems: Sequence[Stem] = random.sample(possible_input_stems,
294
+ n_input_stems)
295
+
296
+ return input_stems, conditioning_stems
297
+
298
+ def add_click_to_track(self, wav: Tensor, wav_sr: int,
299
+ click_frames: Sequence[int],
300
+ start_offset: int) -> Tensor:
301
+
302
+ for idx, frame in enumerate(click_frames):
303
+ if frame >= start_offset:
304
+ first_relevant_index = idx
305
+ break
306
+ else:
307
+ return wav
308
+
309
+ shifted_click_frames: List[int] = [
310
+ f - start_offset for f in click_frames[first_relevant_index:]
311
+ ]
312
+
313
+ click_track: Tensor = audio_utils.create_click(list(wav.shape), wav_sr,
314
+ shifted_click_frames)
315
+ assert click_track.shape == wav.shape
316
+
317
+ mix: Tensor = wav + click_track * 0.5
318
+ return mix
319
+
320
+ def mix_input_and_conditioning(self, stem_tensors: Dict[Stem, Tensor],
321
+ input_stems: Sequence[Stem],
322
+ condition_stems: Sequence[Stem]):
323
+
324
+ assert len(input_stems) > 0
325
+ input_tensor = torch.stack([stem_tensors[s] for s in input_stems
326
+ ]).sum(dim=-0)
327
+
328
+ if len(condition_stems) > 0:
329
+ condition_tensor = torch.stack(
330
+ [stem_tensors[s] for s in condition_stems]).sum(dim=-0)
331
+ else:
332
+ condition_tensor = None
333
+
334
+ return input_tensor, condition_tensor
335
+
336
+ def find_good_chunk(
337
+ self, song_name: str, n_frames_to_take: int, song_n_frames: int,
338
+ song_path: Path,
339
+ song_stems: Iterable[Stem]) -> Tuple[Dict[Stem, Tensor], int]:
340
+ found_good_chunk: bool = False
341
+ attempts: int = 0
342
+ while not found_good_chunk:
343
+ attempts += 1
344
+ if attempts > 10 and (attempts - 1) % 10 == 0:
345
+ print(
346
+ f"Tried to find some non-silent chunk of song {song_name} "
347
+ f"for {attempts} times but it's so hard please master "
348
+ "I am tired let me rest")
349
+
350
+ # choose random chunk
351
+ if self.sync_chunks:
352
+ choices: List[int] = self.syncdata[song_name]
353
+ choices = list(
354
+ filter(lambda x: (x + n_frames_to_take) < song_n_frames,
355
+ choices))
356
+ start_offset: int = random.choice(choices)
357
+
358
+ else:
359
+ start_offset: int = random.randint(
360
+ 0, song_n_frames - n_frames_to_take)
361
+
362
+ # load song stems, filter out silent ones
363
+ stem_tensors: Dict[Stem, Tensor] = self.load_stems(
364
+ song_path,
365
+ song_stems,
366
+ start_offset,
367
+ n_frames_to_take,
368
+ )
369
+ nonsilent_stems: List[Stem] = list(stem_tensors.keys())
370
+
371
+ if self.target_stem != Stem.ANY:
372
+ if self.target_stem in nonsilent_stems and (
373
+ len(nonsilent_stems)
374
+ > (1 if len(list(song_stems)) > 1 else 0)):
375
+ found_good_chunk = True
376
+ else:
377
+ if len(nonsilent_stems) > 0:
378
+ found_good_chunk = True
379
+
380
+ return stem_tensors, start_offset # type: ignore
381
+
382
+ def __getitem__(self, idx: int) -> Dict[str, Tensor | str]:
383
+ # output = {
384
+ # "name": "",
385
+ # "target": torch.rand(1, 320_000),
386
+ # "description": "",
387
+ # "context": torch.rand(1, 320_000),
388
+ # "style": torch.rand(1, 320_000),
389
+ # "beat": Beat(beats, downbeats, seq_len)
390
+ # }
391
+ # return output
392
+
393
+ song_name = self.song_iterator[idx]
394
+ song_path: Path = self.root_dir / song_name
395
+
396
+ # toss a coin to decide whether to augment data
397
+ apply_speed_transform: bool = random.random() < self.speed_transform_p
398
+ apply_pitch_transform: bool = random.random() < self.pitch_transform_p
399
+
400
+ # choose random augmentation factors if needed
401
+ speed_factor = round(random.random() * 0.4 +
402
+ 0.80, 2) if apply_speed_transform else 1
403
+ pitch_factor = (random.randint(-4, 4) if apply_pitch_transform else 0)
404
+
405
+ # get song features
406
+ with open(song_path / "features.json", "r") as f:
407
+ features: Dict[str, str | int] = json.load(f)
408
+ song_n_frames: int = int(features["num_frames"])
409
+ song_sr: int = int(features["sample_rate"])
410
+ n_frames_to_take_orig: int = int(self.chunk_size_samples /
411
+ self.sample_rate * song_sr)
412
+ n_frames_to_take = int(n_frames_to_take_orig / speed_factor)
413
+
414
+ # get all song stems, including possibly silent ones
415
+ song_stems: List[Stem] = []
416
+ for stem in self.stems:
417
+ if (song_path / stem.getname()).exists():
418
+ song_stems.append(stem)
419
+
420
+ if self.target_stem != Stem.ANY and self.target_stem not in song_stems:
421
+ raise RuntimeError(f"Target stem is {self.target_stem} but song "
422
+ f"{song_name} has no interesting stems. "
423
+ "Maybe remove it from the dataset?")
424
+
425
+ if len(song_stems) == 0:
426
+ raise RuntimeError(f"Song {song_name} has no interesting stems. "
427
+ "Maybe remove it from the dataset?")
428
+ if len(song_stems) == 1:
429
+ raise RuntimeError(f"Song {song_name} has only one stem, which is"
430
+ f"{song_stems[0]}. What to do?")
431
+
432
+ # find a good chunk of the song
433
+ stem_tensors: Dict[Stem, Tensor]
434
+ start_offset: int
435
+ stem_tensors, start_offset = self.find_good_chunk(
436
+ song_name, n_frames_to_take, song_n_frames, song_path, song_stems)
437
+ nonsilent_stems: List[Stem] = list(stem_tensors.keys())
438
+
439
+ # split stems between input and conditioning
440
+ input_stems, condition_stems = self.choose_input_and_conditioning(
441
+ nonsilent_stems)
442
+
443
+ # mix input and condition tensors
444
+ input_tensor, condition_tensor = self.mix_input_and_conditioning(
445
+ stem_tensors, input_stems, condition_stems)
446
+
447
+ # if applying style conditioning, find a good style conditioning chunk
448
+ style_tensor: Optional[Tensor] = None
449
+ if self.use_style_conditioning:
450
+ assert self.single_stem
451
+ assert len(input_stems) == 1
452
+ inputstem = input_stems[0]
453
+ style_tensor = self.find_good_chunk(song_name,
454
+ n_frames_to_take_orig,
455
+ song_n_frames, song_path,
456
+ [inputstem])[0][inputstem]
457
+
458
+ # if using beat conditioning, compute beats data for current chunk
459
+ beats_conditioning: Optional[Beat] = None
460
+ if self.use_beat_conditioning:
461
+ beatfile = song_path / "beatthis.npz"
462
+ if not beatfile.exists():
463
+ raise FileNotFoundError(
464
+ f"Couldn't find beat annotations for song {song_path}")
465
+ loaded = np.load(beatfile)
466
+ beats_sec = torch.from_numpy(loaded["beats"])
467
+ downbeats_sec = torch.from_numpy(loaded["downbeats"])
468
+
469
+ # if using a speed augmentation, reposition beats
470
+ beats_frames: Tensor = (beats_sec * speed_factor *
471
+ self.sample_rate).round().long()
472
+ downbeats_frames: Tensor = (downbeats_sec * speed_factor *
473
+ self.sample_rate).round().long()
474
+ start_offset = round(start_offset / song_sr * self.sample_rate *
475
+ speed_factor)
476
+ min_max_beat: Tensor = torch.tensor(
477
+ [start_offset, start_offset + self.chunk_size_samples])
478
+
479
+ beats_start_idx, beats_end_idx = torch.searchsorted(beats_frames,
480
+ min_max_beat,
481
+ right=False)
482
+ beats_cut = beats_frames[beats_start_idx:beats_end_idx]
483
+
484
+ downbeats_start_idx, downbeats_end_idx = torch.searchsorted(
485
+ downbeats_frames, min_max_beat, right=False)
486
+ downbeats_cut = downbeats_frames[
487
+ downbeats_start_idx:downbeats_end_idx]
488
+
489
+ assert (downbeats_end_idx + 1 >= len(downbeats_frames) or
490
+ downbeats_frames[downbeats_end_idx + 1]
491
+ >= start_offset + self.chunk_size_samples)
492
+ assert (beats_end_idx + 1 >= len(beats_frames) or
493
+ beats_frames[beats_end_idx + 1]
494
+ >= start_offset + self.chunk_size_samples)
495
+
496
+ beats_cut -= start_offset
497
+ downbeats_cut -= start_offset
498
+
499
+ beats_conditioning = Beat(beats_cut, downbeats_cut,
500
+ self.chunk_size_samples)
501
+
502
+ match self.type_of_context:
503
+ case "beats":
504
+ beats_as_context = True
505
+ case "stems":
506
+ beats_as_context = False
507
+ case "stems or beats":
508
+ beats_as_context = random.random() < 0.5
509
+
510
+ beats_time = beats_cut.numpy() / self.sample_rate
511
+ downbeats_time = downbeats_cut.numpy() / self.sample_rate
512
+ clicks = librosa.clicks(times=beats_time,
513
+ sr=self.sample_rate,
514
+ click_freq=1000,
515
+ length=self.chunk_size_samples)
516
+ downbeat_clicks = librosa.clicks(times=downbeats_time,
517
+ sr=self.sample_rate,
518
+ click_freq=1000,
519
+ length=self.chunk_size_samples)
520
+ # Combine clicks (downbeats are stronger)
521
+ audio_cliks = clicks + downbeat_clicks
522
+ audio_cliks = np.clip(audio_cliks, -1.0, 1.0)
523
+ # mono audio
524
+ if audio_cliks.ndim > 1:
525
+ audio_cliks = audio_cliks.mean(axis=0)
526
+ context_beats = torch.tensor(audio_cliks)
527
+ context_beats = torch.unsqueeze(context_beats, 0)
528
+ if beats_as_context:
529
+ condition_tensor = context_beats
530
+
531
+ # add click
532
+ if self.add_click:
533
+ raise NotImplementedError(
534
+ "Add click is not implemented for new dataset with style")
535
+ click_frames: List[int] = self.syncdata[song_name]
536
+ input_tensor = self.add_click_to_track(input_tensor, song_sr,
537
+ click_frames, start_offset)
538
+ if condition_tensor is not None:
539
+ condition_tensor = self.add_click_to_track(
540
+ condition_tensor, song_sr, click_frames, start_offset)
541
+
542
+ # get description of song input
543
+ description: str = self.get_description(features, input_stems,
544
+ speed_factor)
545
+
546
+ # resample input and conditioning to desired sample rate
547
+ input_tensor = torchaudio.functional.resample(input_tensor, song_sr,
548
+ self.sample_rate)
549
+ if condition_tensor is not None and not beats_as_context:
550
+ condition_tensor = torchaudio.functional.resample(
551
+ condition_tensor, song_sr, self.sample_rate)
552
+
553
+ if style_tensor is not None:
554
+ style_tensor = torchaudio.functional.resample(
555
+ style_tensor, song_sr, self.sample_rate)
556
+
557
+ # data augmentation to input and conditioning
558
+ if apply_speed_transform or apply_pitch_transform:
559
+ input_tensor = self._transform_chunk(input_tensor, speed_factor,
560
+ pitch_factor,
561
+ self.chunk_size_samples)
562
+ if condition_tensor is not None and not beats_as_context:
563
+ condition_tensor = self._transform_chunk(
564
+ condition_tensor, speed_factor, pitch_factor,
565
+ self.chunk_size_samples)
566
+
567
+ if condition_tensor is not None:
568
+ # cut conditioning to a random length
569
+ min_context_samples = self.min_context_seconds * self.sample_rate
570
+ if min_context_samples < self.chunk_size_samples:
571
+ if torch.rand(1).item() > 0.95:
572
+ index = self.chunk_size_samples
573
+ else:
574
+ index = torch.randint(min_context_samples,
575
+ self.chunk_size_samples + 1,
576
+ (1,)).item()
577
+ condition_tensor = condition_tensor[..., :index]
578
+
579
+ output = {
580
+ "name": song_name,
581
+ "target": input_tensor,
582
+ "description": description,
583
+ "context": condition_tensor,
584
+ }
585
+
586
+ if self.use_beat_conditioning:
587
+ output["beat_seconds"] = beats_time
588
+
589
+ if style_tensor is not None:
590
+ output["style"] = style_tensor
591
+
592
+ if beats_conditioning is not None:
593
+ output["beat"] = beats_conditioning
594
+
595
+ return output
596
+
597
+
598
+ # def generate_sync_data(
599
+ # output_path: Path) -> Dict[str, Dict[str, float | List[int]]]:
600
+ # db = MoisesDB(data_path=str(cfg.DATA_DIR / "moisesdb"),
601
+ # sample_rate=32_000)
602
+ # if not output_path.exists():
603
+ # raise FileNotFoundError("output path doesn't seem to exist.")
604
+ # sync_path = output_path / "sync.json"
605
+ # if sync_path.exists():
606
+ # raise FileExistsError()
607
+ # data = {}
608
+ # errors = 0
609
+ # for song in tqdm(db, total=len(db)): # type: ignore
610
+ # try:
611
+ # songid = song.id
612
+ # sr = song.sr
613
+ # audio = librosa.to_mono(song.audio)
614
+ # # utils.save_audio(audio)
615
+ # tempo, beats = librosa.beat.beat_track(y=audio,
616
+ # sr=sr,
617
+ # units="samples")
618
+ # beats = beats.tolist()
619
+ # data[songid] = {
620
+ # "tempo": tempo,
621
+ # "beats": beats,
622
+ # }
623
+ # except:
624
+ # errors += 1
625
+ # with open(sync_path, "w") as fp:
626
+ # json.dump(data, fp)
627
+ # print(f"Saved sync data of {len(db) - errors} songs, with {errors} errors.")
628
+ # return data
629
+
630
+
631
+ def prepare_data(root_dir: Path, save_mixed_drums: bool, save_mix: bool,
632
+ extract_features: bool, track_bpm: bool):
633
+ from tqdm import tqdm
634
+ import torchaudio
635
+ from torch import Tensor
636
+ from lag.data.auto_labelling import get_audio_features
637
+
638
+ subdirs: List[Path] = sorted([p for p in root_dir.iterdir() if p.is_dir()],
639
+ key=lambda x: x.name)
640
+ # assert len(subdirs) == EXPECTED_N_SONGS
641
+
642
+ if track_bpm:
643
+ syncdata: Dict[str, List[int]] = {}
644
+
645
+ for song in tqdm(subdirs):
646
+
647
+ for stemdir in (p for p in song.iterdir() if p.is_dir()):
648
+ if len(list(stemdir.iterdir())) == 0:
649
+ raise FileNotFoundError(f"Song {song} contains no stems. WTF")
650
+
651
+ # mix drums
652
+ if (song / "drums").exists():
653
+ drums_sample_rates: Set[int] = set()
654
+ drums_audios: List[Tensor] = []
655
+ for drum_stem in (song / "drums").iterdir():
656
+ audio, sr = torchaudio.load(str(drum_stem))
657
+ drums_sample_rates.add(sr)
658
+ drums_audios.append(audio.permute(1, 0))
659
+
660
+ if len(drums_sample_rates) != 1:
661
+ raise ValueError(f"song {song} contains drums stems of "
662
+ "different sample rates")
663
+ drums_sr = drums_sample_rates.pop()
664
+
665
+ # mix drums
666
+ drums_tensor = torch.nn.utils.rnn.pad_sequence(
667
+ drums_audios, batch_first=True,
668
+ padding_value=0).permute(0, 2, 1).sum(dim=0)
669
+
670
+ assert drums_tensor.ndim == 2
671
+
672
+ if save_mixed_drums:
673
+ target_dir = song / "drums_mixed"
674
+ target_dir.mkdir(exist_ok=True)
675
+ torchaudio.save(target_dir / "drums.wav", drums_tensor,
676
+ drums_sr)
677
+
678
+ # load mixed song
679
+ stem_subdirs = [p for p in song.iterdir() if p.is_dir()]
680
+
681
+ # for each stem
682
+ stem_tracks: List[Tensor] = []
683
+ sample_rates: Set[int] = set()
684
+ for stem_subdir in stem_subdirs:
685
+ if stem_subdir.name == "drums":
686
+ continue
687
+
688
+ # for each track of that stem
689
+ for audio_path in stem_subdir.iterdir():
690
+ audio, sr = torchaudio.load(str(audio_path))
691
+ assert audio.ndim == 2
692
+ stem_tracks.append(audio.permute(1, 0))
693
+
694
+ sample_rates.add(sr)
695
+ if len(sample_rates) > 1:
696
+ raise ValueError(f"song {song} contains stems of "
697
+ "different sample rates")
698
+ sr = sample_rates.pop()
699
+
700
+ # pad shorter tracks
701
+ stems_tensor: Tensor = torch.nn.utils.rnn.pad_sequence(
702
+ stem_tracks,
703
+ batch_first=True,
704
+ padding_value=0.,
705
+ ).permute(0, 2, 1)
706
+ assert stems_tensor.ndim == 3
707
+
708
+ # mix song
709
+ mixed_tensor = stems_tensor.sum(dim=0)
710
+ num_frames = mixed_tensor.shape[-1]
711
+ assert mixed_tensor.ndim == 2
712
+
713
+ if save_mix:
714
+ mix_out_path = song / "mixed.wav"
715
+ audio_utils.save_audio(mixed_tensor, mix_out_path, sr)
716
+
717
+ # neural classification to get metadata
718
+ if extract_features:
719
+ features = get_audio_features(mixed_tensor, sr, cfg.weights_dir())
720
+
721
+ features["sample_rate"] = sr
722
+ features["num_frames"] = num_frames
723
+ out_file = song / "features.json"
724
+ with open(out_file, "w") as f:
725
+ json.dump(features, f)
726
+
727
+ # bpm tracking
728
+ if track_bpm:
729
+ song_numpy = audio_utils.to_mono(mixed_tensor).squeeze().numpy()
730
+
731
+ # audio = librosa.to_mono(song.audio)
732
+ # utils.save_audio(audio)
733
+ try:
734
+ tempo, beats = librosa.beat.beat_track(y=song_numpy,
735
+ sr=sr,
736
+ units="samples")
737
+ except Exception as e:
738
+ print(f"Error tracking beats of song {song.name}")
739
+ raise e
740
+
741
+ beats = beats.tolist()
742
+
743
+ syncdata[song.name] = beats # type: ignore
744
+
745
+ if track_bpm:
746
+ sync_path: Path = root_dir / "sync.json"
747
+ with open(sync_path, "w") as f:
748
+ json.dump(syncdata, f) # type: ignore
749
+
750
+
751
+ # if __name__ == "__main__":
752
+ # # from lag import config as cfg
753
+ # from tqdm import tqdm
754
+
755
+ # # root_dir = Path("/home/tkol/dev/datasets") / "moisesdb" / "moisesdb_v0.1"
756
+ # # root_dir = Path("/home/tkol/dev/datasets") / "moisesdb" / "musdb"
757
+ # root_dir = cfg.moises_path()
758
+
759
+ # '''prepare_data(root_dir,
760
+ # save_mixed_drums=False,
761
+ # save_mix=False,
762
+ # extract_features=True,
763
+ # track_bpm=False)'''
764
+
765
+ # stems = {
766
+ # Stem.DRUMS, Stem.GUITAR, Stem.BASS, Stem.PIANO, Stem.KEYBOARD,
767
+ # Stem.STRINGS
768
+ # }
769
+
770
+ # dataset = StemmedDataset(
771
+ # root_dir,
772
+ # stems,
773
+ # target_stem=Stem.DRUMS,
774
+ # single_stem=True,
775
+ # min_context_seconds=5,
776
+ # use_style_conditioning=True,
777
+ # use_beat_conditioning=True,
778
+ # add_click=False,
779
+ # bpm_in_caption=False,
780
+ # sync_chunks=False,
781
+ # train=False,
782
+ # sample_rate=32_000,
783
+ # chunk_size_samples=32_000 * 10,
784
+ # speed_transform_p=1,
785
+ # pitch_transform_p=1,
786
+ # stereo=False,
787
+ # n_samples_per_epoch=None,
788
+ # )
789
+
790
+ # dataset_iterator = iter(dataset)
791
+ # for i in tqdm(range(10)):
792
+ # sample = next(dataset_iterator)
793
+
794
+ # target: Tensor = sample["target"] # type: ignore
795
+ # context: Tensor = sample["context"] if sample["context"] is not None else sample["context"]
796
+
797
+ # audio_utils.save_audio(target, cfg.AUDIO_DIR / "temp" / f"target{i}.wav")
798
+ # audio_utils.save_audio(context, cfg.AUDIO_DIR / "temp" / f"context{i}.wav")
799
+ # mix = target + torch.nn.functional.pad(
800
+ # context, (0, target.shape[-1] - context.shape[-1]))
801
+ # audio_utils.save_audio(mix, cfg.AUDIO_DIR / "temp" / f"mix{i}.wav")
hyperparameters.py ADDED
@@ -0,0 +1,244 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass, field
2
+ from typing import Dict, Tuple, Optional, List, Type, Set
3
+ from pydoc import locate
4
+ from frozendict import frozendict
5
+ from copy import deepcopy
6
+ from pathlib import Path
7
+
8
+ import config as cfg
9
+ from conditioning.condition_type import ConditionType
10
+ from conditioning.conditioning_method import ConditioningMethod
11
+ from conditioning import ConcreteEmbedder
12
+ # from conditioning.prompt_processor import InterleavedContextPromptProcessor
13
+ # from conditioning.t5embedder import T5EmbedderGPU
14
+ from data.stem import Stem
15
+ from utils.logging import to_loggable
16
+
17
+ import typing
18
+ if typing.TYPE_CHECKING:
19
+ from conditioning.prompt_processor import PromptProcessor
20
+
21
+
22
+ class Loggable:
23
+
24
+ def to_dict(self) -> Dict:
25
+ return to_loggable(self.__dict__) # type: ignore
26
+
27
+
28
+ # generic parameters for a model that can be instantiated
29
+ # from a hyperparameter class
30
+ @dataclass(unsafe_hash=True, kw_only=True)
31
+ class ModelParams(Loggable):
32
+ model_class: str
33
+
34
+ # def to_dict(self) -> Dict:
35
+ # return to_loggable(self.__dict__) # type: ignore
36
+
37
+ def instantiate(self):
38
+ klass = locate(self.model_class)
39
+ return klass(self) # type: ignore
40
+
41
+
42
+ # --- ENCODEC ---
43
+ @dataclass(unsafe_hash=True)
44
+ class QuantizerParams:
45
+ dimension: int
46
+ n_q: int
47
+ bins: int
48
+
49
+ q_dropout: bool = False
50
+ decay: float = 0.99
51
+ kmeans_init: bool = True
52
+ kmeans_iters: int = 50
53
+ threshold_ema_dead_code: int = 2
54
+ orthogonal_reg_weight: float = 0.0
55
+ orthogonal_reg_active_codes_only: bool = False
56
+ orthogonal_reg_max_codes: int | None = None
57
+
58
+
59
+ @dataclass(unsafe_hash=True)
60
+ class SeaNetParams:
61
+ dimension: int
62
+ n_filters: int
63
+ ratios: Tuple[int, int, int, int]
64
+ causal: bool
65
+ true_skip: bool
66
+
67
+ channels: int = 1
68
+ n_residual_layers: int = 1
69
+ activation: str = "ELU"
70
+ activation_params: frozendict = field(default_factory=frozendict)
71
+ norm: str = "weight_norm"
72
+ norm_params: frozendict = field(default_factory=frozendict)
73
+ kernel_size: int = 7
74
+ last_kernel_size: int = 7
75
+ residual_kernel_size: int = 3
76
+ dilation_base: int = 2
77
+ pad_mode: str = "reflect"
78
+ compress: int = 2
79
+ lstm: int = 2
80
+
81
+
82
+ @dataclass(unsafe_hash=True, kw_only=True)
83
+ class EncodecParams(ModelParams):
84
+ sample_rate: int
85
+ seanet_params: SeaNetParams
86
+ quantizer_params: QuantizerParams
87
+ sum_loss_mulitiplier: int
88
+ weights: Optional[str] = None
89
+
90
+ model_class: str = "stage.models.lightning_encodec.LightningEncodec"
91
+
92
+ def to_dict(self) -> Dict:
93
+ d = deepcopy(self.__dict__)
94
+ for key, value in self.seanet_params.__dict__.items():
95
+ d["seanet_" + key] = value
96
+ for key, value in self.quantizer_params.__dict__.items():
97
+ d["qt_" + key] = value
98
+ del d["quantizer_params"]
99
+ del d["seanet_params"]
100
+ return d
101
+
102
+
103
+ # --- CONDITIONING ---
104
+ @dataclass(unsafe_hash=True)
105
+ class PromptProcessorParams(Loggable):
106
+ keep_only_valid_steps: bool
107
+ model_class: Type["PromptProcessor"]
108
+ context_dropout: Optional[float] = None
109
+
110
+
111
+ @dataclass(unsafe_hash=True)
112
+ class ConditioningParams(Loggable):
113
+ embedder_types: Dict[ConditionType, Type[ConcreteEmbedder]]
114
+ conditioning_methods: Dict[ConditionType, ConditioningMethod]
115
+ conditioning_dropout: float
116
+
117
+
118
+ # --- LM ---
119
+ @dataclass(unsafe_hash=True, kw_only=True)
120
+ class LmParams(ModelParams):
121
+ dim: int
122
+ n_layers: int
123
+ n_heads: int
124
+ card: int = 2048
125
+ padding_token: Optional[int] = 2048
126
+ sep_token: Optional[int] = None
127
+ cross_attend: bool = True
128
+ weights: Optional[str] = None
129
+ model_class: str = "stage.models.musicgen_lm.MusicgenLm"
130
+
131
+
132
+ @dataclass(unsafe_hash=True, kw_only=True)
133
+ class PretrainedSmallLmParams(LmParams):
134
+ dim: int = 1024
135
+ n_layers: int = 24
136
+ n_heads: int = 16
137
+ weights: Optional[str] = str(cfg.weights_dir() / "lm-small-weights.pt")
138
+
139
+
140
+ @dataclass(unsafe_hash=True, kw_only=True)
141
+ class PretrainedLargeLmParams(LmParams):
142
+ dim: int = 2048
143
+ n_layers: int = 48
144
+ n_heads: int = 32
145
+ weights: Optional[str] = str(cfg.weights_dir() / "lm-large-weights.pt")
146
+
147
+
148
+ @dataclass(unsafe_hash=True, kw_only=True)
149
+ class PretrainedMelodyLmParams(LmParams):
150
+ dim: int = 1536
151
+ n_layers: int = 48
152
+ n_heads: int = 24
153
+ cross_attend: bool = False
154
+ weights: Optional[str] = str(cfg.weights_dir() / "lm-melody-weights.pt")
155
+
156
+
157
+ @dataclass(unsafe_hash=True, kw_only=True)
158
+ class FioraSmallLmParams(PretrainedSmallLmParams):
159
+ sep_token: Optional[int] = 2049
160
+
161
+
162
+ # --- LORA ---
163
+ @dataclass(unsafe_hash=True)
164
+ class LoraParams:
165
+ r: int
166
+ alpha: int
167
+ dropout: float
168
+ layers: List[str]
169
+
170
+
171
+ # --- MUSICGEN ---
172
+ @dataclass(unsafe_hash=True, kw_only=True)
173
+ class MusicgenParams(ModelParams):
174
+ encodec_params: EncodecParams
175
+ prompt_processor_params: PromptProcessorParams
176
+ conditioning_params: ConditioningParams
177
+ lm_params: LmParams
178
+ lora_params: Optional[LoraParams] = None
179
+ model_class: str = "stage.models.lightning_musicgen.LightningMusicgen"
180
+
181
+
182
+ # ------ DATA -------
183
+
184
+
185
+ @dataclass(unsafe_hash=True, kw_only=True)
186
+ class DatasetParams:
187
+ datamodule_class: str
188
+ clip_length_in_seconds: int
189
+ sample_rate: int
190
+
191
+ def to_dict(self) -> Dict:
192
+ return self.__dict__
193
+
194
+ def instantiate(self):
195
+ klass = locate(self.datamodule_class)
196
+ return klass(self) # type: ignore
197
+
198
+
199
+ @dataclass(kw_only=True)
200
+ class StemmedDatasetParams(DatasetParams):
201
+ root_dir: Path
202
+ stems: Set[Stem] = field(
203
+ default_factory=lambda: {
204
+ Stem.DRUMS,
205
+ Stem.BASS,
206
+ Stem.GUITAR,
207
+ Stem.KEYBOARD,
208
+ Stem.PIANO,
209
+ Stem.STRINGS,
210
+ Stem.OTHER,
211
+ })
212
+ single_stem: bool
213
+ target_stem: Stem
214
+ min_context_seconds: int
215
+ use_style_conditioning: bool
216
+ use_beat_conditioning: bool
217
+ type_of_context: str
218
+ add_click: bool
219
+ sync_chunks: bool
220
+ bpm_in_caption: bool
221
+ batch_size_train: int
222
+ batch_size_test: int
223
+ num_workers: int
224
+ clip_length_in_seconds: int
225
+ sample_rate: int
226
+ speed_transform_p: float
227
+ pitch_transform_p: float
228
+ n_samples_per_epoch: int
229
+ datamodule_class: str = "stage.data.stemmed_datamodule.StemmedDatamodule"
230
+
231
+
232
+ @dataclass(kw_only=True)
233
+ class MixDatasetParams(StemmedDatasetParams):
234
+ ...
235
+
236
+
237
+ # --------- CONFIGURATIONS ---------
238
+ pretrained_encodec_meta_32khz_params: EncodecParams = EncodecParams(
239
+ sample_rate=32_000,
240
+ seanet_params=SeaNetParams(128, 64, (8, 5, 4, 4), False, True),
241
+ quantizer_params=QuantizerParams(128, 4, 2048),
242
+ sum_loss_mulitiplier=0,
243
+ weights=str(cfg.weights_dir() / "encodec_32khz.pt"),
244
+ )
inference.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import lightning as L
3
+
4
+ import config as cfg
5
+ from loader import load_model
6
+ from utils.audio import load_audio, save_audio
7
+ """
8
+ REQUIREMENTS:
9
+
10
+ weights/
11
+ - encodec_32khz.pt
12
+ - lm-small-weights.pt in
13
+
14
+ checkpoints/
15
+ - stage-drums-ckp1.pt
16
+ - stage-bass-ckp1.pt
17
+ """
18
+
19
+ #%% Load model
20
+ INSTRUMENT = "drums"
21
+ checkpoint_path = cfg.CKP_DIR / f"stage-{INSTRUMENT}.safetensors"
22
+ model = load_model(checkpoint_path)
23
+
24
+ #%% Load conditioning, generate and save
25
+
26
+ # load context audio, description
27
+ SAMPLE = "sample2"
28
+ SEED = 42
29
+
30
+ # load description if present
31
+ desc_path = cfg.AUDIO_DIR / INSTRUMENT / f"{SAMPLE}-desc.txt"
32
+ desc = desc_path.read_text().strip() if desc_path.exists() else None
33
+
34
+ # load audio context
35
+ wav = load_audio(cfg.AUDIO_DIR / INSTRUMENT / f"{SAMPLE}.wav").to(model.device)
36
+
37
+ # generate
38
+ L.seed_everything(SEED)
39
+ out = model.generate(n_samples=1,
40
+ gen_seconds=10,
41
+ prompt=None,
42
+ context=wav,
43
+ style=None,
44
+ beat=None,
45
+ description=[desc],
46
+ prog_bar=True)
47
+
48
+ # save output and mix
49
+ save_audio(out, cfg.AUDIO_DIR / "gen" / f"{SAMPLE}_{INSTRUMENT}_{SEED}.wav")
50
+ padded_wav = torch.nn.functional.pad(wav,
51
+ tuple((0, out.shape[-1] - wav.shape[-1])),
52
+ value=0)
53
+ mix = out + padded_wav
54
+ save_audio(mix, cfg.AUDIO_DIR / "gen" / f"{SAMPLE}_{INSTRUMENT}_{SEED}_mix.wav")
loader.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from tabnanny import check
3
+ from typing import Optional
4
+ import torch
5
+ from safetensors import torch as sft
6
+
7
+ from conditioning.condition_type import ConditionType
8
+ from conditioning.conditioning_method import ConditioningMethod
9
+ from conditioning.prompt_processor import InterleavedContextPromptProcessor
10
+ from conditioning.t5embedder import T5EmbedderGPU
11
+ from models.lightning_musicgen import LightningMusicgen
12
+ import hyperparameters as hp
13
+
14
+
15
+ def load_model(checkpoint_path: Path,
16
+ device: Optional[str] = None) -> LightningMusicgen:
17
+
18
+ stage_params = hp.MusicgenParams(
19
+ encodec_params=hp.pretrained_encodec_meta_32khz_params,
20
+ prompt_processor_params=hp.PromptProcessorParams(
21
+ keep_only_valid_steps=True,
22
+ model_class=InterleavedContextPromptProcessor,
23
+ context_dropout=0.1),
24
+ conditioning_params=hp.ConditioningParams(
25
+ embedder_types={
26
+ ConditionType.DESCRIPTION: T5EmbedderGPU,
27
+ },
28
+ conditioning_methods={
29
+ ConditionType.DESCRIPTION: ConditioningMethod.CROSS_ATTENTION,
30
+ },
31
+ conditioning_dropout=0.5),
32
+ lm_params=hp.PretrainedSmallLmParams(sep_token=2049))
33
+
34
+ model: LightningMusicgen = stage_params.instantiate()
35
+ # state_dict = torch.load(checkpoint_path)
36
+ # model.load_state_dict(state_dict)
37
+ sft.load_model(model, checkpoint_path)
38
+ if device is None:
39
+ device = "cuda" if torch.cuda.is_available() else "cpu"
40
+ model = model.to(device).eval()
41
+
42
+ return model
models/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+
models/encodec.py ADDED
@@ -0,0 +1,436 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import logging
11
+ import contextlib
12
+ import os
13
+ import math
14
+ from pathlib import Path
15
+ import typing as tp
16
+ import numpy as np
17
+ import torch
18
+ from torch import nn
19
+ # from torchmetrics.functional.audio import scale_invariant_signal_distortion_ratio
20
+
21
+ import config as cfg
22
+ from models.quantization.base import QuantizedResult
23
+ from models import quantization as qt
24
+ import hyperparameters as hp
25
+ # from modules import SEANetDecoder, SEANetEncoder
26
+
27
+ # audiocraft imports
28
+ with contextlib.redirect_stderr(open(os.devnull, "w")):
29
+ from models.modules import SEANetEncoder, SEANetDecoder
30
+
31
+ logger = logging.getLogger()
32
+
33
+
34
+ class EncodecModel(nn.Module):
35
+ """Encodec model operating on the raw waveform.
36
+
37
+ Args:
38
+ encoder (nn.Module): Encoder network.
39
+ decoder (nn.Module): Decoder network.
40
+ quantizer (qt.BaseQuantizer): Quantizer network.
41
+ frame_rate (int): Frame rate for the latent representation.
42
+ sample_rate (int): Audio sample rate.
43
+ channels (int): Number of audio channels.
44
+ causal (bool): Whether to use a causal version of the model.
45
+ renormalize (bool): Whether to renormalize the audio before running the model.
46
+ """
47
+ # we need assignment to override the property in the abstract class,
48
+ # I couldn't find a better way...
49
+ frame_rate: float = 0 # type: ignore
50
+ sample_rate: int = 0
51
+ channels: int = 0
52
+
53
+ def __init__(self,
54
+ encoder: SEANetEncoder,
55
+ decoder: SEANetDecoder,
56
+ quantizer: qt.ResidualVectorQuantizer,
57
+ sample_rate: int,
58
+ channels: int,
59
+ causal: bool = True,
60
+ renormalize: bool = False):
61
+ super().__init__()
62
+ self.encoder: SEANetEncoder = encoder
63
+ self.decoder: SEANetDecoder = decoder
64
+ self.quantizer: qt.ResidualVectorQuantizer = quantizer
65
+ # self.frame_rate = frame_rate
66
+ self.sample_rate = sample_rate
67
+ self.channels = channels
68
+ self.renormalize = renormalize
69
+ self.causal = causal
70
+ self.frame_rate: int = int(
71
+ math.ceil(self.sample_rate /
72
+ np.prod(self.encoder.ratios))) # type: ignore
73
+
74
+ if self.causal:
75
+ # we force disabling here to avoid handling linear overlap of segments
76
+ # as supported in original EnCodec codebase.
77
+ assert not self.renormalize, 'Causal model does not support renormalize'
78
+
79
+ @property
80
+ def total_codebooks(self):
81
+ """Total number of quantizer codebooks available."""
82
+ return self.quantizer.total_codebooks
83
+
84
+ @property
85
+ def num_codebooks(self):
86
+ """Active number of codebooks used by the quantizer."""
87
+ return self.quantizer.num_codebooks
88
+
89
+ def set_num_codebooks(self, n: int):
90
+ """Set the active number of codebooks used by the quantizer."""
91
+ self.quantizer.set_num_codebooks(n)
92
+
93
+ @property
94
+ def cardinality(self):
95
+ """Cardinality of each codebook."""
96
+ return self.quantizer.bins
97
+
98
+ def preprocess(
99
+ self, x: torch.Tensor
100
+ ) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]]:
101
+ scale: tp.Optional[torch.Tensor]
102
+ if self.renormalize:
103
+ mono = x.mean(dim=1, keepdim=True)
104
+ volume = mono.pow(2).mean(dim=2, keepdim=True).sqrt()
105
+ scale = 1e-8 + volume
106
+ x = x / scale
107
+ scale = scale.view(-1, 1)
108
+ else:
109
+ scale = None
110
+ return x, scale
111
+
112
+ def postprocess(self,
113
+ x: torch.Tensor,
114
+ scale: tp.Optional[torch.Tensor] = None) -> torch.Tensor:
115
+ if scale is not None:
116
+ assert self.renormalize
117
+ x = x * scale.view(-1, 1, 1)
118
+ return x
119
+
120
+ def forward_with_sum_loss(
121
+ self,
122
+ x: torch.Tensor,
123
+ sum_loss_multiplier: float = 1.) -> qt.QuantizedResult:
124
+ """ Forward pass enforcing additivity in the latent space:
125
+ Q(x1) + Q(x2) = Q(x1 + x2)
126
+ """
127
+ raise NotImplementedError()
128
+
129
+ # if we are using sum_loss, batch size needs to be even
130
+ if x.shape[0] % 2 != 0:
131
+ raise ValueError("Batch size needs to be even to use sum loss. "
132
+ f"Received {x.shape}")
133
+ # y = x1 + x2
134
+ y = x.clone()
135
+ half_bs: int = x.shape[0] // 2
136
+ y = y[:half_bs] + y[half_bs:]
137
+
138
+ # encode y: E(y)
139
+ y, y_scale = self.preprocess(y)
140
+ encoded_y = self.encoder(y)
141
+
142
+ # encode x: E(x)
143
+ length = x.shape[-1]
144
+ x, scale = self.preprocess(x)
145
+ encoded_x = self.encoder(x)
146
+
147
+ # quantize x = x1, x2: get Q(x) = Q(x1), Q(x2)
148
+ x_quantized: QuantizedResult = self.quantizer(encoded_x,
149
+ self.frame_rate)
150
+
151
+ # quantize y = x1 + x2: get Q(y) = Q(x1 + x2)
152
+ y_quantized = self.quantizer(encoded_y, self.frame_rate) # type: ignore
153
+
154
+ # sum_of_quantized_layers = Q(x1) + Q(x2)
155
+ sum_of_quantized_layers = (
156
+ x_quantized.quantized_layers[:half_bs] + # type: ignore
157
+ x_quantized.quantized_layers[half_bs:]) # type: ignore
158
+
159
+ # quantization_of_sum = Q(x1 + x2) = Q(y)
160
+ quantization_of_sum_layers = y_quantized.quantized_layers
161
+
162
+ # compute sum_loss with L2. prediction: Q(x1) + Q(x2) target: Q(x1+x2)
163
+ sum_loss = nn.functional.mse_loss(sum_of_quantized_layers,
164
+ quantization_of_sum_layers)
165
+ sum_loss = sum_loss * sum_loss_multiplier
166
+ x_quantized.sum_loss = sum_loss
167
+
168
+ # decode Q(x) to get D(x) = D(x1), D(x2)
169
+ decoded_x = self.decoder(x_quantized.x)
170
+
171
+ # remove extra padding added by the encoder and decoder
172
+ assert decoded_x.shape[-1] >= length, (decoded_x.shape[-1], length)
173
+ decoded_x = decoded_x[..., :length]
174
+ # put in x_quantized.x the decoded version to return it
175
+ x_quantized.x = self.postprocess(decoded_x, scale)
176
+
177
+ # compute informative losses (metrics)
178
+ with torch.no_grad():
179
+
180
+ # only take last layer (sum of all layers)
181
+ sum_of_quantized = sum_of_quantized_layers[:, -1, ...]
182
+ quantization_of_sum = quantization_of_sum_layers[:, -1, ...]
183
+
184
+ # decode Q(y) to get D(y)
185
+ decoded_y = self.decoder(y_quantized.x)
186
+ assert decoded_y.shape[-1] >= length, (decoded_y.shape[-1], length)
187
+ decoded_y = decoded_y[..., :length]
188
+
189
+ # decode Q(x1) + Q(x2) to get D(Q(x1) + Q(x2))
190
+ decoded_sum_of_quantized = self.decoder(sum_of_quantized)
191
+ assert decoded_sum_of_quantized.shape[-1] >= length, (
192
+ decoded_sum_of_quantized.shape[-1], length)
193
+ decoded_sum_of_quantized = decoded_sum_of_quantized[..., :length]
194
+
195
+ # sum_of_decoded_quantized = D(Q(x1)) + D(Q(x2))
196
+ sum_of_decoded_quantized = decoded_x[:half_bs] + decoded_x[half_bs:]
197
+
198
+ # variable names recap:
199
+ # sum_of_quantized = Q(x1) + Q(x2)
200
+ # quantization_of_sum = Q(x1 + x2)
201
+ # decoded_sum_of_quantized = D(Q(x1) + Q(x2))
202
+ # sum_of_decoded_quantized = D(Q(x1)) + D(Q(x2))
203
+
204
+ # cosine similarity between Q(x1) + Q(x2) and Q(x1 + x2)
205
+ cos_sim = torch.nn.functional.cosine_similarity(sum_of_quantized,
206
+ quantization_of_sum,
207
+ dim=2).mean()
208
+
209
+ # recon-2
210
+ # sisdr1: prediction: D(Q(x1)) + D(Q(x2)) target: D(y)
211
+ sisdr1 = scale_invariant_signal_distortion_ratio(
212
+ sum_of_decoded_quantized, decoded_y).mean()
213
+
214
+ # comp-1
215
+ # sisdr2: prediction: D(Q(x1) + Q(x2)) target: D(y)
216
+ sisdr2 = scale_invariant_signal_distortion_ratio(
217
+ decoded_sum_of_quantized, decoded_y).mean()
218
+
219
+ # recon-1
220
+ # sisdr3: prediction: D(y) target: y
221
+ sisdr3 = scale_invariant_signal_distortion_ratio(decoded_y,
222
+ y).mean()
223
+
224
+ # comp-2
225
+ # sisdr4: prediction: D(Q(x1) + Q(x2)) target: y
226
+ sisdr4 = scale_invariant_signal_distortion_ratio(
227
+ decoded_sum_of_quantized, y).mean()
228
+
229
+ metrics: tp.Dict[str, float] = {
230
+ "quantizer/cos1": cos_sim.item(),
231
+ "quantizer/recon2": sisdr1.item(),
232
+ "quantizer/comp1": sisdr2.item(),
233
+ "quantizer/recon1": sisdr3.item(),
234
+ "quantizer/comp2": sisdr4.item(),
235
+ }
236
+ x_quantized.metrics = metrics
237
+
238
+ return x_quantized
239
+
240
+ def forward(
241
+ self,
242
+ x: torch.Tensor,
243
+ sum_loss_amount: float = 0.,
244
+ ) -> qt.QuantizedResult:
245
+
246
+ assert x.dim() == 3
247
+ if sum_loss_amount > 0:
248
+ return self.forward_with_sum_loss(x, sum_loss_amount)
249
+
250
+ length = x.shape[-1]
251
+ x, scale = self.preprocess(x)
252
+ emb = self.encoder(x)
253
+ q_res: QuantizedResult = self.quantizer(emb, self.frame_rate)
254
+ out = self.decoder(q_res.x)
255
+
256
+ # remove extra padding added by the encoder and decoder
257
+ assert out.shape[-1] >= length, (out.shape[-1], length)
258
+ out = out[..., :length]
259
+ q_res.x = self.postprocess(out, scale)
260
+
261
+ return q_res
262
+
263
+ def quantize_embedding(self, emb: torch.Tensor) -> QuantizedResult:
264
+ return self.quantizer(emb, self.frame_rate)
265
+
266
+ def quantize(self, x: torch.Tensor) -> QuantizedResult:
267
+ """ Pass x through encoder and quantizer and return QuantizedResult"""
268
+ assert x.dim() == 3
269
+ emb = self.encoder(x)
270
+ quantized: QuantizedResult = self.quantizer(emb, self.frame_rate)
271
+ return quantized
272
+
273
+ def dequantize(self, x: torch.Tensor | QuantizedResult) -> torch.Tensor:
274
+ if isinstance(x, QuantizedResult):
275
+ x = x.x
276
+ decoded: torch.Tensor = self.decoder(x)
277
+ return decoded
278
+
279
+ def encode(
280
+ self,
281
+ x: torch.Tensor,
282
+ return_scales: bool = False
283
+ # ) -> tp.Tuple[torch.Tensor, tp.Optional[torch.Tensor]] | torch.Tensor:
284
+ ) -> torch.Tensor:
285
+ """Encode the given input tensor to quantized representation along with scale parameter.
286
+
287
+ Args:
288
+ x (torch.Tensor): Float tensor of shape [B, C, T]
289
+
290
+ Returns:
291
+ codes, scale (tuple of torch.Tensor, torch.Tensor): Tuple composed of:
292
+ codes a float tensor of shape [B, K, T] with K the number of codebooks used and T the timestep.
293
+ scale a float tensor containing the scale for audio renormalizealization.
294
+ """
295
+ assert x.dim() == 3
296
+ x, scale = self.preprocess(x)
297
+ emb = self.encoder(x)
298
+ codes = self.quantizer.encode(emb)
299
+ # if return_scales:
300
+ # return codes, scale
301
+ return codes
302
+
303
+ def decode(self,
304
+ codes: torch.Tensor,
305
+ scale: tp.Optional[torch.Tensor] = None):
306
+ """Decode the given codes to a reconstructed representation, using the scale to perform
307
+ audio denormalization if needed.
308
+
309
+ Args:
310
+ codes (torch.Tensor): Int tensor of shape [B, K, T]
311
+ scale (torch.Tensor, optional): Float tensor containing the scale value.
312
+
313
+ Returns:
314
+ out (torch.Tensor): Float tensor of shape [B, C, T], the reconstructed audio.
315
+ """
316
+ emb = self.decode_latent(codes)
317
+ out = self.decoder(emb)
318
+ out = self.postprocess(out, scale)
319
+ # out contains extra padding added by the encoder and decoder
320
+ return out
321
+
322
+ def decode_latent(self, codes: torch.Tensor):
323
+ """Decode from the discrete codes to continuous latent space."""
324
+ return self.quantizer.decode(codes)
325
+
326
+ @staticmethod
327
+ def from_pretrained(name: str):
328
+ if name == "facebook/encodec_32khz":
329
+ model = torch.load(cfg.weights_dir() / "encodec_32khz.pt")
330
+ else:
331
+ raise NotImplementedError()
332
+
333
+ return model
334
+
335
+ @staticmethod
336
+ def from_params(params: hp.EncodecParams):
337
+
338
+ seanet_params = params.seanet_params
339
+ qt_params = params.quantizer_params
340
+ encoder: SEANetEncoder = SEANetEncoder(**seanet_params.__dict__)
341
+ decoder: SEANetDecoder = SEANetDecoder(**seanet_params.__dict__)
342
+ quantizer: qt.ResidualVectorQuantizer = qt.ResidualVectorQuantizer(
343
+ **qt_params.__dict__)
344
+
345
+ model = EncodecModel(encoder,
346
+ decoder,
347
+ quantizer,
348
+ params.sample_rate,
349
+ channels=1,
350
+ causal=params.seanet_params.causal,
351
+ renormalize=False)
352
+
353
+ if params.weights is not None:
354
+ weights = torch.load(Path(params.weights),
355
+ weights_only=True,
356
+ map_location="cpu")
357
+ model.load_state_dict(weights)
358
+
359
+ return model
360
+
361
+ # @staticmethod
362
+ # def _get_model(target_bandwidths: tp.List[float],
363
+ # sample_rate: int = 24_000,
364
+ # channels: int = 1,
365
+ # causal: bool = True,
366
+ # model_norm: str = 'weight_norm',
367
+ # audio_normalize: bool = False,
368
+ # segment: tp.Optional[float] = None,
369
+ # name: str = 'unset'):
370
+ # encoder = m.SEANetEncoder(channels=channels,
371
+ # norm=model_norm,
372
+ # causal=causal)
373
+ # decoder = m.SEANetDecoder(channels=channels,
374
+ # norm=model_norm,
375
+ # causal=causal)
376
+ # n_q = int(1000 * target_bandwidths[-1] //
377
+ # (math.ceil(sample_rate / encoder.hop_length) * 10)) # = 32
378
+ # quantizer = qt.ResidualVectorQuantizer(
379
+ # dimension=encoder.dimension,
380
+ # n_q=n_q,
381
+ # bins=1024,
382
+ # )
383
+ # model = EncodecModel(
384
+ # encoder,
385
+ # decoder,
386
+ # quantizer,
387
+ # # target_bandwidths,
388
+ # sample_rate,
389
+ # channels,
390
+ # causal=True,
391
+ # renormalize=audio_normalize,
392
+ # # segment=segment,
393
+ # # name=name,
394
+ # )
395
+ # return model
396
+
397
+ # @staticmethod
398
+ # def _get_pretrained(checkpoint_name: str,
399
+ # repository: tp.Optional[Path] = None):
400
+ # if repository is not None:
401
+ # if not repository.is_dir():
402
+ # raise ValueError(f"{repository} must exist and be a directory.")
403
+ # file = repository / checkpoint_name
404
+ # checksum = file.stem.split('-')[1]
405
+ # _check_checksum(file, checksum)
406
+ # return torch.load(file)
407
+ # else:
408
+ # url = _get_checkpoint_url(cfg.ROOT_URL, checkpoint_name)
409
+ # return torch.hub.load_state_dict_from_url(
410
+ # url, map_location=cfg.device, check_hash=True) # type:ignore
411
+
412
+ # @staticmethod
413
+ # def encodec_model_24khz(pretrained: bool = True,
414
+ # repository: tp.Optional[Path] = None):
415
+ # """Return the pretrained causal 24khz model.
416
+ # """
417
+ # if repository:
418
+ # assert pretrained
419
+ # target_bandwidths = [1.5, 3., 6, 12., 24.]
420
+ # checkpoint_name = 'encodec_24khz-d7cc33bc.th'
421
+ # sample_rate = 24_000
422
+ # channels = 1
423
+ # model = EncodecModel._get_model(
424
+ # target_bandwidths,
425
+ # sample_rate,
426
+ # channels,
427
+ # causal=True,
428
+ # model_norm='weight_norm',
429
+ # audio_normalize=False,
430
+ # name='encodec_24khz' if pretrained else 'unset')
431
+ # if pretrained:
432
+ # state_dict = EncodecModel._get_pretrained(checkpoint_name,
433
+ # repository)
434
+ # model.load_state_dict(state_dict)
435
+ # model.eval()
436
+ # return model
models/lightning_musicgen.py ADDED
@@ -0,0 +1,622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import itertools
2
+ from pathlib import Path
3
+ import lightning as L
4
+ from typing import Dict, Any, List, Mapping, Type, Optional, Sequence
5
+ from torch import Tensor
6
+ import torch
7
+ from dataclasses import dataclass
8
+ from lightning.pytorch import utilities as lightning_utils
9
+ from tqdm import tqdm
10
+ import loralib as lora
11
+
12
+ import hyperparameters as hp
13
+ from conditioning.beat_embedder import Beat, SinusoidalBeatEmbedder
14
+ from conditioning.condition_dispatcher import ConditionDispatcher
15
+ from conditioning.condition_provider import ConditionProvider
16
+ from conditioning.condition_type import ConditionType
17
+ from conditioning.conditioning_method import ConditioningMethod
18
+ from conditioning.embedded_condition import EmbeddedCondition
19
+ from conditioning.t5embedder import T5Embedder, T5EmbedderCPU, T5EmbedderGPU
20
+ from models.encodec import EncodecModel
21
+ from conditioning.prompt_processor import (
22
+ DefaultPromptProcessor, InterleavedContextPromptProcessor, PromptProcessor,
23
+ StraightContextPromptProcessor)
24
+ from models.loss import compute_cross_entropy
25
+ from models.musicgen_lm import MusicgenLm
26
+ from utils.audio import load_audio, save_audio
27
+ from utils.inspection import sanity_check
28
+ from utils.sample import eval_decorator, sample_top_k
29
+ import config as cfg
30
+
31
+
32
+ class LightningMusicgen(L.LightningModule):
33
+
34
+ def __init__(self, params: hp.MusicgenParams):
35
+ super().__init__()
36
+ self.params: hp.MusicgenParams = params
37
+
38
+ # instantiate EnCodec
39
+ self.encodec_model: EncodecModel = EncodecModel.from_params(
40
+ self.params.encodec_params)
41
+ self.sample_rate: int = self.encodec_model.sample_rate
42
+ self.n_q: int = self.encodec_model.num_codebooks
43
+ self.special_token: int = 2048
44
+ assert self.n_q == 4
45
+
46
+ # freeze encodec
47
+ for p in self.encodec_model.parameters():
48
+ p.requires_grad = False
49
+
50
+ # instantiate prompt processor
51
+ self.prompt_processor: PromptProcessor = self.params.prompt_processor_params.model_class(
52
+ self.encodec_model,
53
+ self.special_token,
54
+ keep_only_valid_steps=params.prompt_processor_params.
55
+ keep_only_valid_steps,
56
+ context_dropout=params.prompt_processor_params.context_dropout)
57
+
58
+ # check consistency in prompt processor and lm params
59
+ if self.prompt_processor.uses_sep_token:
60
+ if self.params.lm_params.sep_token is None:
61
+ raise cfg.ConfigurationError(
62
+ "This prompt processor requires an LM with "
63
+ "support for a separator token.")
64
+
65
+ # instantiate lm
66
+ self.lm: MusicgenLm = MusicgenLm(self.params.lm_params)
67
+
68
+ # check consistency in conditioning parameters
69
+ if (self.params.conditioning_params.embedder_types.keys()
70
+ != self.params.conditioning_params.conditioning_methods.keys()):
71
+ t1 = set(
72
+ p.value
73
+ for p in self.params.conditioning_params.embedder_types.keys())
74
+ t2 = set(
75
+ p.value for p in
76
+ self.params.conditioning_params.conditioning_methods.keys())
77
+ raise ValueError(
78
+ "Embeddings produced by the condition provider don't match the "
79
+ f"conditioning methods given in params. "
80
+ f"processed conditions: {t1} "
81
+ f"conditioning methods: {t2}")
82
+
83
+ # instantiate condition provider (embedder)
84
+ self.condition_provider = ConditionProvider(
85
+ self.params.lm_params.dim,
86
+ embedder_types=self.params.conditioning_params.embedder_types,
87
+ )
88
+
89
+ if self.params.lm_params.weights is not None:
90
+ w = torch.load(Path(self.params.lm_params.weights),
91
+ map_location=None,
92
+ weights_only=True)
93
+ newstatedict = {
94
+ "condition_provider.embedders.description.output_proj.weight":
95
+ w['conditioner.output_proj.weight'],
96
+ "condition_provider.embedders.description.output_proj.bias":
97
+ w['conditioner.output_proj.bias']
98
+ }
99
+ self.load_state_dict(newstatedict, strict=False)
100
+
101
+ # instantiate condition dispatcher (contains fusers)
102
+ self.condition_dispatcher: ConditionDispatcher = ConditionDispatcher(
103
+ self.params.conditioning_params.conditioning_methods,
104
+ self.params.lm_params.dim,
105
+ self.params.conditioning_params.conditioning_dropout)
106
+
107
+ # inject lora if needed
108
+ if self.params.lora_params is not None:
109
+ self._inject_lora()
110
+
111
+ self.save_hyperparameters()
112
+
113
+ def _inject_lora(self):
114
+ assert self.params.lora_params is not None
115
+
116
+ # freeze the decoder model
117
+ # for p in self.lm.decoder.parameters():
118
+ # p.requires_grad = False
119
+
120
+ layers: torch.nn.ModuleList = self.lm.decoder.attn_layers.layers
121
+ # inject lora in every attention layer
122
+ for att_idx in range(self.params.lm_params.n_layers):
123
+ for sublayer_idx in range(2):
124
+ layeridx = att_idx * 3 + sublayer_idx
125
+ sublayer: torch.nn.ModuleList = layers[layeridx] # type: ignore
126
+ # for all layer types we want to swap
127
+ for layername in self.params.lora_params.layers:
128
+ source_layer = sublayer[1].__getattr__(f"to_{layername}")
129
+ new_layer = lora.Linear(source_layer.in_features,
130
+ source_layer.out_features,
131
+ self.params.lora_params.r,
132
+ self.params.lora_params.alpha,
133
+ self.params.lora_params.dropout,
134
+ bias=False)
135
+ with torch.no_grad():
136
+ new_layer.weight.data.copy_(source_layer.weight.data)
137
+ if layername == "q":
138
+ sublayer[1].to_q = new_layer
139
+ elif layername == "k":
140
+ sublayer[1].to_k = new_layer
141
+ elif layername == "v":
142
+ sublayer[1].to_v = new_layer
143
+ elif layername == "out":
144
+ sublayer[1].to_out = new_layer
145
+ else:
146
+ raise RuntimeError(f"unknown layer name {layername}")
147
+ # sublayer[1].__setattr__(f"to_{layername}", new_layer)
148
+
149
+ lora.mark_only_lora_as_trainable(self)
150
+
151
+ def configure_optimizers(self): # type: ignore
152
+ opt = torch.optim.AdamW(self.parameters(),
153
+ lr=1e-5,
154
+ betas=(0.9, 0.95),
155
+ weight_decay=0.001)
156
+ return opt
157
+ # n_warmup_steps: int = 1000
158
+
159
+ # projections = (
160
+ # p for n, p in self.named_parameters() if "output_proj" in n)
161
+ # embeddings = (p for n, p in self.named_parameters() if "token_emb" in n)
162
+ # warmup_params = itertools.chain(projections, embeddings)
163
+ # others = (p for n, p in self.named_parameters()
164
+ # if "output_proj" not in n and "token_emb" not in n)
165
+
166
+ # multigroup_optim = torch.optim.AdamW(
167
+ # ({
168
+ # "params": warmup_params
169
+ # }, {
170
+ # "params": others
171
+ # }),
172
+ # lr=2e-5,
173
+ # betas=(0.9, 0.95),
174
+ # weight_decay=0.1,
175
+ # )
176
+
177
+ # lambda_encoder = lambda x: (1 + 1.5 * (1 - (x / n_warmup_steps))
178
+ # ) if x < n_warmup_steps else 1.
179
+ # lambda_decoder = lambda x: 0. if x < n_warmup_steps else 1.
180
+
181
+ # multigroup_scheduler = torch.optim.lr_scheduler.LambdaLR(
182
+ # multigroup_optim,
183
+ # lr_lambda=[lambda_encoder, lambda_decoder],
184
+ # )
185
+
186
+ # scheduler_config = {
187
+ # "scheduler": multigroup_scheduler,
188
+ # "interval": "step"
189
+ # }
190
+
191
+ # return {"optimizer": multigroup_optim, "lr_scheduler": scheduler_config}
192
+
193
+ # def on_train_batch_end(self, outputs: Tensor | Mapping[str, Any] | None,
194
+ # batch: Any, batch_idx: int) -> None:
195
+ # def on_before_optimizer_step(self, optimizer):
196
+ # decoder_grads = lightning_utils.grad_norm(self.lm.decoder, 2)
197
+ # self.log_dict(decoder_grads)
198
+
199
+ def training_step(self, batch, batch_idx) -> Tensor:
200
+ self.train(True)
201
+ loss = self.run_step(batch)
202
+ self.log(
203
+ "train/loss",
204
+ loss,
205
+ # prog_bar=True,
206
+ batch_size=len(batch["target"]),
207
+ # sync_dist=True,
208
+ # on_step=True,
209
+ )
210
+ self.log("global_step", self.global_step, prog_bar=True, logger=False)
211
+ if self._trainer is not None and self.lr_schedulers() is not None:
212
+ self.log(
213
+ "train/new_params_lr",
214
+ self.lr_schedulers().get_last_lr()[0], # type: ignore
215
+ prog_bar=True)
216
+ self.log(
217
+ "train/old_params_lr",
218
+ self.lr_schedulers().get_last_lr()[1], # type: ignore
219
+ prog_bar=True)
220
+ return loss
221
+
222
+ def validation_step(self, batch, batch_idx) -> Tensor:
223
+ self.train(False)
224
+ with torch.no_grad():
225
+ loss = self.run_step(batch)
226
+ self.log(
227
+ "val/loss",
228
+ loss,
229
+ prog_bar=True,
230
+ batch_size=len(batch["target"]),
231
+ sync_dist=True,
232
+ )
233
+ # sanity_check(self, interrupt=True)
234
+ return loss
235
+
236
+ def run_step(self, batch: Dict[str, Any]) -> Tensor:
237
+ """
238
+ Expects batch to be a dictionary like:
239
+ {
240
+ "target": Tensor,
241
+ "context": Tensor, - optional
242
+ "style": Tensor, - optional
243
+ "description": string, - optional
244
+ }
245
+ """
246
+
247
+ # call prompt pre-processor
248
+ (prompt, prompt_mask, target,
249
+ decode_logits_fn) = self.prompt_processor.preprocess(batch)
250
+
251
+ attention_mask = prompt_mask.sum(dim=-2) > 0
252
+
253
+ # embed/encode conditioning data
254
+ processed_conditions: Dict[ConditionType, EmbeddedCondition] = (
255
+ self.condition_provider.process_conditions(batch))
256
+
257
+ # dispatch eatch conditioning to the proper method, fusing if necessary
258
+ method_to_cond: Dict[ConditioningMethod,
259
+ EmbeddedCondition] = self.condition_dispatcher(
260
+ processed_conditions)
261
+
262
+ # call language model
263
+ logits = self.lm(
264
+ x=prompt,
265
+ attention_mask=attention_mask,
266
+ cross_attention_input=method_to_cond.get(
267
+ ConditioningMethod.CROSS_ATTENTION),
268
+ prepend_embeds=method_to_cond.get(ConditioningMethod.INPUT_PREPEND),
269
+ sum_embeds=method_to_cond.get(ConditioningMethod.INPUT_SUM),
270
+ )
271
+
272
+ # de-interleave logits and postprocess prompt
273
+ logits, logits_mask = decode_logits_fn(logits)
274
+
275
+ # compute cross entropy
276
+ cross_entropy_loss, _ = compute_cross_entropy(logits, target,
277
+ logits_mask)
278
+
279
+ return cross_entropy_loss
280
+
281
+ def sample_next_token(
282
+ self, current_sequence: Tensor, attention_mask: Tensor,
283
+ method_to_cond: Dict[ConditioningMethod,
284
+ EmbeddedCondition]) -> Tensor:
285
+
286
+ if not current_sequence.isfinite().all():
287
+ if current_sequence.isnan().any():
288
+ print(f"Before forward pass some logits are nan")
289
+ else:
290
+ print(f"Before forward pass some logits are not finite")
291
+
292
+ # call language model
293
+ logits: Tensor = self.lm(
294
+ x=current_sequence,
295
+ attention_mask=attention_mask,
296
+ cross_attention_input=method_to_cond.get(
297
+ ConditioningMethod.CROSS_ATTENTION),
298
+ prepend_embeds=method_to_cond.get(ConditioningMethod.INPUT_PREPEND),
299
+ sum_embeds=method_to_cond.get(ConditioningMethod.INPUT_SUM),
300
+ )
301
+
302
+ if not logits.isfinite().all():
303
+ if logits.isnan().any():
304
+ print(f"After forward pass some logits are nan")
305
+ else:
306
+ print(f"After forward pass some logits are not finite")
307
+
308
+ # classifier-free guidance
309
+ cond_logits, uncond_logits = logits.split(
310
+ current_sequence.shape[0] // 2,
311
+ dim=0,
312
+ )
313
+ logits = uncond_logits + (cond_logits - uncond_logits) * 3.0
314
+
315
+ # get logits for last token
316
+ logits = logits.permute(0, 1, 3, 2) # B, K, card, T
317
+ logits = logits[..., -1] # B, K, card,
318
+
319
+ # apply softmax
320
+ probs = torch.softmax(logits, dim=-1)
321
+
322
+ # sample
323
+ next_token = sample_top_k(probs, k=250)
324
+ return next_token
325
+
326
+ def predict_step(self, batch):
327
+ batch["prog_bar"] = False
328
+ return self.generate(**batch)
329
+
330
+ # @torch.inference_mode()
331
+ @eval_decorator
332
+ @torch.no_grad()
333
+ def generate(self,
334
+ n_samples: int,
335
+ gen_seconds: float | int,
336
+ prompt: Optional[Tensor],
337
+ context: Optional[Tensor | List[Tensor]],
338
+ style: Optional[Tensor],
339
+ beat: Optional[List[Beat]],
340
+ description: Optional[List[str]],
341
+ context_dropout_mask: Optional[Tensor] = None,
342
+ prog_bar: bool = False) -> Tensor:
343
+ """Run autoregressive generation
344
+
345
+ Args:
346
+ n_samples (int): number of samples to generate (batch size). All other input parameters should match this.
347
+ gen_seconds (float | int): total length of generation in seconds, including prompt if present.
348
+ prompt (Optional[Tensor]): a piece of input to continue.
349
+ context (Optional[Tensor | List[Tensor]]): a musical context to generate an accompaniment for.
350
+ style (Optional[Tensor]): a piece of music to use as stylistic reference.
351
+ beat (Optional[List[Beat]]): a beat object to follow
352
+ description (Optional[List[str]]): a list of descriptions to use as conditioning
353
+ context_dropout_mask (Optional[Tensor], optional): Defaults to None.
354
+ prog_bar (bool, optional): whether to display a progress bar. Defaults to False.
355
+
356
+ Raises:
357
+ ValueError: _description_
358
+
359
+ Returns:
360
+ Tensor: _description_
361
+ """
362
+
363
+ n_gen_frames = int(self.encodec_model.frame_rate * gen_seconds)
364
+
365
+ # generate empty sequence
366
+ gen_sequence = torch.full((n_samples, self.n_q, n_gen_frames),
367
+ -1,
368
+ dtype=torch.long,
369
+ device=self.device)
370
+
371
+ # pre-process prompt to feed to the lm
372
+ (gen_sequence, gen_mask, start_offset,
373
+ decode_sequence_fn) = self.prompt_processor.prepare_for_generation(
374
+ prompt,
375
+ context,
376
+ gen_sequence,
377
+ use_cfg=True,
378
+ context_dropout_mask=context_dropout_mask)
379
+
380
+ # attention mask: in timesteps in which ALL residual layers are invalid,
381
+ # set attention mask to False.
382
+ attention_mask = gen_mask.sum(dim=-2) > 0
383
+
384
+ # from now on we only need the first part of the gen_mask, the second was cfg
385
+ gen_mask = gen_mask[:gen_mask.shape[0] // 2]
386
+
387
+ # embed/encode conditioning data
388
+ conditions = {
389
+ "description": description,
390
+ # "context": context,
391
+ "style": style,
392
+ "beat": beat,
393
+ }
394
+
395
+ # check for compatibilty of conditions
396
+ for c_name, c_value in conditions.items():
397
+ if c_value is None:
398
+ continue
399
+ condtype: ConditionType = ConditionType(c_name)
400
+ if condtype.value not in self.condition_provider.embedders:
401
+ raise ValueError(
402
+ f"This version of the model does not support conditioning "
403
+ f"with {c_name}. You should pass None.")
404
+
405
+ processed_conditions: Dict[ConditionType, EmbeddedCondition] = (
406
+ self.condition_provider.process_conditions(conditions,
407
+ duplicate_for_cfg=True,
408
+ batch_size=n_samples))
409
+
410
+ # dispatch eatch conditioning to the proper method, fusing if necessary
411
+ method_to_cond: Dict[ConditioningMethod,
412
+ EmbeddedCondition] = self.condition_dispatcher(
413
+ processed_conditions)
414
+ # autoregression
415
+
416
+ iterator = range(start_offset, gen_sequence.shape[-1])
417
+ if prog_bar:
418
+ iterator = tqdm(iterator, desc="generating autoregressively...")
419
+
420
+ for offset in iterator:
421
+ current_sequence = gen_sequence[..., :offset]
422
+ current_mask = attention_mask[..., :offset]
423
+ next_token = self.sample_next_token(current_sequence, current_mask,
424
+ method_to_cond)
425
+ valid_mask = gen_mask[
426
+ ..., # TODO: I can't figure out if this is correct or if it matters at all anyways
427
+ offset:offset + 1].expand(n_samples, -1, -1)
428
+ next_token[~valid_mask] = self.special_token
429
+ gen_sequence[:n_samples, :, offset:offset + 1] = torch.where(
430
+ gen_sequence[:n_samples, :, offset:offset + 1] == -1,
431
+ next_token,
432
+ gen_sequence[:n_samples, :, offset:offset + 1],
433
+ )
434
+ gen_sequence[n_samples:, :, offset:offset + 1] = torch.where(
435
+ gen_sequence[n_samples:, :, offset:offset + 1] == -1,
436
+ next_token,
437
+ gen_sequence[n_samples:, :, offset:offset + 1],
438
+ )
439
+
440
+ if prog_bar and torch.cuda.is_available():
441
+ torch.cuda.synchronize()
442
+
443
+ assert not (gen_sequence == -1).any()
444
+ gen_sequence = gen_sequence[:gen_sequence.shape[0] // 2]
445
+ # assert (gen_sequence == torch.where(
446
+ # gen_mask[None, ...].expand(n_samples, -1, -1),
447
+ # gen_sequence,
448
+ # self.special_token,
449
+ # )).all()
450
+
451
+ out_codes, out_mask = decode_sequence_fn(gen_sequence)
452
+
453
+ self.encodec_model.eval()
454
+ with torch.no_grad():
455
+ out_audio = self.encodec_model.decode(out_codes)
456
+ return out_audio
457
+
458
+ @staticmethod
459
+ def load_from_checkpoint_replacing_paths(ckp_path: Path | str):
460
+ ckp_path = Path(ckp_path)
461
+
462
+ def swap_parent(filepath: Path | str, new_parent: Path) -> Path | str:
463
+ if isinstance(filepath, Path):
464
+ typeout = Path
465
+ elif isinstance(filepath, str):
466
+ typeout = str
467
+ else:
468
+ raise RuntimeError("expected Path or str")
469
+
470
+ filepath = Path(filepath)
471
+ return typeout(new_parent / filepath.name)
472
+
473
+ ckp = torch.load(ckp_path, map_location="cpu")
474
+ params = ckp["hyper_parameters"]["params"]
475
+ params.encodec_params.weights = swap_parent(
476
+ params.encodec_params.weights, cfg.weights_dir())
477
+ params.lm_params.weights = swap_parent(params.lm_params.weights,
478
+ cfg.weights_dir())
479
+ model = LightningMusicgen(params)
480
+ model.load_state_dict(ckp["state_dict"])
481
+ return model
482
+
483
+
484
+ if __name__ == "__main__":
485
+ from time import time
486
+
487
+ device = torch.device("cuda")
488
+
489
+ # musicgen params
490
+ model_params = hp.MusicgenParams(
491
+ encodec_params=hp.pretrained_encodec_meta_32khz_params,
492
+ prompt_processor_params=hp.PromptProcessorParams(
493
+ model_class=InterleavedContextPromptProcessor,
494
+ keep_only_valid_steps=True,
495
+ context_dropout=0.5,
496
+ ),
497
+ conditioning_params=hp.ConditioningParams(
498
+ embedder_types={
499
+ ConditionType.DESCRIPTION: T5EmbedderGPU,
500
+ ConditionType.BEAT: SinusoidalBeatEmbedder,
501
+ },
502
+ conditioning_methods={
503
+ ConditionType.DESCRIPTION: ConditioningMethod.CROSS_ATTENTION,
504
+ ConditionType.BEAT: ConditioningMethod.INPUT_PREPEND,
505
+ },
506
+ conditioning_dropout=0.5,
507
+ ),
508
+ lm_params=hp.PretrainedSmallLmParams(sep_token=2049),
509
+ )
510
+
511
+ model = model_params.instantiate().to(device)
512
+
513
+ context = [
514
+ torch.rand(1, 1, 200_000).to(device),
515
+ # torch.rand(1, 1, 1234).to(device)
516
+ ]
517
+
518
+ # TEST TRAINING/VALIDATION STEP
519
+ n_tries = 5
520
+ for _ in range(n_tries):
521
+ batch = {
522
+ "target": torch.rand(1, 1, 320_000).to(device),
523
+ "context": context,
524
+ "beat": [
525
+ Beat(beats=(torch.arange(18) * 16_000).long(),
526
+ downbeats=(torch.arange(0, 18, 4) * 16_000).long(),
527
+ seq_len=320_000)
528
+ ],
529
+ "description": [""],
530
+ }
531
+ t0 = time()
532
+ loss = model.run_step(batch)
533
+ torch.cuda.synchronize()
534
+ t1 = time()
535
+ print(f"training step in {t1 - t0} seconds")
536
+
537
+ # print("MODEL COMPILED")
538
+ # model = torch.compile(model, fullgraph=True,
539
+ # backend="eager") # type: ignore
540
+ # n_tries = 5
541
+ # for _ in range(n_tries):
542
+ # batch = {
543
+ # "target": torch.rand(2, 1, 1_000).to(device),
544
+ # "context": torch.rand(2, 1, 1_000).to(device) * 2,
545
+ # "style": torch.rand(2, 1, 1_000).to(device),
546
+ # "description": ["", ""],
547
+ # }
548
+ # t0 = time()
549
+ # loss = model.run_step(batch)
550
+ # t1 = time()
551
+ # print(f"training step in {t1 - t0} seconds")
552
+
553
+ # TEST INFERENCE
554
+ L.seed_everything(42)
555
+ model.eval()
556
+
557
+ # audio1 = load_audio(cfg.AUDIO_DIR / "42cpu.wav").to(device)
558
+ # audio2 = load_audio(cfg.AUDIO_DIR / "42gpuT5cpu.wav").to(device)
559
+
560
+ # PROMPT
561
+ # prompt = audio1
562
+ prompt = None
563
+
564
+ # CONTEXT
565
+ # context = torch.rand(2, 1, 320_000).to(device)
566
+ # context = None
567
+ # context = torch.cat((audio1, audio2), dim=0)
568
+ # context = audio1.reshape(1, 1, -1)
569
+ context = load_audio(cfg.EXP_DIR / "experiment_1" /
570
+ "context.wav").to(device).reshape(1, 1, -1)
571
+ # context[0, ...] = 0
572
+
573
+ # STYLE
574
+ # style = torch.cat((audio1, audio2), dim=0)
575
+ # style = torch.rand(2, 1, 320_000).to(device)
576
+ style = None
577
+
578
+ # DESCRIPTION
579
+ description = [
580
+ ""
581
+ # "lo-fi chill beat with drums, keyboard and bass playing in a relaxed mood",
582
+ # "lo-fi chill beat with drums, keyboard and bass playing in a relaxed mood"
583
+ ]
584
+ # description = None
585
+
586
+ # with torch.autocast(device_type="cuda"):
587
+ # t0 = time()
588
+ # gen_audio = model.generate(
589
+ # n_samples=len(description),
590
+ # gen_seconds=10,
591
+ # prompt=prompt,
592
+ # context=context,
593
+ # style=style,
594
+ # description=description,
595
+ # prog_bar=True,
596
+ # )
597
+ # torch.cuda.synchronize()
598
+ # t1 = time()
599
+ # print(f"inference completed in {t1 - t0} seconds")
600
+
601
+ # # for i in range(gen_audio.shape[0]):
602
+ # save_audio(gen_audio, cfg.AUDIO_DIR / f"temp.wav")
603
+
604
+ # args = {
605
+ # "n_samples": 1,
606
+ # "gen_seconds": 10,
607
+ # "prompt": prompt,
608
+ # "context": context,
609
+ # "style": style,
610
+ # "description": description,
611
+ # }
612
+ # i = iter((args,))
613
+
614
+ # trainer = L.Trainer(precision="32", enable_progress_bar=False)
615
+
616
+ # t0 = time()
617
+ # gen_audio = trainer.predict(model, i)
618
+ # torch.cuda.synchronize()
619
+ # t1 = time()
620
+ # print(f"predict completed in {t1 - t0} seconds")
621
+
622
+ # save_audio(gen_audio[0], cfg.AUDIO_DIR / f"temp.wav") # type: ignore
models/loss.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from torch import Tensor
3
+ from typing import Tuple, List
4
+ from torch.nn import functional as F
5
+
6
+
7
+ def compute_cross_entropy(
8
+ logits: Tensor,
9
+ targets: Tensor,
10
+ mask: Tensor,
11
+ ) -> Tuple[torch.Tensor, List[torch.Tensor]]:
12
+ """Compute cross entropy between multi-codebook targets and model's logits.
13
+ The cross entropy is computed per codebook to provide codebook-level cross entropy.
14
+ Valid timesteps for each of the codebook are pulled from the mask, where invalid
15
+ timesteps are set to 0.
16
+
17
+ Args:
18
+ logits (torch.Tensor): Model's logits of shape [B, K, T, card].
19
+ targets (torch.Tensor): Target codes, of shape [B, K, T].
20
+ mask (torch.Tensor): Mask for valid target codes, of shape [B, K, T].
21
+ Returns:
22
+ ce (torch.Tensor): Cross entropy averaged over the codebooks
23
+ ce_per_codebook (list of torch.Tensor): Cross entropy per codebook (detached).
24
+ """
25
+ B, K, T = targets.shape
26
+ assert logits.shape[:-1] == targets.shape
27
+ assert mask.shape == targets.shape
28
+ ce = torch.zeros([], device=targets.device)
29
+ ce_per_codebook: List[Tensor] = []
30
+ for k in range(K):
31
+ logits_k = (logits[:, k, ...].contiguous().view(-1, logits.size(-1))
32
+ ) # [B x T, card]
33
+ targets_k = targets[:, k, ...].contiguous().view(-1) # [B x T]
34
+ mask_k = mask[:, k, ...].contiguous().view(-1) # [B x T]
35
+ ce_targets = targets_k[mask_k]
36
+ ce_logits = logits_k[mask_k]
37
+
38
+ # if the codebook is masked out, the loss is 0
39
+ if mask_k.sum() == 0:
40
+ q_ce = torch.tensor(0.0, device=targets.device)
41
+ else:
42
+ q_ce = F.cross_entropy(ce_logits, ce_targets)
43
+
44
+ ce += q_ce
45
+ ce_per_codebook.append(q_ce.detach())
46
+ # average cross entropy across codebooks
47
+ ce = ce / K
48
+ return ce, ce_per_codebook
models/modules/__init__.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Torch modules."""
7
+
8
+ # flake8: noqa
9
+ from .conv import (
10
+ pad1d,
11
+ unpad1d,
12
+ pad_for_conv1d,
13
+ NormConv1d,
14
+ NormConvTranspose1d,
15
+ NormConv2d,
16
+ NormConvTranspose2d,
17
+ SConv1d,
18
+ SConvTranspose1d,
19
+ )
20
+ from .lstm import SLSTM
21
+ from .seanet import SEANetEncoder, SEANetDecoder
22
+ from .codebooks_patterns import DelayedPatternProvider
23
+ # from .transformer import StreamingTransformerEncoder
models/modules/codebooks_patterns.py ADDED
@@ -0,0 +1,548 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ from collections import namedtuple
8
+ from dataclasses import dataclass
9
+ from functools import lru_cache
10
+ import logging
11
+ import typing as tp
12
+
13
+ from abc import ABC, abstractmethod
14
+ import torch
15
+
16
+ LayoutCoord = namedtuple('LayoutCoord', ['t', 'q']) # (timestep, codebook index)
17
+ PatternLayout = tp.List[tp.List[LayoutCoord]] # Sequence of coordinates
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class Pattern:
23
+ """Base implementation of a pattern over a sequence with multiple codebooks.
24
+
25
+ The codebook pattern consists in a layout, defining for each sequence step
26
+ the list of coordinates of each codebook timestep in the resulting interleaved sequence.
27
+ The first item of the pattern is always an empty list in order to properly insert a special token
28
+ to start with. For convenience, we also keep track of ``n_q`` the number of codebooks used for the pattern
29
+ and ``timesteps`` the number of timesteps corresponding to the original sequence.
30
+
31
+ The pattern provides convenient methods to build and revert interleaved sequences from it:
32
+ ``build_pattern_sequence`` maps a given a dense input tensor of multi-codebook sequence from [B, K, T]
33
+ to the interleaved sequence of shape [B, K, S] applying the pattern, with B being the batch size,
34
+ K being the number of codebooks, T the number of original timesteps and S the number of sequence steps
35
+ for the output sequence. The unfilled positions are replaced with a special token and the built sequence
36
+ is returned along with a mask indicating valid tokens.
37
+ ``revert_pattern_sequence`` maps back an interleaved sequence of shape [B, K, S] to the original alignment
38
+ of codebooks across timesteps to an output tensor of shape [B, K, T], using again a special token and a mask
39
+ to fill and specify invalid positions if needed.
40
+ See the dedicated methods for more details.
41
+ """
42
+ # Pattern layout, for each sequence step, we have a list of coordinates
43
+ # corresponding to the original codebook timestep and position.
44
+ # The first list is always an empty list in order to properly insert
45
+ # a special token to start with.
46
+ layout: PatternLayout
47
+ timesteps: int
48
+ n_q: int
49
+
50
+ def __post_init__(self):
51
+ assert len(self.layout) > 0
52
+ self._validate_layout()
53
+ self._build_reverted_sequence_scatter_indexes = lru_cache(100)(self._build_reverted_sequence_scatter_indexes)
54
+ self._build_pattern_sequence_scatter_indexes = lru_cache(100)(self._build_pattern_sequence_scatter_indexes)
55
+ logger.info("New pattern, time steps: %d, sequence steps: %d", self.timesteps, len(self.layout))
56
+
57
+ def _validate_layout(self):
58
+ """Runs checks on the layout to ensure a valid pattern is defined.
59
+ A pattern is considered invalid if:
60
+ - Multiple timesteps for a same codebook are defined in the same sequence step
61
+ - The timesteps for a given codebook are not in ascending order as we advance in the sequence
62
+ (this would mean that we have future timesteps before past timesteps).
63
+ """
64
+ q_timesteps = {q: 0 for q in range(self.n_q)}
65
+ for s, seq_coords in enumerate(self.layout):
66
+ if len(seq_coords) > 0:
67
+ qs = set()
68
+ for coord in seq_coords:
69
+ qs.add(coord.q)
70
+ last_q_timestep = q_timesteps[coord.q]
71
+ assert coord.t >= last_q_timestep, \
72
+ f"Past timesteps are found in the sequence for codebook = {coord.q} at step {s}"
73
+ q_timesteps[coord.q] = coord.t
74
+ # each sequence step contains at max 1 coordinate per codebook
75
+ assert len(qs) == len(seq_coords), \
76
+ f"Multiple entries for a same codebook are found at step {s}"
77
+
78
+ @property
79
+ def num_sequence_steps(self):
80
+ return len(self.layout) - 1
81
+
82
+ @property
83
+ def max_delay(self):
84
+ max_t_in_seq_coords = 0
85
+ for seq_coords in self.layout[1:]:
86
+ for coords in seq_coords:
87
+ max_t_in_seq_coords = max(max_t_in_seq_coords, coords.t + 1)
88
+ return max_t_in_seq_coords - self.timesteps
89
+
90
+ @property
91
+ def valid_layout(self):
92
+ valid_step = len(self.layout) - self.max_delay
93
+ return self.layout[:valid_step]
94
+
95
+ def starts_with_special_token(self):
96
+ return self.layout[0] == []
97
+
98
+ def get_sequence_coords_with_timestep(self, t: int, q: tp.Optional[int] = None):
99
+ """Get codebook coordinates in the layout that corresponds to the specified timestep t
100
+ and optionally to the codebook q. Coordinates are returned as a tuple with the sequence step
101
+ and the actual codebook coordinates.
102
+ """
103
+ assert t <= self.timesteps, "provided timesteps is greater than the pattern's number of timesteps"
104
+ if q is not None:
105
+ assert q <= self.n_q, "provided number of codebooks is greater than the pattern's number of codebooks"
106
+ coords = []
107
+ for s, seq_codes in enumerate(self.layout):
108
+ for code in seq_codes:
109
+ if code.t == t and (q is None or code.q == q):
110
+ coords.append((s, code))
111
+ return coords
112
+
113
+ def get_steps_with_timestep(self, t: int, q: tp.Optional[int] = None) -> tp.List[int]:
114
+ return [step for step, coords in self.get_sequence_coords_with_timestep(t, q)]
115
+
116
+ def get_first_step_with_timesteps(self, t: int, q: tp.Optional[int] = None) -> tp.Optional[int]:
117
+ steps_with_timesteps = self.get_steps_with_timestep(t, q)
118
+ return steps_with_timesteps[0] if len(steps_with_timesteps) > 0 else None
119
+
120
+ def _build_pattern_sequence_scatter_indexes(self, timesteps: int, n_q: int, keep_only_valid_steps: bool,
121
+ device: tp.Union[torch.device, str] = 'cpu'):
122
+ """Build scatter indexes corresponding to the pattern, up to the provided sequence_steps.
123
+
124
+ Args:
125
+ timesteps (int): Maximum number of timesteps steps to consider.
126
+ keep_only_valid_steps (bool): Restrict the pattern layout to match only valid steps.
127
+ device (torch.device or str): Device for created tensors.
128
+ Returns:
129
+ indexes (torch.Tensor): Indexes corresponding to the sequence, of shape [K, S].
130
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes, of shape [K, S].
131
+ """
132
+ assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
133
+ assert timesteps <= self.timesteps, "invalid number of timesteps used to build the sequence from the pattern"
134
+ # use the proper layout based on whether we limit ourselves to valid steps only or not,
135
+ # note that using the valid_layout will result in a truncated sequence up to the valid steps
136
+ ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
137
+ # single item indexing being super slow with pytorch vs. numpy, so we use numpy here
138
+ indexes = torch.zeros(n_q, len(ref_layout), dtype=torch.long).numpy()
139
+ mask = torch.zeros(n_q, len(ref_layout), dtype=torch.bool).numpy()
140
+ # fill indexes with last sequence step value that will correspond to our special token
141
+ # the last value is n_q * timesteps as we have flattened z and append special token as the last token
142
+ # which will correspond to the index: n_q * timesteps
143
+ indexes[:] = n_q * timesteps
144
+ # iterate over the pattern and fill scattered indexes and mask
145
+ for s, sequence_coords in enumerate(ref_layout):
146
+ for coords in sequence_coords:
147
+ if coords.t < timesteps:
148
+ indexes[coords.q, s] = coords.t + coords.q * timesteps
149
+ mask[coords.q, s] = 1
150
+ indexes = torch.from_numpy(indexes).to(device)
151
+ mask = torch.from_numpy(mask).to(device)
152
+ return indexes, mask
153
+
154
+ def build_pattern_sequence(self, z: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
155
+ """Build sequence corresponding to the pattern from the input tensor z.
156
+ The sequence is built using up to sequence_steps if specified, and non-pattern
157
+ coordinates are filled with the special token.
158
+
159
+ Args:
160
+ z (torch.Tensor): Input tensor of multi-codebooks sequence, of shape [B, K, T].
161
+ special_token (int): Special token used to fill non-pattern coordinates in the new sequence.
162
+ keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
163
+ Steps that are beyond valid steps will be replaced by the special_token in that case.
164
+ Returns:
165
+ values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, S] with S
166
+ corresponding either to the sequence_steps if provided, otherwise to the length of the pattern.
167
+ indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, S].
168
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, S].
169
+ """
170
+ B, K, T = z.shape
171
+ indexes, mask = self._build_pattern_sequence_scatter_indexes(
172
+ T, K, keep_only_valid_steps=keep_only_valid_steps, device=str(z.device)
173
+ )
174
+ z = z.view(B, -1)
175
+ # we append the special token as the last index of our flattened z tensor
176
+ z = torch.cat([z, torch.zeros_like(z[:, :1]) + special_token], dim=1)
177
+ values = z[:, indexes.view(-1)]
178
+ values = values.view(B, K, indexes.shape[-1])
179
+ return values, indexes, mask
180
+
181
+ def _build_reverted_sequence_scatter_indexes(self, sequence_steps: int, n_q: int,
182
+ keep_only_valid_steps: bool = False,
183
+ is_model_output: bool = False,
184
+ device: tp.Union[torch.device, str] = 'cpu'):
185
+ """Builds scatter indexes required to retrieve the original multi-codebook sequence
186
+ from interleaving pattern.
187
+
188
+ Args:
189
+ sequence_steps (int): Sequence steps.
190
+ n_q (int): Number of codebooks.
191
+ keep_only_valid_steps (bool): Build a sequence from the pattern up to valid (= fully defined) steps.
192
+ Steps that are beyond valid steps will be replaced by the special_token in that case.
193
+ is_model_output (bool): Whether to keep the sequence item corresponding to initial special token or not.
194
+ device (torch.device or str): Device for created tensors.
195
+ Returns:
196
+ indexes (torch.Tensor): Indexes for reconstructing the output, of shape [K, T].
197
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
198
+ """
199
+ ref_layout = self.valid_layout if keep_only_valid_steps else self.layout
200
+ # TODO(jade): Do we want to further truncate to only valid timesteps here as well?
201
+ timesteps = self.timesteps
202
+ assert n_q == self.n_q, f"invalid number of codebooks for the sequence and the pattern: {n_q} != {self.n_q}"
203
+ assert sequence_steps <= len(ref_layout), \
204
+ f"sequence to revert is longer than the defined pattern: {sequence_steps} > {len(ref_layout)}"
205
+
206
+ # ensure we take the appropriate indexes to keep the model output from the first special token as well
207
+ if is_model_output and self.starts_with_special_token():
208
+ ref_layout = ref_layout[1:]
209
+
210
+ # single item indexing being super slow with pytorch vs. numpy, so we use numpy here
211
+ indexes = torch.zeros(n_q, timesteps, dtype=torch.long).numpy()
212
+ mask = torch.zeros(n_q, timesteps, dtype=torch.bool).numpy()
213
+ # fill indexes with last sequence step value that will correspond to our special token
214
+ indexes[:] = n_q * sequence_steps
215
+ for s, sequence_codes in enumerate(ref_layout):
216
+ if s < sequence_steps:
217
+ for code in sequence_codes:
218
+ if code.t < timesteps:
219
+ indexes[code.q, code.t] = s + code.q * sequence_steps
220
+ mask[code.q, code.t] = 1
221
+ indexes = torch.from_numpy(indexes).to(device)
222
+ mask = torch.from_numpy(mask).to(device)
223
+ return indexes, mask
224
+
225
+ def revert_pattern_sequence(self, s: torch.Tensor, special_token: int, keep_only_valid_steps: bool = False):
226
+ """Revert a sequence built from the pattern back to the original multi-codebook sequence without interleaving.
227
+ The sequence is reverted using up to timesteps if specified, and non-pattern coordinates
228
+ are filled with the special token.
229
+
230
+ Args:
231
+ s (torch.Tensor): Interleaved sequence tensor obtained from the pattern, of shape [B, K, S].
232
+ special_token (int or float): Special token used to fill non-pattern coordinates in the new sequence.
233
+ Returns:
234
+ values (torch.Tensor): Interleaved sequence matching the pattern, of shape [B, K, T] with T
235
+ corresponding either to the timesteps if provided, or the total timesteps in pattern otherwise.
236
+ indexes (torch.Tensor): Indexes corresponding to the interleaved sequence, of shape [K, T].
237
+ mask (torch.Tensor): Mask corresponding to indexes that matches valid indexes of shape [K, T].
238
+ """
239
+ B, K, S = s.shape
240
+ indexes, mask = self._build_reverted_sequence_scatter_indexes(
241
+ S, K, keep_only_valid_steps, is_model_output=False, device=str(s.device)
242
+ )
243
+ s = s.view(B, -1)
244
+ # we append the special token as the last index of our flattened z tensor
245
+ s = torch.cat([s, torch.zeros_like(s[:, :1]) + special_token], dim=1)
246
+ values = s[:, indexes.view(-1)]
247
+ values = values.view(B, K, indexes.shape[-1])
248
+ return values, indexes, mask
249
+
250
+ def revert_pattern_logits(self, logits: torch.Tensor, special_token: float, keep_only_valid_steps: bool = False):
251
+ """Revert model logits obtained on a sequence built from the pattern
252
+ back to a tensor matching the original sequence.
253
+
254
+ This method is similar to ``revert_pattern_sequence`` with the following specificities:
255
+ 1. It is designed to work with the extra cardinality dimension
256
+ 2. We return the logits for the first sequence item that matches the special_token and
257
+ which matching target in the original sequence is the first item of the sequence,
258
+ while we skip the last logits as there is no matching target
259
+ """
260
+ B, card, K, S = logits.shape
261
+ indexes, mask = self._build_reverted_sequence_scatter_indexes(
262
+ S, K, keep_only_valid_steps, is_model_output=True, device=logits.device
263
+ )
264
+ logits = logits.reshape(B, card, -1)
265
+ # we append the special token as the last index of our flattened z tensor
266
+ logits = torch.cat([logits, torch.zeros_like(logits[:, :, :1]) + special_token], dim=-1) # [B, card, K x S]
267
+ values = logits[:, :, indexes.view(-1)]
268
+ values = values.view(B, card, K, indexes.shape[-1])
269
+ return values, indexes, mask
270
+
271
+
272
+ class CodebooksPatternProvider(ABC):
273
+ """Abstraction around providing pattern for interleaving codebooks.
274
+
275
+ The CodebooksPatternProvider abstraction allows to implement various strategies to
276
+ define interleaving pattern of sequences composed of multiple codebooks. For a given
277
+ number of codebooks `n_q`, the pattern provider can generate a specified pattern
278
+ corresponding to a sequence of `T` timesteps with `n_q` parallel codebooks. This pattern
279
+ can be used to construct a new sequence from the original codes respecting the specified
280
+ pattern. The pattern is defined as a list of list of code coordinates, code coordinate
281
+ being a tuple with the original timestep and codebook to build the new sequence.
282
+ Note that all patterns must start with an empty list that is then used to insert a first
283
+ sequence step of special tokens in the newly generated sequence.
284
+
285
+ Args:
286
+ n_q (int): number of codebooks.
287
+ cached (bool): if True, patterns for a given length are cached. In general
288
+ that should be true for efficiency reason to avoid synchronization points.
289
+ """
290
+ def __init__(self, n_q: int, cached: bool = True):
291
+ assert n_q > 0
292
+ self.n_q = n_q
293
+ self.get_pattern = lru_cache(100)(self.get_pattern) # type: ignore
294
+
295
+ @abstractmethod
296
+ def get_pattern(self, timesteps: int) -> Pattern:
297
+ """Builds pattern with specific interleaving between codebooks.
298
+
299
+ Args:
300
+ timesteps (int): Total number of timesteps.
301
+ """
302
+ raise NotImplementedError()
303
+
304
+
305
+ class DelayedPatternProvider(CodebooksPatternProvider):
306
+ """Provider for delayed pattern across delayed codebooks.
307
+ Codebooks are delayed in the sequence and sequence steps will contain codebooks
308
+ from different timesteps.
309
+
310
+ Example:
311
+ Taking timesteps=4 and n_q=3, delays=None, the multi-codebook sequence:
312
+ [[1, 2, 3, 4],
313
+ [1, 2, 3, 4],
314
+ [1, 2, 3, 4]]
315
+ The resulting sequence obtained from the returned pattern is:
316
+ [[S, 1, 2, 3, 4],
317
+ [S, S, 1, 2, 3],
318
+ [S, S, S, 1, 2]]
319
+ (with S being a special token)
320
+
321
+ Args:
322
+ n_q (int): Number of codebooks.
323
+ delays (list of int, optional): Delay for each of the codebooks.
324
+ If delays not defined, each codebook is delayed by 1 compared to the previous one.
325
+ flatten_first (int): Flatten the first N timesteps.
326
+ empty_initial (int): Prepend with N empty list of coordinates.
327
+ """
328
+ def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None,
329
+ flatten_first: int = 0, empty_initial: int = 0):
330
+ super().__init__(n_q)
331
+ if delays is None:
332
+ delays = list(range(n_q))
333
+ self.delays = delays
334
+ self.flatten_first = flatten_first
335
+ self.empty_initial = empty_initial
336
+ assert len(self.delays) == self.n_q
337
+ assert sorted(self.delays) == self.delays
338
+
339
+ def get_pattern(self, timesteps: int) -> Pattern:
340
+ omit_special_token = self.empty_initial < 0
341
+ out: PatternLayout = [] if omit_special_token else [[]]
342
+ max_delay = max(self.delays)
343
+ if self.empty_initial:
344
+ out += [[] for _ in range(self.empty_initial)]
345
+ if self.flatten_first:
346
+ for t in range(min(timesteps, self.flatten_first)):
347
+ for q in range(self.n_q):
348
+ out.append([LayoutCoord(t, q)])
349
+ for t in range(self.flatten_first, timesteps + max_delay):
350
+ v = []
351
+ for q, delay in enumerate(self.delays):
352
+ t_for_q = t - delay
353
+ if t_for_q >= self.flatten_first:
354
+ v.append(LayoutCoord(t_for_q, q))
355
+ out.append(v)
356
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
357
+
358
+
359
+ class ParallelPatternProvider(DelayedPatternProvider):
360
+ """Provider for parallel pattern across codebooks.
361
+ This pattern provider is a special case of the delayed pattern with actually no delay,
362
+ hence delays=repeat(0, n_q).
363
+
364
+ Args:
365
+ n_q (int): Number of codebooks.
366
+ empty_initial (int): Prepend with N empty list of coordinates.
367
+ """
368
+ def __init__(self, n_q: int, empty_initial: int = 0):
369
+ super().__init__(n_q, [0] * n_q, empty_initial=empty_initial)
370
+
371
+
372
+ class UnrolledPatternProvider(CodebooksPatternProvider):
373
+ """Provider for unrolling codebooks pattern.
374
+ This pattern provider enables to represent the codebook flattened completely or only to some extend
375
+ while also specifying a given delay between the flattened codebooks representation, allowing to
376
+ unroll the codebooks in the sequence.
377
+
378
+ Example:
379
+ 1. Flattening of the codebooks.
380
+ By default, the pattern provider will fully flatten the codebooks such as flattening=range(n_q),
381
+ taking n_q = 3 and timesteps = 4:
382
+ [[1, 2, 3, 4],
383
+ [1, 2, 3, 4],
384
+ [1, 2, 3, 4]]
385
+ will result into:
386
+ [[S, S, 1, S, S, 2, S, S, 3, S, S, 4],
387
+ [S, 1, S, S, 2, S, S, 3, S, S, 4, S],
388
+ [1, S, S, 2, S, S, 3, S, S, 4, S, S]]
389
+ 2. Partial flattening of the codebooks. The ``flattening`` parameter allows to specify the inner step
390
+ for each of the codebook, allowing to define which codebook to flatten (or keep in parallel), for example
391
+ taking n_q = 3, timesteps = 4 and flattening = [0, 1, 1]:
392
+ [[1, 2, 3, 4],
393
+ [1, 2, 3, 4],
394
+ [1, 2, 3, 4]]
395
+ will result into:
396
+ [[S, 1, S, S, 2, S, S, 3, S, S, 4, S],
397
+ [S, 1, S, S, 2, S, S, 3, S, S, 4, S],
398
+ [1, S, S, 2, S, S, 3, S, S, 4, S, S]]
399
+ 3. Flattening with delay. The ``delay`` parameter allows to further unroll the sequence of codebooks
400
+ allowing to specify the delay per codebook. Note that the delay between codebooks flattened to the
401
+ same inner timestep should be coherent. For example, taking n_q = 3, timesteps = 4, flattening = [0, 1, 1]
402
+ and delays = [0, 3, 3]:
403
+ [[1, 2, 3, 4],
404
+ [1, 2, 3, 4],
405
+ [1, 2, 3, 4]]
406
+ will result into:
407
+ [[S, S, S, 1, S, 2, S, 3, S, 4],
408
+ [S, S, S, 1, S, 2, S, 3, S, 4],
409
+ [1, 2, 3, S, 4, S, 5, S, 6, S]]
410
+
411
+ Args:
412
+ n_q (int): Number of codebooks.
413
+ flattening (list of int, optional): Flattening schema over the codebooks. If not defined,
414
+ the codebooks will be flattened to 1 codebook per step, meaning that the sequence will
415
+ have n_q extra steps for each timestep.
416
+ delays (list of int, optional): Delay for each of the codebooks. If not defined,
417
+ no delay is added and therefore will default to [0] * ``n_q``.
418
+ Note that two codebooks that will be flattened to the same inner step
419
+ should have the same delay, otherwise the pattern is considered as invalid.
420
+ """
421
+ FlattenedCodebook = namedtuple('FlattenedCodebook', ['codebooks', 'delay'])
422
+
423
+ def __init__(self, n_q: int, flattening: tp.Optional[tp.List[int]] = None,
424
+ delays: tp.Optional[tp.List[int]] = None):
425
+ super().__init__(n_q)
426
+ if flattening is None:
427
+ flattening = list(range(n_q))
428
+ if delays is None:
429
+ delays = [0] * n_q
430
+ assert len(flattening) == n_q
431
+ assert len(delays) == n_q
432
+ assert sorted(flattening) == flattening
433
+ assert sorted(delays) == delays
434
+ self._flattened_codebooks = self._build_flattened_codebooks(delays, flattening)
435
+ self.max_delay = max(delays)
436
+
437
+ def _build_flattened_codebooks(self, delays: tp.List[int], flattening: tp.List[int]):
438
+ """Build a flattened codebooks representation as a dictionary of inner step
439
+ and the actual codebook indices corresponding to the flattened codebook. For convenience, we
440
+ also store the delay associated to the flattened codebook to avoid maintaining an extra mapping.
441
+ """
442
+ flattened_codebooks: dict = {}
443
+ for q, (inner_step, delay) in enumerate(zip(flattening, delays)):
444
+ if inner_step not in flattened_codebooks:
445
+ flat_codebook = UnrolledPatternProvider.FlattenedCodebook(codebooks=[q], delay=delay)
446
+ else:
447
+ flat_codebook = flattened_codebooks[inner_step]
448
+ assert flat_codebook.delay == delay, (
449
+ "Delay and flattening between codebooks is inconsistent: ",
450
+ "two codebooks flattened to the same position should have the same delay."
451
+ )
452
+ flat_codebook.codebooks.append(q)
453
+ flattened_codebooks[inner_step] = flat_codebook
454
+ return flattened_codebooks
455
+
456
+ @property
457
+ def _num_inner_steps(self):
458
+ """Number of inner steps to unroll between timesteps in order to flatten the codebooks.
459
+ """
460
+ return max([inner_step for inner_step in self._flattened_codebooks.keys()]) + 1
461
+
462
+ def num_virtual_steps(self, timesteps: int) -> int:
463
+ return timesteps * self._num_inner_steps + 1
464
+
465
+ def get_pattern(self, timesteps: int) -> Pattern:
466
+ """Builds pattern for delay across codebooks.
467
+
468
+ Args:
469
+ timesteps (int): Total number of timesteps.
470
+ """
471
+ # the PatternLayout is built as a tuple of sequence position and list of coordinates
472
+ # so that it can be reordered properly given the required delay between codebooks of given timesteps
473
+ indexed_out: list = [(-1, [])]
474
+ max_timesteps = timesteps + self.max_delay
475
+ for t in range(max_timesteps):
476
+ # for each timestep, we unroll the flattened codebooks,
477
+ # emitting the sequence step with the corresponding delay
478
+ for step in range(self._num_inner_steps):
479
+ if step in self._flattened_codebooks:
480
+ # we have codebooks at this virtual step to emit
481
+ step_codebooks = self._flattened_codebooks[step]
482
+ t_for_q = t + step_codebooks.delay
483
+ coords = [LayoutCoord(t, q) for q in step_codebooks.codebooks]
484
+ if t_for_q < max_timesteps and t < max_timesteps:
485
+ indexed_out.append((t_for_q, coords))
486
+ else:
487
+ # there is no codebook in this virtual step so we emit an empty list
488
+ indexed_out.append((t, []))
489
+ out = [coords for _, coords in sorted(indexed_out)]
490
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
491
+
492
+
493
+ class CoarseFirstPattern(CodebooksPatternProvider):
494
+ """First generates all the codebooks #1 (e.g. coarser), then the remaining ones,
495
+ potentially with delays.
496
+
497
+ ..Warning:: You must always generate the full training duration at test time, for instance,
498
+ 30 seconds, as otherwise, the fine codebooks will start being generated in an unexpected
499
+ location. This is due to the non causality of the remaining codebooks with respect to
500
+ the first ones.
501
+
502
+ Args:
503
+ n_q (int): Number of codebooks.
504
+ delays (list of int, optional): Delay for each of the codebooks.
505
+ If delays not defined, each codebook is delayed by 1 compared to the previous one.
506
+ """
507
+ def __init__(self, n_q: int, delays: tp.Optional[tp.List[int]] = None):
508
+ super().__init__(n_q)
509
+ if delays is None:
510
+ delays = [0] * (n_q - 1)
511
+ self.delays = delays
512
+ assert len(self.delays) == self.n_q - 1
513
+ assert sorted(self.delays) == self.delays
514
+
515
+ def get_pattern(self, timesteps: int) -> Pattern:
516
+ out: PatternLayout = [[]]
517
+ for t in range(timesteps):
518
+ out.append([LayoutCoord(t, 0)])
519
+ max_delay = max(self.delays)
520
+ for t in range(timesteps + max_delay):
521
+ v = []
522
+ for q, delay in enumerate(self.delays):
523
+ t_for_q = t - delay
524
+ if t_for_q >= 0:
525
+ v.append(LayoutCoord(t_for_q, q + 1))
526
+ out.append(v)
527
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
528
+
529
+
530
+ class MusicLMPattern(CodebooksPatternProvider):
531
+ """Almost MusicLM style pattern. This is equivalent to full flattening
532
+ but in a different order.
533
+
534
+ Args:
535
+ n_q (int): Number of codebooks.
536
+ group_by (int): Number of codebooks to group together.
537
+ """
538
+ def __init__(self, n_q: int, group_by: int = 2):
539
+ super().__init__(n_q)
540
+ self.group_by = group_by
541
+
542
+ def get_pattern(self, timesteps: int) -> Pattern:
543
+ out: PatternLayout = [[]]
544
+ for offset in range(0, self.n_q, self.group_by):
545
+ for t in range(timesteps):
546
+ for q in range(offset, offset + self.group_by):
547
+ out.append([LayoutCoord(t, q)])
548
+ return Pattern(out, n_q=self.n_q, timesteps=timesteps)
models/modules/conv.py ADDED
@@ -0,0 +1,346 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Convolutional layers wrappers and utilities."""
7
+
8
+ import math
9
+ import typing as tp
10
+ import warnings
11
+
12
+ import torch
13
+ from torch import nn
14
+ from torch.nn import functional as F
15
+ from torch.nn.utils import spectral_norm
16
+ from torch.nn.utils.parametrizations import weight_norm
17
+ # from torch.nn.utils import weight_norm
18
+
19
+ from .norm import ConvLayerNorm
20
+
21
+ CONV_NORMALIZATIONS = frozenset([
22
+ 'none', 'weight_norm', 'spectral_norm', 'time_layer_norm', 'layer_norm',
23
+ 'time_group_norm'
24
+ ])
25
+
26
+
27
+ # self.conv = apply_parametrization_norm(nn.Conv1d(*args, **kwargs), norm)
28
+ def apply_parametrization_norm(module: nn.Module,
29
+ norm: str = 'none') -> nn.Module:
30
+ assert norm in CONV_NORMALIZATIONS
31
+ if norm == 'weight_norm':
32
+ return weight_norm(module)
33
+ elif norm == 'spectral_norm':
34
+ return spectral_norm(module)
35
+ else:
36
+ # We already check was in CONV_NORMALIZATION, so any other choice
37
+ # doesn't need reparametrization.
38
+ return module
39
+
40
+
41
+ def get_norm_module(module: nn.Module,
42
+ causal: bool = False,
43
+ norm: str = 'none',
44
+ **norm_kwargs) -> nn.Module:
45
+ """Return the proper normalization module. If causal is True, this will ensure the returned
46
+ module is causal, or return an error if the normalization doesn't support causal evaluation.
47
+ """
48
+ assert norm in CONV_NORMALIZATIONS
49
+ if norm == 'layer_norm':
50
+ assert isinstance(module, nn.modules.conv._ConvNd)
51
+ return ConvLayerNorm(module.out_channels, **norm_kwargs)
52
+ elif norm == 'time_group_norm':
53
+ if causal:
54
+ raise ValueError("GroupNorm doesn't support causal evaluation.")
55
+ assert isinstance(module, nn.modules.conv._ConvNd)
56
+ return nn.GroupNorm(1, module.out_channels, **norm_kwargs)
57
+ else:
58
+ return nn.Identity()
59
+
60
+
61
+ def get_extra_padding_for_conv1d(x: torch.Tensor,
62
+ kernel_size: int,
63
+ stride: int,
64
+ padding_total: int = 0) -> int:
65
+ """See `pad_for_conv1d`.
66
+ """
67
+ length = x.shape[-1]
68
+ n_frames = (length - kernel_size + padding_total) / stride + 1
69
+ ideal_length = (math.ceil(n_frames) - 1) * stride + (kernel_size -
70
+ padding_total)
71
+ return ideal_length - length
72
+
73
+
74
+ def pad_for_conv1d(x: torch.Tensor,
75
+ kernel_size: int,
76
+ stride: int,
77
+ padding_total: int = 0):
78
+ """Pad for a convolution to make sure that the last window is full.
79
+ Extra padding is added at the end. This is required to ensure that we can rebuild
80
+ an output of the same length, as otherwise, even with padding, some time steps
81
+ might get removed.
82
+ For instance, with total padding = 4, kernel size = 4, stride = 2:
83
+ 0 0 1 2 3 4 5 0 0 # (0s are padding)
84
+ 1 2 3 # (output frames of a convolution, last 0 is never used)
85
+ 0 0 1 2 3 4 5 0 # (output of tr. conv., but pos. 5 is going to get removed as padding)
86
+ 1 2 3 4 # once you removed padding, we are missing one time step !
87
+ """
88
+ extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride,
89
+ padding_total)
90
+ return F.pad(x, (0, extra_padding))
91
+
92
+
93
+ def pad1d(x: torch.Tensor,
94
+ paddings: tp.Tuple[int, int],
95
+ mode: str = 'zero',
96
+ value: float = 0.):
97
+ """Tiny wrapper around F.pad, just to allow for reflect padding on small input.
98
+ If this is the case, we insert extra 0 padding to the right before the reflection happen.
99
+ """
100
+ length = x.shape[-1]
101
+ padding_left, padding_right = paddings
102
+ assert padding_left >= 0 and padding_right >= 0, (padding_left,
103
+ padding_right)
104
+ if mode == 'reflect':
105
+ max_pad = max(padding_left, padding_right)
106
+ extra_pad = 0
107
+ if length <= max_pad:
108
+ extra_pad = max_pad - length + 1
109
+ x = F.pad(x, (0, extra_pad))
110
+ padded = F.pad(x, paddings, mode, value)
111
+ end = padded.shape[-1] - extra_pad
112
+ return padded[..., :end]
113
+ else:
114
+ return F.pad(x, paddings, mode, value)
115
+
116
+
117
+ def unpad1d(x: torch.Tensor, paddings: tp.Tuple[int, int]):
118
+ """Remove padding from x, handling properly zero padding. Only for 1d!"""
119
+ padding_left, padding_right = paddings
120
+ assert padding_left >= 0 and padding_right >= 0, (padding_left,
121
+ padding_right)
122
+ assert (padding_left + padding_right) <= x.shape[-1]
123
+ end = x.shape[-1] - padding_right
124
+ return x[..., padding_left:end]
125
+
126
+
127
+ class NormConv1d(nn.Module):
128
+ """Wrapper around Conv1d and normalization applied to this conv
129
+ to provide a uniform interface across normalization approaches.
130
+ """
131
+
132
+ def __init__(self,
133
+ *args,
134
+ causal: bool = False,
135
+ norm: str = 'none',
136
+ norm_kwargs: tp.Dict[str, tp.Any] = {},
137
+ **kwargs):
138
+ super().__init__()
139
+ self.conv = apply_parametrization_norm(nn.Conv1d(*args, **kwargs), norm)
140
+ self.norm = get_norm_module(self.conv, causal, norm, **norm_kwargs)
141
+ self.norm_type = norm
142
+
143
+ def forward(self, x):
144
+ #print("inputNormConv1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)), "norm:", self.norm_type, "conv:", self.conv)
145
+ in_x = x.clone()
146
+ x = self.conv(x)
147
+
148
+ #print("betweenNormConv1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
149
+ # if torch.isnan(torch.sum(x)).any():
150
+ # print("got nan", x.shape, self.conv)
151
+ # raise ValueError("wtf")
152
+ x = self.norm(x)
153
+ #print("outputNormConv1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
154
+ return x
155
+
156
+
157
+ class NormConv2d(nn.Module):
158
+ """Wrapper around Conv2d and normalization applied to this conv
159
+ to provide a uniform interface across normalization approaches.
160
+ """
161
+
162
+ def __init__(self,
163
+ *args,
164
+ norm: str = 'none',
165
+ norm_kwargs: tp.Dict[str, tp.Any] = {},
166
+ **kwargs):
167
+ super().__init__()
168
+ self.conv = apply_parametrization_norm(nn.Conv2d(*args, **kwargs), norm)
169
+ self.norm = get_norm_module(self.conv,
170
+ causal=False,
171
+ norm=norm,
172
+ **norm_kwargs)
173
+ self.norm_type = norm
174
+
175
+ def forward(self, x):
176
+ #print("inputNormConv2d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
177
+ x = self.conv(x)
178
+ x = self.norm(x)
179
+ #print("outputNormConv2d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
180
+ return x
181
+
182
+
183
+ class NormConvTranspose1d(nn.Module):
184
+ """Wrapper around ConvTranspose1d and normalization applied to this conv
185
+ to provide a uniform interface across normalization approaches.
186
+ """
187
+
188
+ def __init__(self,
189
+ *args,
190
+ causal: bool = False,
191
+ norm: str = 'none',
192
+ norm_kwargs: tp.Dict[str, tp.Any] = {},
193
+ **kwargs):
194
+ super().__init__()
195
+ self.convtr = apply_parametrization_norm(
196
+ nn.ConvTranspose1d(*args, **kwargs), norm)
197
+ self.norm = get_norm_module(self.convtr, causal, norm, **norm_kwargs)
198
+ self.norm_type = norm
199
+
200
+ def forward(self, x):
201
+ #print("inputNormConvTranspose1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
202
+ x = self.convtr(x)
203
+ x = self.norm(x)
204
+ #print("outputNormConvTranspose1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
205
+ return x
206
+
207
+
208
+ class NormConvTranspose2d(nn.Module):
209
+ """Wrapper around ConvTranspose2d and normalization applied to this conv
210
+ to provide a uniform interface across normalization approaches.
211
+ """
212
+
213
+ def __init__(self,
214
+ *args,
215
+ norm: str = 'none',
216
+ norm_kwargs: tp.Dict[str, tp.Any] = {},
217
+ **kwargs):
218
+ super().__init__()
219
+ self.convtr = apply_parametrization_norm(
220
+ nn.ConvTranspose2d(*args, **kwargs), norm)
221
+ self.norm = get_norm_module(self.convtr,
222
+ causal=False,
223
+ norm=norm,
224
+ **norm_kwargs)
225
+
226
+ def forward(self, x):
227
+ #print("inputNormConvTranspose2d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
228
+ x = self.convtr(x)
229
+ x = self.norm(x)
230
+ #print("outputNormConvTranspose2d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
231
+ return x
232
+
233
+
234
+ class SConv1d(nn.Module):
235
+ """Conv1d with some builtin handling of asymmetric or causal padding
236
+ and normalization.
237
+ """
238
+
239
+ def __init__(self,
240
+ in_channels: int,
241
+ out_channels: int,
242
+ kernel_size: int,
243
+ stride: int = 1,
244
+ dilation: int = 1,
245
+ groups: int = 1,
246
+ bias: bool = True,
247
+ causal: bool = False,
248
+ norm: str = 'none',
249
+ norm_kwargs: tp.Dict[str, tp.Any] = {},
250
+ pad_mode: str = 'reflect'):
251
+ super().__init__()
252
+ # warn user on unusual setup between dilation and stride
253
+ if stride > 1 and dilation > 1:
254
+ warnings.warn(
255
+ 'SConv1d has been initialized with stride > 1 and dilation > 1'
256
+ f' (kernel_size={kernel_size} stride={stride}, dilation={dilation}).'
257
+ )
258
+ self.conv = NormConv1d(in_channels,
259
+ out_channels,
260
+ kernel_size,
261
+ stride,
262
+ dilation=dilation,
263
+ groups=groups,
264
+ bias=bias,
265
+ causal=causal,
266
+ norm=norm,
267
+ norm_kwargs=norm_kwargs)
268
+ self.causal = causal
269
+ self.pad_mode = pad_mode
270
+
271
+ def forward(self, x):
272
+ #print("inputSConv1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
273
+ B, C, T = x.shape
274
+ kernel_size = self.conv.conv.kernel_size[0]
275
+ stride = self.conv.conv.stride[0]
276
+ dilation = self.conv.conv.dilation[0]
277
+ padding_total = (kernel_size - 1) * dilation - (stride - 1)
278
+ extra_padding = get_extra_padding_for_conv1d(x, kernel_size, stride,
279
+ padding_total)
280
+ if self.causal:
281
+ # Left padding for causal
282
+ x = pad1d(x, (padding_total, extra_padding), mode=self.pad_mode)
283
+ else:
284
+ # Asymmetric padding required for odd strides
285
+ padding_right = padding_total // 2
286
+ padding_left = padding_total - padding_right
287
+ x = pad1d(x, (padding_left, padding_right + extra_padding),
288
+ mode=self.pad_mode)
289
+ x = self.conv(x)
290
+ #print("outputSConv1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
291
+ return x
292
+
293
+
294
+ class SConvTranspose1d(nn.Module):
295
+ """ConvTranspose1d with some builtin handling of asymmetric or causal padding
296
+ and normalization.
297
+ """
298
+
299
+ def __init__(self,
300
+ in_channels: int,
301
+ out_channels: int,
302
+ kernel_size: int,
303
+ stride: int = 1,
304
+ causal: bool = False,
305
+ norm: str = 'none',
306
+ trim_right_ratio: float = 1.,
307
+ norm_kwargs: tp.Dict[str, tp.Any] = {}):
308
+ super().__init__()
309
+ self.convtr = NormConvTranspose1d(in_channels,
310
+ out_channels,
311
+ kernel_size,
312
+ stride,
313
+ causal=causal,
314
+ norm=norm,
315
+ norm_kwargs=norm_kwargs)
316
+ self.causal = causal
317
+ self.trim_right_ratio = trim_right_ratio
318
+ assert self.causal or self.trim_right_ratio == 1., \
319
+ "`trim_right_ratio` != 1.0 only makes sense for causal convolutions"
320
+ assert self.trim_right_ratio >= 0. and self.trim_right_ratio <= 1.
321
+
322
+ def forward(self, x):
323
+ #print("inputSConvTranspose1d:", x.shape, torch.sum(x), torch.sum(torch.abs(x)))
324
+ kernel_size = self.convtr.convtr.kernel_size[0]
325
+ stride = self.convtr.convtr.stride[0]
326
+ padding_total = kernel_size - stride
327
+
328
+ y = self.convtr(x)
329
+
330
+ # We will only trim fixed padding. Extra padding from `pad_for_conv1d` would be
331
+ # removed at the very end, when keeping only the right length for the output,
332
+ # as removing it here would require also passing the length at the matching layer
333
+ # in the encoder.
334
+ if self.causal:
335
+ # Trim the padding on the right according to the specified ratio
336
+ # if trim_right_ratio = 1.0, trim everything from right
337
+ padding_right = math.ceil(padding_total * self.trim_right_ratio)
338
+ padding_left = padding_total - padding_right
339
+ y = unpad1d(y, (padding_left, padding_right))
340
+ else:
341
+ # Asymmetric padding required for odd strides
342
+ padding_right = padding_total // 2
343
+ padding_left = padding_total - padding_right
344
+ y = unpad1d(y, (padding_left, padding_right))
345
+ #print("outputSConvTranspose1d:", y.shape, torch.sum(y), torch.sum(torch.abs(y)))
346
+ return y
models/modules/decoder.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from abc import ABC, abstractmethod
2
+ from torch import nn, Tensor
3
+ import torch
4
+ import x_transformers as xt
5
+ from typing import Optional
6
+
7
+ import config as cfg
8
+
9
+
10
+ class ResidualTokenEmbedding(nn.Module):
11
+
12
+ def __init__(self,
13
+ dim: int,
14
+ num_tokens: int,
15
+ n_layers: int,
16
+ padding_token: Optional[int] = None):
17
+ super().__init__()
18
+ self.padding_token: Optional[int] = padding_token
19
+ self.emb = nn.ModuleList([
20
+ nn.Embedding(num_tokens, dim, padding_idx=self.padding_token)
21
+ for _ in range(n_layers)
22
+ ])
23
+
24
+ for layer in self.emb:
25
+ nn.init.kaiming_normal_(layer.weight)
26
+
27
+ def forward(self, x):
28
+ n_res_layers = x.shape[1]
29
+ assert n_res_layers == len(self.emb)
30
+ token_emb: Tensor = sum( # type: ignore
31
+ [self.emb[i](x[:, i]) for i in range(n_res_layers)])
32
+ return token_emb
33
+
34
+
35
+ def create_sin_embedding(
36
+ positions: torch.Tensor,
37
+ dim: int,
38
+ max_period: float = 10000,
39
+ dtype: torch.dtype = torch.float32,
40
+ ) -> torch.Tensor:
41
+ """Create sinusoidal positional embedding, with shape `[B, T, C]`.
42
+
43
+ Args:
44
+ positions (torch.Tensor): LongTensor of positions.
45
+ dim (int): Dimension of the embedding.
46
+ max_period (float): Maximum period of the cosine/sine functions.
47
+ dtype (torch.dtype or str): dtype to use to generate the embedding.
48
+ Returns:
49
+ torch.Tensor: Sinusoidal positional embedding.
50
+ """
51
+ # We aim for BTC format
52
+ assert dim % 2 == 0
53
+ half_dim = dim // 2
54
+ positions = positions.to(dtype)
55
+ adim = torch.arange(half_dim, device=positions.device,
56
+ dtype=dtype).view(1, 1, -1)
57
+ max_period_tensor = torch.full([],
58
+ max_period,
59
+ device=positions.device,
60
+ dtype=dtype) # avoid sync point
61
+ phase = positions / (max_period_tensor**(adim / (half_dim - 1)))
62
+ return torch.cat([torch.cos(phase), torch.sin(phase)], dim=-1)
63
+
64
+
65
+ class ResidualSinusoidalEmbedding(nn.Module):
66
+
67
+ def __init__(self, dim, theta=10000):
68
+ super().__init__()
69
+ assert dim % 2 == 0
70
+ self.scale = 1
71
+ self.theta = theta
72
+ self.dim = dim
73
+
74
+ def forward(self, x, pos=None, seq_start_pos=None):
75
+ B, K, T = x.shape
76
+ positions = (pos if pos is not None else torch.arange(
77
+ T, device=x.device).view(1, -1, 1))
78
+ pos_emb = create_sin_embedding(positions,
79
+ self.dim,
80
+ max_period=self.theta,
81
+ dtype=x.dtype)
82
+ return pos_emb * self.scale
83
+
84
+
85
+ class ResidualOutputProj(nn.Module):
86
+
87
+ def __init__(self, input_dim: int, output_dim: int, n_layers: int,
88
+ use_bias: bool):
89
+ super().__init__()
90
+ self.linears = nn.ModuleList([
91
+ nn.Linear(input_dim, output_dim, use_bias) for _ in range(n_layers)
92
+ ])
93
+
94
+ def forward(self, x):
95
+ return torch.stack([layer(x) for layer in self.linears], dim=1)
96
+
97
+
98
+ class TransformerDecoder(ABC, nn.Module):
99
+
100
+ @abstractmethod
101
+ def forward(x, mask, context, context_mask, prepend_data, prepend_mask,
102
+ sum_data) -> Tensor:
103
+ ...
104
+
105
+
106
+ class XTransformerDecoder(TransformerDecoder):
107
+
108
+ def __init__(
109
+ self,
110
+ num_tokens: int,
111
+ max_seq_len: int,
112
+ use_abs_pos_emb: bool,
113
+ scaled_sinu_pos_emb: bool,
114
+ dim: int,
115
+ depth: int,
116
+ heads: int,
117
+ attn_dim_head: int,
118
+ attn_flash: bool,
119
+ ff_no_bias: bool,
120
+ cross_attend: bool,
121
+ ):
122
+
123
+ self.decoder: xt.TransformerWrapper = xt.TransformerWrapper(
124
+ num_tokens=self.input_card,
125
+ max_seq_len=500,
126
+ use_abs_pos_emb=True,
127
+ scaled_sinu_pos_emb=True,
128
+ attn_layers=xt.Decoder(
129
+ dim=self.dim,
130
+ depth=self.n_layers,
131
+ heads=self.n_heads,
132
+ attn_dim_head=64,
133
+ attn_flash=True,
134
+ ff_no_bias=True,
135
+ cross_attend=self.cross_attend,
136
+ ),
137
+ )
138
+
139
+ self.decoder.token_emb = ResidualTokenEmbedding( # type: ignore
140
+ self.dim,
141
+ self.input_card,
142
+ self.n_q,
143
+ self.padding_token,
144
+ )
145
+ self.decoder.pos_emb = ResidualSinusoidalEmbedding( # type: ignore
146
+ dim=self.dim)
147
+ self.decoder.to_logits = ResidualOutputProj(self.dim, self.card,
148
+ self.n_q, False)
149
+
150
+ # TODO: this is horrendous, gotta find a fix
151
+ if not self.cross_attend:
152
+ self.nullwav_embeds = torch.load(cfg.weights_dir() /
153
+ "nullwav_embeds.pt")[0]
154
+ else:
155
+ self.nullwav_embeds = None
models/modules/lstm.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """LSTM layers module."""
8
+
9
+ from torch import nn
10
+
11
+
12
+ class SLSTM(nn.Module):
13
+ """
14
+ LSTM without worrying about the hidden state, nor the layout of the data.
15
+ Expects input as convolutional layout.
16
+ """
17
+ def __init__(self, dimension: int, num_layers: int = 2, skip: bool = True):
18
+ super().__init__()
19
+ self.skip = skip
20
+ self.lstm = nn.LSTM(dimension, dimension, num_layers)
21
+
22
+ def forward(self, x):
23
+ x = x.permute(2, 0, 1)
24
+ y, _ = self.lstm(x)
25
+ if self.skip:
26
+ y = y + x
27
+ y = y.permute(1, 2, 0)
28
+ return y
models/modules/norm.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ """Normalization modules."""
8
+
9
+ import typing as tp
10
+
11
+ import einops
12
+ import torch
13
+ from torch import nn
14
+
15
+
16
+ class ConvLayerNorm(nn.LayerNorm):
17
+ """
18
+ Convolution-friendly LayerNorm that moves channels to last dimensions
19
+ before running the normalization and moves them back to original position right after.
20
+ """
21
+ def __init__(self, normalized_shape: tp.Union[int, tp.List[int], torch.Size], **kwargs):
22
+ super().__init__(normalized_shape, **kwargs)
23
+
24
+ def forward(self, x):
25
+ x = einops.rearrange(x, 'b ... t -> b t ...')
26
+ x = super().forward(x)
27
+ x = einops.rearrange(x, 'b t ... -> b ... t')
28
+ return
models/modules/rope.py ADDED
@@ -0,0 +1,125 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ import typing as tp
8
+
9
+ from torch import nn
10
+ import torch
11
+
12
+
13
+ class XPos(nn.Module):
14
+ """Length-extrapolatable positional embedding (xPos) from [Sun et al 2022](https://arxiv.org/abs/2212.10554v1).
15
+ This applies an exponential decay to the RoPE rotation matrix.
16
+
17
+ Args:
18
+ dim (int): Embedding dimension.
19
+ smoothing (float): Smoothing factor applied to the decay rates.
20
+ base_scale (int): Base decay rate, given in terms of scaling time.
21
+ device (torch.device, optional): Device on which to initialize the module.
22
+ dtype (torch.dtype): dtype to use to generate the embedding.
23
+ """
24
+ def __init__(self, dim: int, smoothing: float = 0.4, base_scale: int = 512,
25
+ device=None, dtype: torch.dtype = torch.float32):
26
+ super().__init__()
27
+ assert dim % 2 == 0
28
+ assert dtype in [torch.float64, torch.float32]
29
+ self.dtype = dtype
30
+ self.base_scale = base_scale
31
+
32
+ half_dim = dim // 2
33
+ adim = torch.arange(half_dim, device=device, dtype=dtype)
34
+ decay_rates = (adim / half_dim + smoothing) / (1.0 + smoothing)
35
+ self.register_buffer("decay_rates", decay_rates)
36
+ self.decay: tp.Optional[torch.Tensor] = None
37
+
38
+ def get_decay(self, start: int, end: int):
39
+ """Create complex decay tensor, cache values for fast computation."""
40
+ if self.decay is None or end > self.decay.shape[0]:
41
+ assert isinstance(self.decay_rates, torch.Tensor) # Satisfy type checker.
42
+ idx = torch.arange(end, device=self.decay_rates.device, dtype=self.dtype)
43
+ power = idx / self.base_scale
44
+ scale = self.decay_rates ** power.unsqueeze(-1)
45
+ self.decay = torch.polar(scale, torch.zeros_like(scale))
46
+ return self.decay[start:end] # [T, C/2]
47
+
48
+
49
+ class RotaryEmbedding(nn.Module):
50
+ """Rotary positional embedding (RoPE) from [Su et al 2022](https://arxiv.org/abs/2104.09864).
51
+
52
+ Args:
53
+ dim (int): Embedding dimension (twice the number of frequencies).
54
+ max_period (float): Maximum period of the rotation frequencies.
55
+ xpos (bool): Use xPos, applies an exponential decay to rotation matrix.
56
+ scale (float): Scale of positional embedding, set to 0 to deactivate.
57
+ device (torch.device, optional): Device on which to initialize the module.
58
+ dtype (torch.dtype): dtype to use to generate the embedding.
59
+ """
60
+ def __init__(self, dim: int, max_period: float = 10000.0, xpos: bool = False,
61
+ scale: float = 1.0, device=None, dtype: torch.dtype = torch.float32):
62
+ super().__init__()
63
+ assert dim % 2 == 0
64
+ self.scale = scale
65
+ assert dtype in [torch.float64, torch.float32]
66
+ self.dtype = dtype
67
+
68
+ adim = torch.arange(0, dim, 2, device=device, dtype=dtype)[: (dim // 2)]
69
+ frequencies = 1.0 / (max_period ** (adim / dim))
70
+ self.register_buffer("frequencies", frequencies)
71
+ self.rotation: tp.Optional[torch.Tensor] = None
72
+
73
+ self.xpos = XPos(dim, device=device, dtype=dtype) if xpos else None
74
+
75
+ def get_rotation(self, start: int, end: int):
76
+ """Create complex rotation tensor, cache values for fast computation."""
77
+ if self.rotation is None or end > self.rotation.shape[0]:
78
+ assert isinstance(self.frequencies, torch.Tensor) # Satisfy type checker.
79
+ idx = torch.arange(end, device=self.frequencies.device, dtype=self.dtype)
80
+ angles = torch.outer(idx, self.frequencies)
81
+ self.rotation = torch.polar(torch.ones_like(angles), angles)
82
+ return self.rotation[start:end]
83
+
84
+ def rotate(self, x: torch.Tensor, start: int = 0, time_dim: int = 1, invert_decay: bool = False):
85
+ """Apply rope rotation to query or key tensor."""
86
+ T = x.shape[time_dim]
87
+ target_shape = [1] * x.dim()
88
+ target_shape[time_dim] = T
89
+ target_shape[-1] = -1
90
+ rotation = self.get_rotation(start, start + T).view(target_shape)
91
+
92
+ if self.xpos:
93
+ decay = self.xpos.get_decay(start, start + T).view(target_shape)
94
+ else:
95
+ decay = 1.0
96
+
97
+ if invert_decay:
98
+ decay = decay ** -1
99
+
100
+ x_complex = torch.view_as_complex(x.to(self.dtype).reshape(*x.shape[:-1], -1, 2))
101
+ scaled_rotation = (rotation * decay) * self.scale + (1.0 - self.scale)
102
+ x_out = torch.view_as_real(x_complex * scaled_rotation).view_as(x)
103
+
104
+ return x_out.type_as(x)
105
+
106
+ def rotate_qk(self, query: torch.Tensor, key: torch.Tensor, start: int = 0, time_dim: int = 1):
107
+ """ Apply rope rotation to both query and key tensors.
108
+ Supports streaming mode, in which query and key are not expected to have the same shape.
109
+ In streaming mode, key will be of length [P + C] with P the cached past timesteps, but
110
+ query will be [C] (typically C == 1).
111
+
112
+ Args:
113
+ query (torch.Tensor): Query to rotate.
114
+ key (torch.Tensor): Key to rotate.
115
+ start (int): Start index of the sequence for time offset.
116
+ time_dim (int): which dimension represent the time steps.
117
+ """
118
+ query_timesteps = query.shape[time_dim]
119
+ key_timesteps = key.shape[time_dim]
120
+ streaming_offset = key_timesteps - query_timesteps
121
+
122
+ query_out = self.rotate(query, start + streaming_offset, time_dim)
123
+ key_out = self.rotate(key, start, time_dim, invert_decay=True)
124
+
125
+ return query_out, key_out
models/modules/seanet.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
+ """Encodec SEANet-based encoder and decoder implementation."""
8
+
9
+ import typing as tp
10
+
11
+ import numpy as np
12
+ import torch.nn as nn
13
+
14
+ from . import (
15
+ SConv1d,
16
+ SConvTranspose1d,
17
+ SLSTM
18
+ )
19
+
20
+
21
+ class SEANetResnetBlock(nn.Module):
22
+ """Residual block from SEANet model.
23
+ Args:
24
+ dim (int): Dimension of the input/output
25
+ kernel_sizes (list): List of kernel sizes for the convolutions.
26
+ dilations (list): List of dilations for the convolutions.
27
+ activation (str): Activation function.
28
+ activation_params (dict): Parameters to provide to the activation function
29
+ norm (str): Normalization method.
30
+ norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
31
+ causal (bool): Whether to use fully causal convolution.
32
+ pad_mode (str): Padding mode for the convolutions.
33
+ compress (int): Reduced dimensionality in residual branches (from Demucs v3)
34
+ true_skip (bool): Whether to use true skip connection or a simple convolution as the skip connection.
35
+ """
36
+ def __init__(self, dim: int, kernel_sizes: tp.List[int] = [3, 1], dilations: tp.List[int] = [1, 1],
37
+ activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
38
+ norm: str = 'weight_norm', norm_params: tp.Dict[str, tp.Any] = {}, causal: bool = False,
39
+ pad_mode: str = 'reflect', compress: int = 2, true_skip: bool = True):
40
+ super().__init__()
41
+ assert len(kernel_sizes) == len(dilations), 'Number of kernel sizes should match number of dilations'
42
+ act = getattr(nn, activation)
43
+ hidden = dim // compress
44
+ block = []
45
+ for i, (kernel_size, dilation) in enumerate(zip(kernel_sizes, dilations)): # this is always length 2
46
+ in_chs = dim if i == 0 else hidden
47
+ out_chs = dim if i == len(kernel_sizes) - 1 else hidden
48
+ # print(in_chs, "_", out_chs) # 32 _ 16; 16 _ 32; 64 _ 32; 32 _ 64; etc until 256 _ 128; 128_ 256 for encode
49
+ block += [
50
+ act(**activation_params),
51
+ SConv1d(in_chs, out_chs, kernel_size=kernel_size, dilation=dilation,
52
+ norm=norm, norm_kwargs=norm_params,
53
+ causal=causal, pad_mode=pad_mode),
54
+ ]
55
+ self.block = nn.Sequential(*block)
56
+ self.shortcut: nn.Module
57
+ # true_skip is always false since the default in SEANetEncoder / SEANetDecoder does not get changed
58
+ if true_skip:
59
+ self.shortcut = nn.Identity()
60
+ else:
61
+ self.shortcut = SConv1d(dim, dim, kernel_size=1, norm=norm, norm_kwargs=norm_params,
62
+ causal=causal, pad_mode=pad_mode)
63
+
64
+ def forward(self, x):
65
+ return self.shortcut(x) + self.block(x) # This is simply the sum of two tensors of the same size
66
+
67
+ # Only channels, norm, causal are different between 24HZ & 48HZ, everything else is default parameter
68
+ # 24HZ -> channels = 1, norm = weight_norm, causal = True
69
+ # 48HZ -> channels = 2, norm = time_group_norm, causal = False
70
+ class SEANetEncoder(nn.Module):
71
+ """SEANet encoder.
72
+ Args:
73
+ channels (int): Audio channels.
74
+ dimension (int): Intermediate representation dimension.
75
+ n_filters (int): Base width for the model.
76
+ n_residual_layers (int): nb of residual layers.
77
+ ratios (Sequence[int]): kernel size and stride ratios. The encoder uses downsampling ratios instead of
78
+ upsampling ratios, hence it will use the ratios in the reverse order to the ones specified here
79
+ that must match the decoder order
80
+ activation (str): Activation function. ELU = Exponential Linear Unit
81
+ activation_params (dict): Parameters to provide to the activation function
82
+ norm (str): Normalization method.
83
+ norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
84
+ kernel_size (int): Kernel size for the initial convolution.
85
+ last_kernel_size (int): Kernel size for the initial convolution.
86
+ residual_kernel_size (int): Kernel size for the residual layers.
87
+ dilation_base (int): How much to increase the dilation with each layer.
88
+ causal (bool): Whether to use fully causal convolution.
89
+ pad_mode (str): Padding mode for the convolutions.
90
+ true_skip (bool): Whether to use true skip connection or a simple
91
+ (streamable) convolution as the skip connection in the residual network blocks.
92
+ compress (int): Reduced dimensionality in residual branches (from Demucs v3).
93
+ lstm (int): Number of LSTM layers at the end of the encoder.
94
+ """
95
+ def __init__(self, channels: int = 1, dimension: int = 128, n_filters: int = 32, n_residual_layers: int = 1,
96
+ ratios: tp.List[int] = [8, 5, 4, 2], activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
97
+ norm: str = 'weight_norm', norm_params: tp.Dict[str, tp.Any] = {}, kernel_size: int = 7,
98
+ last_kernel_size: int = 7, residual_kernel_size: int = 3, dilation_base: int = 2, causal: bool = False,
99
+ pad_mode: str = 'reflect', true_skip: bool = False, compress: int = 2, lstm: int = 2):
100
+ super().__init__()
101
+ self.channels = channels
102
+ self.dimension = dimension
103
+ self.n_filters = n_filters
104
+ self.ratios = list(reversed(ratios))
105
+ del ratios
106
+ self.n_residual_layers = n_residual_layers
107
+ self.hop_length = np.prod(self.ratios)
108
+
109
+ act = getattr(nn, activation)
110
+ mult = 1
111
+ model: tp.List[nn.Module] = [
112
+ SConv1d(channels, mult * n_filters, kernel_size, norm=norm, norm_kwargs=norm_params,
113
+ causal=causal, pad_mode=pad_mode)
114
+ ]
115
+ # Downsample to raw audio scale
116
+ for ratio in self.ratios: # CHANGED from: for i, ratio in enumerate(self.ratios):
117
+ # Add residual layers
118
+ for j in range(n_residual_layers): # This is always 1, parameter never gets changed from default anywhere
119
+ model += [
120
+ SEANetResnetBlock(mult * n_filters, kernel_sizes=[residual_kernel_size, 1],
121
+ dilations=[dilation_base ** j, 1],
122
+ norm=norm, norm_params=norm_params,
123
+ activation=activation, activation_params=activation_params,
124
+ causal=causal, pad_mode=pad_mode, compress=compress, true_skip=true_skip)]
125
+
126
+ # Add downsampling layers
127
+ model += [
128
+ act(**activation_params),
129
+ SConv1d(mult * n_filters, mult * n_filters * 2,
130
+ kernel_size=ratio * 2, stride=ratio,
131
+ norm=norm, norm_kwargs=norm_params,
132
+ causal=causal, pad_mode=pad_mode),
133
+ ]
134
+ mult *= 2
135
+
136
+ if lstm:
137
+ model += [SLSTM(mult * n_filters, num_layers=lstm)]
138
+
139
+ model += [
140
+ act(**activation_params),
141
+ SConv1d(mult * n_filters, dimension, last_kernel_size, norm=norm, norm_kwargs=norm_params,
142
+ causal=causal, pad_mode=pad_mode)
143
+ ]
144
+
145
+ self.model = nn.Sequential(*model)
146
+
147
+ def forward(self, x):
148
+ return self.model(x)
149
+
150
+
151
+ class SEANetDecoder(nn.Module):
152
+ """SEANet decoder.
153
+ Args:
154
+ channels (int): Audio channels.
155
+ dimension (int): Intermediate representation dimension.
156
+ n_filters (int): Base width for the model.
157
+ n_residual_layers (int): nb of residual layers.
158
+ ratios (Sequence[int]): kernel size and stride ratios
159
+ activation (str): Activation function.
160
+ activation_params (dict): Parameters to provide to the activation function
161
+ final_activation (str): Final activation function after all convolutions.
162
+ final_activation_params (dict): Parameters to provide to the activation function
163
+ norm (str): Normalization method.
164
+ norm_params (dict): Parameters to provide to the underlying normalization used along with the convolution.
165
+ kernel_size (int): Kernel size for the initial convolution.
166
+ last_kernel_size (int): Kernel size for the initial convolution.
167
+ residual_kernel_size (int): Kernel size for the residual layers.
168
+ dilation_base (int): How much to increase the dilation with each layer.
169
+ causal (bool): Whether to use fully causal convolution.
170
+ pad_mode (str): Padding mode for the convolutions.
171
+ true_skip (bool): Whether to use true skip connection or a simple
172
+ (streamable) convolution as the skip connection in the residual network blocks.
173
+ compress (int): Reduced dimensionality in residual branches (from Demucs v3).
174
+ lstm (int): Number of LSTM layers at the end of the encoder.
175
+ trim_right_ratio (float): Ratio for trimming at the right of the transposed convolution under the causal setup.
176
+ If equal to 1.0, it means that all the trimming is done at the right.
177
+ """
178
+ def __init__(self, channels: int = 1, dimension: int = 128, n_filters: int = 32, n_residual_layers: int = 1,
179
+ ratios: tp.List[int] = [8, 5, 4, 2], activation: str = 'ELU', activation_params: dict = {'alpha': 1.0},
180
+ final_activation: tp.Optional[str] = None, final_activation_params: tp.Optional[dict] = None,
181
+ norm: str = 'weight_norm', norm_params: tp.Dict[str, tp.Any] = {}, kernel_size: int = 7,
182
+ last_kernel_size: int = 7, residual_kernel_size: int = 3, dilation_base: int = 2, causal: bool = False,
183
+ pad_mode: str = 'reflect', true_skip: bool = False, compress: int = 2, lstm: int = 2,
184
+ trim_right_ratio: float = 1.0):
185
+ super().__init__()
186
+ self.dimension = dimension
187
+ self.channels = channels
188
+ self.n_filters = n_filters
189
+ self.ratios = ratios
190
+ del ratios
191
+ self.n_residual_layers = n_residual_layers
192
+ self.hop_length = np.prod(self.ratios)
193
+
194
+ act = getattr(nn, activation)
195
+ mult = int(2 ** len(self.ratios))
196
+ model: tp.List[nn.Module] = [
197
+ SConv1d(dimension, mult * n_filters, kernel_size, norm=norm, norm_kwargs=norm_params,
198
+ causal=causal, pad_mode=pad_mode)
199
+ ]
200
+
201
+ if lstm:
202
+ model += [SLSTM(mult * n_filters, num_layers=lstm)]
203
+
204
+ # Upsample to raw audio scale
205
+ for i, ratio in enumerate(self.ratios):
206
+ # Add upsampling layers
207
+ model += [
208
+ act(**activation_params),
209
+ SConvTranspose1d(mult * n_filters, mult * n_filters // 2,
210
+ kernel_size=ratio * 2, stride=ratio,
211
+ norm=norm, norm_kwargs=norm_params,
212
+ causal=causal, trim_right_ratio=trim_right_ratio),
213
+ ]
214
+ # Add residual layers
215
+ for j in range(n_residual_layers):
216
+ model += [
217
+ SEANetResnetBlock(mult * n_filters // 2, kernel_sizes=[residual_kernel_size, 1],
218
+ dilations=[dilation_base ** j, 1],
219
+ activation=activation, activation_params=activation_params,
220
+ norm=norm, norm_params=norm_params, causal=causal,
221
+ pad_mode=pad_mode, compress=compress, true_skip=true_skip)]
222
+
223
+ mult //= 2
224
+
225
+ # Add final layers
226
+ model += [
227
+ act(**activation_params),
228
+ SConv1d(n_filters, channels, last_kernel_size, norm=norm, norm_kwargs=norm_params,
229
+ causal=causal, pad_mode=pad_mode)
230
+ ]
231
+ # Add optional final activation to decoder (eg. tanh)
232
+ if final_activation is not None: # This is always None
233
+ final_act = getattr(nn, final_activation)
234
+ final_activation_params = final_activation_params or {}
235
+ model += [
236
+ final_act(**final_activation_params)
237
+ ]
238
+ self.model = nn.Sequential(*model)
239
+
240
+ def forward(self, z):
241
+ y = self.model(z)
242
+ return y
243
+
244
+
245
+ def test():
246
+ import torch
247
+ encoder = SEANetEncoder()
248
+ decoder = SEANetDecoder()
249
+ x = torch.randn(1, 1, 24000)
250
+ z = encoder(x)
251
+ assert list(z.shape) == [1, 128, 75], z.shape
252
+ y = decoder(z)
253
+ assert y.shape == x.shape, (x.shape, y.shape)
254
+
255
+
256
+ if __name__ == '__main__':
257
+ test()
models/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
models/modules/transformer.py ADDED
@@ -0,0 +1,755 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ # All rights reserved.
3
+ #
4
+ # This source code is licensed under the license found in the
5
+ # LICENSE file in the root directory of this source tree.
6
+
7
+ """
8
+ Transformer model, with streaming support, xformer attention support
9
+ and easy causal attention with a potentially finite receptive field.
10
+
11
+ See `StreamingTransformer` for more information.
12
+
13
+ Unlike regular PyTorch Transformer, we make the hard choice that batches are first.
14
+ """
15
+
16
+ import typing as tp
17
+
18
+ from einops import rearrange
19
+ import torch
20
+ import torch.nn as nn
21
+ from torch.nn import functional as F
22
+ from torch.utils.checkpoint import checkpoint as torch_checkpoint
23
+ from xformers import ops
24
+
25
+ from .rope import RotaryEmbedding
26
+ from .streaming import StreamingModule
27
+
28
+ _efficient_attention_backend: str = 'torch'
29
+
30
+
31
+ def set_efficient_attention_backend(backend: str = 'torch'):
32
+ # Using torch by default, it seems a bit faster on older P100 GPUs (~20% faster).
33
+ global _efficient_attention_backend
34
+ assert _efficient_attention_backend in ['xformers', 'torch']
35
+ _efficient_attention_backend = backend
36
+
37
+
38
+ def _get_attention_time_dimension(memory_efficient: bool) -> int:
39
+ if _efficient_attention_backend == 'torch' and memory_efficient:
40
+ return 2
41
+ else:
42
+ return 1
43
+
44
+
45
+ def _is_profiled() -> bool:
46
+ # Return true if we are currently running with a xformers profiler activated.
47
+ try:
48
+ from xformers.profiler import profiler
49
+ except ImportError:
50
+ return False
51
+ return profiler._Profiler._CURRENT_PROFILER is not None
52
+
53
+
54
+ def create_norm_fn(norm_type: str, dim: int, **kwargs) -> nn.Module:
55
+ """Create normalization module for transformer encoder layer.
56
+
57
+ Args:
58
+ norm_type (str): Normalization method.
59
+ dim (int): Dimension of the normalized layer.
60
+ **kwargs (dict): Additional parameters for normalization layer.
61
+ Returns:
62
+ nn.Module: Normalization module.
63
+ """
64
+ if norm_type == 'layer_norm':
65
+ return nn.LayerNorm(dim, eps=1e-5, **kwargs)
66
+ else:
67
+ raise ValueError(f"Unknown norm type: {norm_type}")
68
+
69
+
70
+ def create_sin_embedding(positions: torch.Tensor, dim: int, max_period: float = 10000,
71
+ dtype: torch.dtype = torch.float32) -> torch.Tensor:
72
+ """Create sinusoidal positional embedding, with shape `[B, T, C]`.
73
+
74
+ Args:
75
+ positions (torch.Tensor): LongTensor of positions.
76
+ dim (int): Dimension of the embedding.
77
+ max_period (float): Maximum period of the cosine/sine functions.
78
+ dtype (torch.dtype or str): dtype to use to generate the embedding.
79
+ Returns:
80
+ torch.Tensor: Sinusoidal positional embedding.
81
+ """
82
+ # We aim for BTC format
83
+ assert dim % 2 == 0
84
+ half_dim = dim // 2
85
+ positions = positions.to(dtype)
86
+ adim = torch.arange(half_dim, device=positions.device, dtype=dtype).view(1, 1, -1)
87
+ max_period_tensor = torch.full([], max_period, device=positions.device, dtype=dtype) # avoid sync point
88
+ phase = positions / (max_period_tensor ** (adim / (half_dim - 1)))
89
+ return torch.cat([torch.cos(phase), torch.sin(phase)], dim=-1)
90
+
91
+
92
+ def expand_repeated_kv(x: torch.Tensor, n_rep: int, memory_efficient: bool) -> torch.Tensor:
93
+ """torch.repeat_interleave(x, dim=2, repeats=n_rep) from xlformers."""
94
+ if n_rep == 1:
95
+ return x
96
+ if _efficient_attention_backend == 'torch' and memory_efficient:
97
+ bs, n_kv_heads, slen, head_dim = x.shape
98
+ return (
99
+ x[:, :, None, :, :]
100
+ .expand(bs, n_kv_heads, n_rep, slen, head_dim)
101
+ .reshape(bs, n_kv_heads * n_rep, slen, head_dim)
102
+ )
103
+ else:
104
+ bs, slen, n_kv_heads, head_dim = x.shape
105
+ return (
106
+ x[:, :, :, None, :]
107
+ .expand(bs, slen, n_kv_heads, n_rep, head_dim)
108
+ .reshape(bs, slen, n_kv_heads * n_rep, head_dim)
109
+ )
110
+
111
+
112
+ class LayerScale(nn.Module):
113
+ """Layer scale from [Touvron et al 2021] (https://arxiv.org/pdf/2103.17239.pdf).
114
+ This rescales diagonally the residual outputs close to 0, with a learnt scale.
115
+
116
+ Args:
117
+ channels (int): Number of channels.
118
+ init (float): Initial scale.
119
+ channel_last (bool): If True, expect `[*, C]` shaped tensors, otherwise, `[*, C, T]`.
120
+ device (torch.device or str, optional): Device on which to initialize the module.
121
+ dtype (torch.dtype, optional): dtype to use to initialize the module.
122
+ """
123
+ def __init__(self, channels: int, init: float = 1e-4, channel_last: bool = True,
124
+ device=None, dtype=None):
125
+ super().__init__()
126
+ self.channel_last = channel_last
127
+ self.scale = nn.Parameter(
128
+ torch.full((channels,), init,
129
+ requires_grad=True, device=device, dtype=dtype))
130
+
131
+ def forward(self, x: torch.Tensor):
132
+ if self.channel_last:
133
+ return self.scale * x
134
+ else:
135
+ return self.scale[:, None] * x
136
+
137
+
138
+ class StreamingMultiheadAttention(StreamingModule):
139
+ """Similar to `nn.MultiheadAttention` but with support for streaming, causal evaluation.
140
+
141
+ Args:
142
+ embed_dim (int): Dimension to project to.
143
+ num_heads (int): Number of heads.
144
+ dropout (float): Dropout level.
145
+ bias (bool): Use bias in projections.
146
+ causal (bool): Causal mask applied automatically.
147
+ past_context (int, optional): Receptive field for the causal mask, infinite if None.
148
+ custom (bool): Use custom MHA implementation, for testing / benchmarking.
149
+ memory_efficient (bool): Use xformers based memory efficient attention.
150
+ attention_as_float32 (bool): Perform the attention as float32
151
+ (especially important with memory_efficient as autocast won't do this automatically).
152
+ rope (`RotaryEmbedding`, optional): Rope embedding to use.
153
+ cross_attention: Should be true when used as a cross attention.
154
+ All keys and values must be available at once, streaming is only for the queries.
155
+ Cannot be used with `causal` or `rope` (as it wouldn't make sens to
156
+ interpret the time steps in the keys relative to those in the queries).
157
+ safe_streaming (bool): Bug fix, will go away with xformers update.
158
+ qk_layer_norm (bool): Layer normalization applied to queries and keys before dot product.
159
+ kv_repeat (int): If > 1, will repeat keys and queries multiple times (need to divide num_heads).
160
+ This will lead to faster decoding time on A100 or other GPUs with tensorcore.
161
+ device (torch.device, optional): Device on which to initialize.
162
+ dtype (torch.dtype, optional): dtype to use.
163
+ """
164
+ def __init__(self, embed_dim: int, num_heads: int, dropout: float = 0.0, bias: bool = True,
165
+ causal: bool = False, past_context: tp.Optional[int] = None, custom: bool = False,
166
+ memory_efficient: bool = False, attention_as_float32: bool = False,
167
+ rope: tp.Optional[RotaryEmbedding] = None, cross_attention: bool = False,
168
+ safe_streaming: bool = True, qk_layer_norm: bool = False, kv_repeat: int = 1,
169
+ device=None, dtype=None):
170
+ super().__init__()
171
+ factory_kwargs = {'device': device, 'dtype': dtype}
172
+ if past_context is not None:
173
+ assert causal
174
+
175
+ self.embed_dim = embed_dim
176
+ self.causal = causal
177
+ self.past_context = past_context
178
+ self.memory_efficient = memory_efficient
179
+ self.attention_as_float32 = attention_as_float32
180
+ self.rope = rope
181
+ self.cross_attention = cross_attention
182
+ self.safe_streaming = safe_streaming
183
+ self.num_heads = num_heads
184
+ self.dropout = dropout
185
+ self.kv_repeat = kv_repeat
186
+ if cross_attention:
187
+ assert not causal, "Causal cannot work with cross attention."
188
+ assert rope is None, "Rope cannot work with cross attention."
189
+
190
+ if memory_efficient:
191
+ _verify_xformers_memory_efficient_compat()
192
+
193
+ self.custom = _is_custom(custom, memory_efficient)
194
+ if self.custom:
195
+ out_dim = embed_dim
196
+ assert num_heads % kv_repeat == 0
197
+ assert not cross_attention or kv_repeat == 1
198
+ num_kv = num_heads // kv_repeat
199
+ kv_dim = (embed_dim // num_heads) * num_kv
200
+ out_dim += 2 * kv_dim
201
+ in_proj = nn.Linear(embed_dim, out_dim, bias=bias, **factory_kwargs)
202
+ # We try to follow the default PyTorch MHA convention, to easily compare results.
203
+ self.in_proj_weight = in_proj.weight
204
+ self.in_proj_bias = in_proj.bias
205
+ if bias:
206
+ self.in_proj_bias.data.zero_() # Following Pytorch convention
207
+ self.out_proj = nn.Linear(embed_dim, embed_dim, bias=bias, **factory_kwargs)
208
+ if bias:
209
+ self.out_proj.bias.data.zero_()
210
+ else:
211
+ assert not qk_layer_norm
212
+ assert kv_repeat == 1
213
+ self.mha = nn.MultiheadAttention(
214
+ embed_dim, num_heads, dropout=dropout, bias=bias, batch_first=True,
215
+ **factory_kwargs)
216
+ self.qk_layer_norm = qk_layer_norm
217
+ if qk_layer_norm:
218
+ assert self.custom
219
+ assert kv_repeat == 1
220
+ ln_dim = embed_dim
221
+ self.q_layer_norm = nn.LayerNorm(ln_dim)
222
+ self.k_layer_norm = nn.LayerNorm(ln_dim)
223
+
224
+ def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
225
+ if not self.custom:
226
+ # Support compat with regular MHA
227
+ keys = [n for n, _ in self.mha.named_parameters()]
228
+ for key in keys:
229
+ if prefix + key in state_dict:
230
+ state_dict[prefix + "mha." + key] = state_dict.pop(prefix + key)
231
+ super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
232
+
233
+ def _get_mask(self, current_steps: int, device: torch.device, dtype: torch.dtype):
234
+ # Return a causal mask, accounting for potentially stored past keys/values
235
+ # We actually return a bias for the attention score, as this has the same
236
+ # convention both in the builtin MHA in Pytorch, and Xformers functions.
237
+ time_dim = _get_attention_time_dimension(self.memory_efficient)
238
+ if self.memory_efficient:
239
+ from xformers.ops import LowerTriangularMask
240
+ if current_steps == 1:
241
+ # If we only have one step, then we do not need a mask.
242
+ return None
243
+ elif 'past_keys' in self._streaming_state:
244
+ raise RuntimeError("Not supported at the moment")
245
+ else:
246
+ # Then we can safely use a lower triangular mask
247
+ return LowerTriangularMask()
248
+ if self._streaming_state:
249
+ past_keys = self._streaming_state['past_keys']
250
+ past_steps = past_keys.shape[time_dim]
251
+ else:
252
+ past_steps = 0
253
+
254
+ queries_pos = torch.arange(
255
+ past_steps, current_steps + past_steps, device=device).view(-1, 1)
256
+ keys_pos = torch.arange(past_steps + current_steps, device=device).view(1, -1)
257
+ delta = queries_pos - keys_pos
258
+ valid = delta >= 0
259
+ if self.past_context is not None:
260
+ valid &= (delta <= self.past_context)
261
+ return torch.where(
262
+ valid,
263
+ torch.zeros([], device=device, dtype=dtype),
264
+ torch.full([], float('-inf'), device=device, dtype=dtype))
265
+
266
+ def _complete_kv(self, k, v):
267
+ time_dim = _get_attention_time_dimension(self.memory_efficient)
268
+ if self.cross_attention:
269
+ # With cross attention we assume all keys and values
270
+ # are already available, and streaming is with respect
271
+ # to the queries only.
272
+ return k, v
273
+ # Complete the key/value pair using the streaming state.
274
+ if self._streaming_state:
275
+ pk = self._streaming_state['past_keys']
276
+ nk = torch.cat([pk, k], dim=time_dim)
277
+ if v is k:
278
+ nv = nk
279
+ else:
280
+ pv = self._streaming_state['past_values']
281
+ nv = torch.cat([pv, v], dim=time_dim)
282
+ else:
283
+ nk = k
284
+ nv = v
285
+
286
+ assert nk.shape[time_dim] == nv.shape[time_dim]
287
+ offset = 0
288
+ if self.past_context is not None:
289
+ offset = max(0, nk.shape[time_dim] - self.past_context)
290
+ if self._is_streaming:
291
+ self._streaming_state['past_keys'] = nk[:, offset:]
292
+ if v is not k:
293
+ self._streaming_state['past_values'] = nv[:, offset:]
294
+ if 'offset' in self._streaming_state:
295
+ self._streaming_state['offset'] += offset
296
+ else:
297
+ self._streaming_state['offset'] = torch.tensor(0)
298
+ return nk, nv
299
+
300
+ def _apply_rope(self, query: torch.Tensor, key: torch.Tensor):
301
+ time_dim = _get_attention_time_dimension(self.memory_efficient)
302
+ # Apply rope embeddings to query and key tensors.
303
+ assert self.rope is not None
304
+ if 'past_keys' in self._streaming_state:
305
+ past_keys_offset = self._streaming_state['past_keys'].shape[1]
306
+ else:
307
+ past_keys_offset = 0
308
+ if 'offset' in self._streaming_state:
309
+ past_context_offset = int(self._streaming_state['offset'].item())
310
+ else:
311
+ past_context_offset = 0
312
+ streaming_offset = past_context_offset + past_keys_offset
313
+ return self.rope.rotate_qk(query, key, start=streaming_offset, time_dim=time_dim)
314
+
315
+ def forward(self, query: torch.Tensor, key: torch.Tensor, value: torch.Tensor,
316
+ key_padding_mask=None, need_weights=False, attn_mask=None,
317
+ average_attn_weights=True, is_causal=False):
318
+ assert not is_causal, ("New param added in torch 2.0.1 not supported, "
319
+ "use the causal args in the constructor.")
320
+
321
+ time_dim = _get_attention_time_dimension(self.memory_efficient)
322
+ if time_dim == 2:
323
+ layout = "b h t d"
324
+ else:
325
+ layout = "b t h d"
326
+ dtype = query.dtype
327
+ if self._is_streaming:
328
+ assert self.causal or self.cross_attention, \
329
+ "Streaming only available for causal or cross attention"
330
+
331
+ custom_attn_mask = attn_mask is not None
332
+
333
+ if self.causal:
334
+ assert attn_mask is None
335
+ # At the moment we specialize only for the self-attention case.
336
+ assert query.shape[1] == key.shape[1], "Causal only for same length query / key / value"
337
+ assert value.shape[1] == key.shape[1], "Causal only for same length query / key / value"
338
+ attn_mask = self._get_mask(query.shape[1], query.device, query.dtype)
339
+
340
+ if self.custom:
341
+ # custom implementation
342
+ assert need_weights is False
343
+ assert key_padding_mask is None
344
+ if self.cross_attention:
345
+ # Different queries, keys, values, we have to spit manually the weights
346
+ # before applying the linear.
347
+ dim = self.in_proj_weight.shape[0] // 3
348
+ if self.in_proj_bias is None:
349
+ bias_q, bias_k, bias_v = None, None, None
350
+ else:
351
+ bias_q = self.in_proj_bias[:dim]
352
+ bias_k = self.in_proj_bias[dim: 2 * dim]
353
+ bias_v = self.in_proj_bias[2 * dim:]
354
+ q = nn.functional.linear(query, self.in_proj_weight[:dim], bias_q)
355
+ # todo: when streaming, we could actually save k, v and check the shape actually match.
356
+ k = nn.functional.linear(key, self.in_proj_weight[dim: 2 * dim], bias_k)
357
+ v = nn.functional.linear(value, self.in_proj_weight[2 * dim:], bias_v)
358
+ if self.qk_layer_norm is True:
359
+ q = self.q_layer_norm(q)
360
+ k = self.k_layer_norm(k)
361
+ q, k, v = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k, v]]
362
+ else:
363
+ if not _is_profiled():
364
+ # profiling breaks that propertysomehow.
365
+ assert query is key, "specialized implementation"
366
+ assert value is key, "specialized implementation"
367
+ projected = nn.functional.linear(query, self.in_proj_weight, self.in_proj_bias)
368
+ if self.kv_repeat == 1:
369
+ if time_dim == 2:
370
+ bound_layout = "b h p t d"
371
+ else:
372
+ bound_layout = "b t p h d"
373
+ packed = rearrange(projected, f"b t (p h d) -> {bound_layout}", p=3, h=self.num_heads)
374
+ q, k, v = ops.unbind(packed, dim=2)
375
+ else:
376
+ embed_dim = self.embed_dim
377
+ per_head_dim = (embed_dim // self.num_heads)
378
+ kv_heads = self.num_heads // self.kv_repeat
379
+ q = projected[:, :, :embed_dim]
380
+ start = embed_dim
381
+ end = start + per_head_dim * kv_heads
382
+ k = projected[:, :, start: end]
383
+ v = projected[:, :, end:]
384
+ q = rearrange(q, f"b t (h d) -> {layout}", h=self.num_heads)
385
+ k = rearrange(k, f"b t (h d) -> {layout}", h=kv_heads)
386
+ v = rearrange(v, f"b t (h d) -> {layout}", h=kv_heads)
387
+
388
+ if self.qk_layer_norm is True:
389
+ assert self.kv_repeat == 1
390
+ q, k = [rearrange(x, f"{layout} -> b t (h d)") for x in [q, k]]
391
+ q = self.q_layer_norm(q)
392
+ k = self.k_layer_norm(k)
393
+ q, k = [rearrange(x, f"b t (h d) -> {layout}", h=self.num_heads) for x in [q, k]]
394
+ if self.rope:
395
+ q, k = self._apply_rope(q, k)
396
+ k, v = self._complete_kv(k, v)
397
+ if self.kv_repeat > 1:
398
+ k = expand_repeated_kv(k, self.kv_repeat, self.memory_efficient)
399
+ v = expand_repeated_kv(v, self.kv_repeat, self.memory_efficient)
400
+ if self.attention_as_float32:
401
+ q, k, v = [x.float() for x in [q, k, v]]
402
+ if self.memory_efficient:
403
+ if custom_attn_mask:
404
+ # When using a custom attn mask:
405
+ # Move to query's device, repeat for each sample, remove align8 padding
406
+ seq_len = query.shape[1]
407
+ attn_mask = attn_mask.to(q.dtype)
408
+ attn_mask = attn_mask.repeat((q.shape[0], 1, 1, 1))
409
+ attn_mask = attn_mask[..., :seq_len, :seq_len]
410
+
411
+ p = self.dropout if self.training else 0
412
+ if _efficient_attention_backend == 'torch':
413
+ x = torch.nn.functional.scaled_dot_product_attention(
414
+ q, k, v, is_causal=attn_mask is not None, dropout_p=p)
415
+ else:
416
+ x = ops.memory_efficient_attention(q, k, v, attn_mask, p=p)
417
+ else:
418
+ # We include the dot product as float32, for consistency
419
+ # with the other implementations that include that step
420
+ # as part of the attention. Note that when using `autocast`,
421
+ # the einsums would be done as bfloat16, but the softmax
422
+ # would be done as bfloat16, so `attention_as_float32` will
423
+ # extend a bit the range of operations done in float32,
424
+ # although this should make no difference.
425
+ q = q / q.shape[-1] ** 0.5
426
+ key_layout = layout.replace('t', 'k')
427
+ query_layout = layout
428
+ if self._is_streaming and self.safe_streaming and q.device.type == 'cuda':
429
+ with torch.autocast(device_type=q.device.type, dtype=torch.float32):
430
+ pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
431
+ else:
432
+ pre_w = torch.einsum(f"{query_layout},{key_layout}-> b h t k", q, k)
433
+ if attn_mask is not None:
434
+ pre_w = pre_w + attn_mask
435
+ w = torch.softmax(pre_w, dim=-1)
436
+ w = F.dropout(w, self.dropout, training=self.training).to(v)
437
+ # Key and value have the same format.
438
+ x = torch.einsum(f"b h t k, {key_layout} -> {layout}", w, v)
439
+ x = x.to(dtype)
440
+ x = rearrange(x, f"{layout} -> b t (h d)", h=self.num_heads)
441
+ x = self.out_proj(x)
442
+ else:
443
+ key, value = self._complete_kv(key, value)
444
+ if self.attention_as_float32:
445
+ query, key, value = [x.float() for x in [query, key, value]]
446
+ x, _ = self.mha(
447
+ query, key, value, key_padding_mask,
448
+ need_weights, attn_mask, average_attn_weights)
449
+ x = x.to(dtype)
450
+
451
+ return x, None
452
+
453
+
454
+ class StreamingTransformerLayer(nn.TransformerEncoderLayer):
455
+ """TransformerLayer with Streaming / Causal support.
456
+ This also integrates cross_attention, when passing `cross_attention=True`,
457
+ rather than having two separate classes like in PyTorch.
458
+
459
+ Args:
460
+ d_model (int): Dimension of the data.
461
+ num_heads (int): Number of heads.
462
+ dim_feedforward (int): Intermediate dimension of FF module.
463
+ dropout (float): Dropout both for MHA and FF.
464
+ bias_ff (bool): Use bias for FF.
465
+ bias_attn (bool): Use bias for MHA.
466
+ causal (bool): Causal mask applied automatically.
467
+ past_context (int, optional): Receptive field for the causal mask, infinite if None.
468
+ custom (bool): Use custom MHA implementation, for testing / benchmarking.
469
+ memory_efficient (bool): Use xformers based memory efficient attention.
470
+ attention_as_float32 (bool): Perform the attention as float32
471
+ (especially important with memory_efficient as autocast won't do this automatically).
472
+ qk_layer_norm (bool): Layer normalization applied to queries and keys before dot product in attention.
473
+ qk_layer_norm_cross (bool): Same for the cross attention.
474
+ cross_attention (bool): If True, expect to get secondary input for cross-attention.
475
+ Cross attention will use the default MHA, as it typically won't require
476
+ special treatment.
477
+ layer_scale (float, optional): If not None, LayerScale will be used with
478
+ the given value as initial scale.
479
+ rope (`RotaryEmbedding`, optional): Rope embedding to use.
480
+ attention_dropout (float, optional): If not None, separate the value of the dimension dropout
481
+ in FFN and of the attention dropout.
482
+ kv_repeat (int): If > 1, will repeat keys and queries multiple times (need to divide num_heads).
483
+ This will lead to faster decoding time on A100 or other GPUs with tensorcore.
484
+ device (torch.device, optional): Device on which to initialize.
485
+ dtype (torch.dtype, optional): dtype to use.
486
+ **kwargs: See `nn.TransformerEncoderLayer`.
487
+ """
488
+ def __init__(self, d_model: int, num_heads: int, dim_feedforward: int = 2048, dropout: float = 0.1,
489
+ bias_ff: bool = True, bias_attn: bool = True, causal: bool = False,
490
+ past_context: tp.Optional[int] = None, custom: bool = False,
491
+ memory_efficient: bool = False, attention_as_float32: bool = False,
492
+ qk_layer_norm: bool = False, qk_layer_norm_cross: bool = False,
493
+ cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
494
+ rope: tp.Optional[RotaryEmbedding] = None, attention_dropout: tp.Optional[float] = None,
495
+ kv_repeat: int = 1, norm: str = 'layer_norm', device=None, dtype=None, **kwargs):
496
+ super().__init__(d_model, num_heads, dim_feedforward, dropout,
497
+ device=device, dtype=dtype, batch_first=True, **kwargs)
498
+ factory_kwargs = {'device': device, 'dtype': dtype}
499
+ # Redefine self_attn to our streaming multi-head attention
500
+ attn_kwargs: tp.Dict[str, tp.Any] = {
501
+ 'embed_dim': d_model,
502
+ 'num_heads': num_heads,
503
+ 'dropout': dropout if attention_dropout is None else attention_dropout,
504
+ 'bias': bias_attn,
505
+ 'custom': custom,
506
+ 'memory_efficient': memory_efficient,
507
+ 'attention_as_float32': attention_as_float32,
508
+ }
509
+ self.self_attn: StreamingMultiheadAttention = StreamingMultiheadAttention(
510
+ causal=causal, past_context=past_context, rope=rope, qk_layer_norm=qk_layer_norm,
511
+ kv_repeat=kv_repeat, **attn_kwargs, **factory_kwargs) # type: ignore
512
+ # Redefine feedforward layers to expose bias parameter
513
+ self.linear1 = nn.Linear(d_model, dim_feedforward, bias=bias_ff, **factory_kwargs)
514
+ self.linear2 = nn.Linear(dim_feedforward, d_model, bias=bias_ff, **factory_kwargs)
515
+
516
+ self.layer_scale_1: nn.Module
517
+ self.layer_scale_2: nn.Module
518
+ if layer_scale is None:
519
+ self.layer_scale_1 = nn.Identity()
520
+ self.layer_scale_2 = nn.Identity()
521
+ else:
522
+ self.layer_scale_1 = LayerScale(d_model, layer_scale, **factory_kwargs)
523
+ self.layer_scale_2 = LayerScale(d_model, layer_scale, **factory_kwargs)
524
+
525
+ self.cross_attention: tp.Optional[nn.Module] = None
526
+ if cross_attention:
527
+ self.cross_attention = StreamingMultiheadAttention(
528
+ cross_attention=True, qk_layer_norm=qk_layer_norm_cross,
529
+ **attn_kwargs, **factory_kwargs)
530
+ # Norm and dropout
531
+ self.dropout_cross = nn.Dropout(dropout)
532
+ # eps value matching that used in PyTorch reference implementation.
533
+ self.norm_cross = nn.LayerNorm(d_model, eps=1e-5, **factory_kwargs)
534
+ self.layer_scale_cross: nn.Module
535
+ if layer_scale is None:
536
+ self.layer_scale_cross = nn.Identity()
537
+ else:
538
+ self.layer_scale_cross = LayerScale(d_model, layer_scale, **factory_kwargs)
539
+ self.norm1 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
540
+ self.norm2 = create_norm_fn(norm, d_model, **factory_kwargs) # type: ignore
541
+
542
+ def _cross_attention_block(self, src: torch.Tensor,
543
+ cross_attention_src: torch.Tensor) -> torch.Tensor:
544
+ assert self.cross_attention is not None
545
+ # queries are from src, keys and values from cross_attention_src.
546
+ x = self.cross_attention(
547
+ src, cross_attention_src, cross_attention_src, need_weights=False)[0]
548
+ return self.dropout_cross(x) # type: ignore
549
+
550
+ def forward(self, src: torch.Tensor, src_mask: tp.Optional[torch.Tensor] = None, # type: ignore
551
+ src_key_padding_mask: tp.Optional[torch.Tensor] = None,
552
+ cross_attention_src: tp.Optional[torch.Tensor] = None):
553
+ if self.cross_attention is None:
554
+ assert cross_attention_src is None
555
+ else:
556
+ assert cross_attention_src is not None
557
+ x = src
558
+ if self.norm_first:
559
+ x = x + self.layer_scale_1(
560
+ self._sa_block(self.norm1(x), src_mask, src_key_padding_mask))
561
+ if cross_attention_src is not None:
562
+ x = x + self.layer_scale_cross(
563
+ self._cross_attention_block(
564
+ self.norm_cross(x), cross_attention_src))
565
+ x = x + self.layer_scale_2(self._ff_block(self.norm2(x)))
566
+ else:
567
+ x = self.norm1(x + self.layer_scale_1(
568
+ self._sa_block(x, src_mask, src_key_padding_mask)))
569
+ if cross_attention_src is not None:
570
+ x = self.norm_cross(
571
+ x + self.layer_scale_cross(
572
+ self._cross_attention_block(src, cross_attention_src)))
573
+ x = self.norm2(x + self.layer_scale_2(self._ff_block(x)))
574
+ return x
575
+
576
+
577
+ class StreamingTransformer(StreamingModule):
578
+ """Transformer with Streaming / Causal support.
579
+
580
+ Args:
581
+ d_model (int): Dimension of the data.
582
+ num_heads (int): Number of heads.
583
+ dim_feedforward (int): Intermediate dimension of FF module.
584
+ dropout (float): Dropout both for MHA and FF.
585
+ bias_ff (bool): Use bias for FF.
586
+ bias_attn (bool): Use bias for MHA.
587
+ causal (bool): Causal mask applied automatically.
588
+ past_context (int, optional): Receptive field for the causal mask, infinite if None.
589
+ custom (bool): Use custom MHA implementation, for testing / benchmarking.
590
+ memory_efficient (bool): Use xformers based memory efficient attention.
591
+ attention_as_float32 (bool): Perform the attention as float32
592
+ (especially important with memory_efficient as autocast won't do this automatically).
593
+ cross_attention (bool): If True, expect to get secondary input for cross-attention.
594
+ layer_scale (float, optional): If not None, LayerScale will be used
595
+ with the given value as initial scale.
596
+ positional_embedding (str): Positional embedding strategy (sin, rope, or sin_rope).
597
+ max_period (float): Maximum period of the time embedding.
598
+ positional_scale (float): Scale of positional embedding, set to 0 to deactivate.
599
+ xpos (bool): Apply xpos exponential decay to positional embedding (rope only).
600
+ lr (float, optional): learning rate override through the `make_optim_group` API.
601
+ weight_decay (float, optional): Weight_decay override through the `make_optim_group` API.
602
+ layer_class: (subclass of `StreamingTransformerLayer): class to use
603
+ to initialize the layers, allowing further customization outside of AudioCraft.
604
+ checkpointing (str): Checkpointing strategy to reduce memory usage.
605
+ No checkpointing if set to 'none'. Per layer checkpointing using PyTorch
606
+ if set to 'torch' (entire layer checkpointed, i.e. linears are evaluated twice,
607
+ minimal memory usage, but maximal runtime). Finally, `xformers_default` provide
608
+ a policy for opting-out some operations of the checkpointing like
609
+ linear layers and attention, providing a middle ground between speed and memory.
610
+ device (torch.device, optional): Device on which to initialize.
611
+ dtype (torch.dtype, optional): dtype to use.
612
+ **kwargs: See `nn.TransformerEncoderLayer`.
613
+ """
614
+ def __init__(self, d_model: int, num_heads: int, num_layers: int, dim_feedforward: int = 2048,
615
+ dropout: float = 0.1, bias_ff: bool = True, bias_attn: bool = True,
616
+ causal: bool = False, past_context: tp.Optional[int] = None,
617
+ custom: bool = False, memory_efficient: bool = False, attention_as_float32: bool = False,
618
+ cross_attention: bool = False, layer_scale: tp.Optional[float] = None,
619
+ positional_embedding: str = 'sin', max_period: float = 10_000, positional_scale: float = 1.,
620
+ xpos: bool = False, lr: tp.Optional[float] = None, weight_decay: tp.Optional[float] = None,
621
+ layer_class: tp.Type[StreamingTransformerLayer] = StreamingTransformerLayer,
622
+ checkpointing: str = 'none', device=None, dtype=None, **kwargs):
623
+ super().__init__()
624
+ assert d_model % num_heads == 0
625
+
626
+ self.positional_embedding = positional_embedding
627
+ self.max_period = max_period
628
+ self.positional_scale = positional_scale
629
+ self.weight_decay = weight_decay
630
+ self.lr = lr
631
+
632
+ assert positional_embedding in ['sin', 'rope', 'sin_rope']
633
+ self.rope: tp.Optional[RotaryEmbedding] = None
634
+ if self.positional_embedding in ['rope', 'sin_rope']:
635
+ assert _is_custom(custom, memory_efficient)
636
+ self.rope = RotaryEmbedding(d_model // num_heads, max_period=max_period,
637
+ xpos=xpos, scale=positional_scale, device=device)
638
+
639
+ self.checkpointing = checkpointing
640
+
641
+ assert checkpointing in ['none', 'torch', 'xformers_default', 'xformers_mm']
642
+ if self.checkpointing.startswith('xformers'):
643
+ _verify_xformers_internal_compat()
644
+
645
+ self.layers = nn.ModuleList()
646
+ for idx in range(num_layers):
647
+ self.layers.append(
648
+ layer_class(
649
+ d_model=d_model, num_heads=num_heads, dim_feedforward=dim_feedforward,
650
+ dropout=dropout, bias_ff=bias_ff, bias_attn=bias_attn,
651
+ causal=causal, past_context=past_context, custom=custom,
652
+ memory_efficient=memory_efficient, attention_as_float32=attention_as_float32,
653
+ cross_attention=cross_attention, layer_scale=layer_scale, rope=self.rope,
654
+ device=device, dtype=dtype, **kwargs))
655
+
656
+ if self.checkpointing != 'none':
657
+ for layer in self.layers:
658
+ # see audiocraft/optim/fsdp.py, magic signal to indicate this requires fixing the
659
+ # backward hook inside of FSDP...
660
+ layer._magma_checkpointed = True # type: ignore
661
+
662
+ def _apply_layer(self, layer, *args, **kwargs):
663
+ method = self.checkpointing
664
+ if method == 'none':
665
+ return layer(*args, **kwargs)
666
+ elif method == 'torch':
667
+ return torch_checkpoint(layer, *args, use_reentrant=False, **kwargs)
668
+ elif method.startswith('xformers'):
669
+ from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy
670
+ if method == 'xformers_default':
671
+ # those operations will be saved, and not recomputed.
672
+ # According to Francisco we can get smarter policies but this is a good start.
673
+ allow_list = [
674
+ "xformers.efficient_attention_forward_cutlass.default",
675
+ "xformers_flash.flash_fwd.default",
676
+ "aten.addmm.default",
677
+ "aten.mm.default",
678
+ ]
679
+ elif method == 'xformers_mm':
680
+ # those operations will be saved, and not recomputed.
681
+ # According to Francisco we can get smarter policies but this is a good start.
682
+ allow_list = [
683
+ "aten.addmm.default",
684
+ "aten.mm.default",
685
+ ]
686
+ else:
687
+ raise ValueError(f"xformers checkpointing xformers policy {method} is not known.")
688
+ policy_fn = _get_default_policy(allow_list)
689
+ return checkpoint(layer, *args, policy_fn=policy_fn, **kwargs)
690
+ else:
691
+ raise ValueError(f"Checkpointing method {method} is unknown.")
692
+
693
+ def forward(self, x: torch.Tensor, *args, **kwargs):
694
+ B, T, C = x.shape
695
+
696
+ if 'offsets' in self._streaming_state:
697
+ offsets = self._streaming_state['offsets']
698
+ else:
699
+ offsets = torch.zeros(B, dtype=torch.long, device=x.device)
700
+
701
+ if self.positional_embedding in ['sin', 'sin_rope']:
702
+ positions = torch.arange(T, device=x.device).view(1, -1, 1)
703
+ positions = positions + offsets.view(-1, 1, 1)
704
+ pos_emb = create_sin_embedding(positions, C, max_period=self.max_period, dtype=x.dtype)
705
+ x = x + self.positional_scale * pos_emb
706
+
707
+ for layer in self.layers:
708
+ x = self._apply_layer(layer, x, *args, **kwargs)
709
+
710
+ if self._is_streaming:
711
+ self._streaming_state['offsets'] = offsets + T
712
+
713
+ return x
714
+
715
+ def make_optim_group(self):
716
+ group = {"params": list(self.parameters())}
717
+ if self.lr is not None:
718
+ group["lr"] = self.lr
719
+ if self.weight_decay is not None:
720
+ group["weight_decay"] = self.weight_decay
721
+ return group
722
+
723
+
724
+ # special attention related function
725
+
726
+ def _verify_xformers_memory_efficient_compat():
727
+ try:
728
+ from xformers.ops import memory_efficient_attention, LowerTriangularMask # noqa
729
+ except ImportError:
730
+ raise ImportError(
731
+ "xformers is not installed. Please install it and try again.\n"
732
+ "To install on AWS and Azure, run \n"
733
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
734
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
735
+ "To install on FAIR Cluster, run \n"
736
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
737
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
738
+
739
+
740
+ def _verify_xformers_internal_compat():
741
+ try:
742
+ from xformers.checkpoint_fairinternal import checkpoint, _get_default_policy # noqa
743
+ except ImportError:
744
+ raise ImportError(
745
+ "Francisco's fairinternal xformers is not installed. Please install it and try again.\n"
746
+ "To install on AWS and Azure, run \n"
747
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='8.0'\\\n"
748
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n"
749
+ "To install on FAIR Cluster, run \n"
750
+ "FORCE_CUDA=1 TORCH_CUDA_ARCH_LIST='6.0;7.0'\\\n"
751
+ "pip install -U git+https://git@github.com/fairinternal/xformers.git#egg=xformers\n")
752
+
753
+
754
+ def _is_custom(custom: bool, memory_efficient: bool):
755
+ return custom or memory_efficient
models/musicgen_lm.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from torch import nn, Tensor
3
+ import torch
4
+ from typing import Optional, Tuple, List, Dict
5
+ import x_transformers as xt
6
+
7
+ from conditioning.condition_type import ConditionType
8
+ from conditioning.conditioning_method import ConditioningMethod
9
+ from conditioning.embedded_condition import EmbeddedCondition
10
+ import hyperparameters as hp
11
+ from utils.inspection import printshape
12
+ import config as cfg
13
+
14
+
15
+ class ResidualTokenEmbedding(nn.Module):
16
+
17
+ def __init__(self,
18
+ dim: int,
19
+ num_tokens: int,
20
+ n_layers: int,
21
+ padding_token: Optional[int] = None):
22
+ super().__init__()
23
+ self.padding_token: Optional[int] = padding_token
24
+ self.emb = nn.ModuleList([
25
+ nn.Embedding(num_tokens, dim, padding_idx=self.padding_token)
26
+ for _ in range(n_layers)
27
+ ])
28
+
29
+ for layer in self.emb:
30
+ nn.init.kaiming_normal_(layer.weight)
31
+
32
+ def forward(self, x):
33
+ n_res_layers = x.shape[1]
34
+ assert n_res_layers == len(self.emb)
35
+ token_emb: Tensor = sum( # type: ignore
36
+ [self.emb[i](x[:, i]) for i in range(n_res_layers)])
37
+ return token_emb
38
+
39
+
40
+ def create_sin_embedding(
41
+ positions: torch.Tensor,
42
+ dim: int,
43
+ max_period: float = 10000,
44
+ dtype: torch.dtype = torch.float32,
45
+ ) -> torch.Tensor:
46
+ """Create sinusoidal positional embedding, with shape `[B, T, C]`.
47
+
48
+ Args:
49
+ positions (torch.Tensor): LongTensor of positions.
50
+ dim (int): Dimension of the embedding.
51
+ max_period (float): Maximum period of the cosine/sine functions.
52
+ dtype (torch.dtype or str): dtype to use to generate the embedding.
53
+ Returns:
54
+ torch.Tensor: Sinusoidal positional embedding.
55
+ """
56
+ # We aim for BTC format
57
+ assert dim % 2 == 0
58
+ half_dim = dim // 2
59
+ positions = positions.to(dtype)
60
+ adim = torch.arange(half_dim, device=positions.device,
61
+ dtype=dtype).view(1, 1, -1)
62
+ max_period_tensor = torch.full([],
63
+ max_period,
64
+ device=positions.device,
65
+ dtype=dtype) # avoid sync point
66
+ phase = positions / (max_period_tensor**(adim / (half_dim - 1)))
67
+ return torch.cat([torch.cos(phase), torch.sin(phase)], dim=-1)
68
+
69
+
70
+ class ResidualSinusoidalEmbedding(nn.Module):
71
+
72
+ def __init__(self, dim, theta=10000):
73
+ super().__init__()
74
+ assert dim % 2 == 0
75
+ self.scale = 1
76
+ self.theta = theta
77
+ self.dim = dim
78
+
79
+ def forward(self, x, pos=None, seq_start_pos=None):
80
+ B, K, T = x.shape
81
+ positions = (pos if pos is not None else torch.arange(
82
+ T, device=x.device).view(1, -1, 1))
83
+ pos_emb = create_sin_embedding(positions,
84
+ self.dim,
85
+ max_period=self.theta,
86
+ dtype=x.dtype)
87
+ return pos_emb * self.scale
88
+
89
+
90
+ class ResidualOutputProj(nn.Module):
91
+
92
+ def __init__(self, input_dim: int, output_dim: int, n_layers: int,
93
+ use_bias: bool):
94
+ super().__init__()
95
+ self.linears = nn.ModuleList([
96
+ nn.Linear(input_dim, output_dim, use_bias) for _ in range(n_layers)
97
+ ])
98
+
99
+ def forward(self, x):
100
+ return torch.stack([layer(x) for layer in self.linears], dim=1)
101
+
102
+
103
+ class DropoutModule(nn.Module):
104
+ """Base module for all dropout modules."""
105
+
106
+ def __init__(self, seed: int = 1234):
107
+ super().__init__()
108
+ self.rng = torch.Generator()
109
+ self.rng.manual_seed(seed)
110
+
111
+
112
+ class ConditioningDropout(DropoutModule):
113
+
114
+ def __init__(self, p: float, seed: int = 1234):
115
+ super().__init__(seed=seed)
116
+ self.p = p
117
+
118
+ def forward(self, x: Tensor, mask: Tensor) -> Tuple[Tensor, Tensor]:
119
+ if not self.training:
120
+ return x, mask
121
+ drop = torch.rand(1, generator=self.rng).item() < self.p
122
+ if not drop:
123
+ return x, mask
124
+ return torch.zeros_like(x), torch.zeros_like(mask)
125
+
126
+
127
+ class ClassifierFreeGuidanceDropout(DropoutModule):
128
+ """Classifier Free Guidance dropout.
129
+ All attributes are dropped with the same probability.
130
+
131
+ Args:
132
+ p (float): Probability to apply condition dropout during training.
133
+ seed (int): Random seed.
134
+ """
135
+
136
+ def __init__(self, p: float, seed: int = 1234):
137
+ super().__init__(seed=seed)
138
+ self.p = p
139
+
140
+ def forward(self, samples: List) -> List[str | None]:
141
+ if not self.training:
142
+ return samples
143
+ drop = torch.rand(1, generator=self.rng).item() < self.p
144
+ if not drop:
145
+ return samples
146
+ return [None for _ in range(len(samples))]
147
+
148
+ def __repr__(self):
149
+ return f"ClassifierFreeGuidanceDropout(p={self.p})"
150
+
151
+
152
+ class MusicgenLm(nn.Module):
153
+
154
+ def __init__(self, params: hp.LmParams):
155
+ super().__init__()
156
+
157
+ self.dim: int = params.dim
158
+ self.n_layers: int = params.n_layers
159
+ self.n_heads: int = params.n_heads
160
+ self.card: int = params.card
161
+ self.cross_attend: bool = params.cross_attend
162
+ self.padding_token: Optional[int] = params.padding_token
163
+ self.sep_token: Optional[int] = params.sep_token
164
+
165
+ # if using an extra padding token, input card is card+1
166
+ self.input_card: int = self.card + (
167
+ 1 if self.padding_token is not None and
168
+ self.padding_token >= self.card else 0)
169
+
170
+ if self.sep_token is not None:
171
+ self.input_card += 1
172
+
173
+ self.cfg_coef: float = 3.0
174
+ self.n_q = 4
175
+
176
+ # DECODER
177
+ self.decoder: xt.TransformerWrapper = xt.TransformerWrapper(
178
+ num_tokens=self.input_card,
179
+ max_seq_len=500,
180
+ use_abs_pos_emb=True,
181
+ scaled_sinu_pos_emb=True,
182
+ attn_layers=xt.Decoder(
183
+ dim=self.dim,
184
+ depth=self.n_layers,
185
+ heads=self.n_heads,
186
+ attn_dim_head=64,
187
+ attn_flash=True,
188
+ ff_no_bias=True,
189
+ cross_attend=self.cross_attend,
190
+ ),
191
+ )
192
+ self.decoder.token_emb = ResidualTokenEmbedding( # type: ignore
193
+ self.dim,
194
+ self.input_card,
195
+ self.n_q,
196
+ self.padding_token,
197
+ )
198
+ self.decoder.pos_emb = ResidualSinusoidalEmbedding( # type: ignore
199
+ dim=self.dim)
200
+ self.decoder.to_logits = ResidualOutputProj(self.dim, self.card,
201
+ self.n_q, False)
202
+
203
+ # TODO: this is horrendous, gotta find a fix
204
+ if not self.cross_attend:
205
+ self.nullwav_embeds = torch.load(cfg.weights_dir() /
206
+ "nullwav_embeds.pt")[0]
207
+ else:
208
+ self.nullwav_embeds = None
209
+
210
+ if params.weights is not None:
211
+
212
+ ckpt_state_dict: Dict = torch.load(Path(params.weights),
213
+ map_location=None,
214
+ weights_only=True)
215
+ for key in ("conditioner.output_proj.weight",
216
+ "conditioner.output_proj.bias"):
217
+ if key in ckpt_state_dict:
218
+ ckpt_state_dict.pop(key)
219
+
220
+ if self.sep_token is not None:
221
+ for k in range(self.n_q):
222
+ param_name = f"decoder.token_emb.emb.{k}.weight"
223
+ param = ckpt_state_dict[param_name]
224
+ assert self.sep_token == param.shape[0]
225
+ sep_token_emb = torch.empty_like(param[:1])
226
+ nn.init.kaiming_normal_(sep_token_emb)
227
+ param = torch.cat((param, sep_token_emb))
228
+ ckpt_state_dict[param_name] = param
229
+
230
+ self.load_state_dict(ckpt_state_dict, strict=True)
231
+
232
+ # zero-out padding token in embedding
233
+ # TODO: figure out how to handle doing or not this shit
234
+ if self.cross_attend:
235
+ if self.padding_token is not None:
236
+ with torch.no_grad():
237
+ for k in range(self.n_q):
238
+ self.decoder.token_emb.emb[ # type: ignore
239
+ k].weight[
240
+ self.padding_token] = torch.zeros_like(
241
+ self.decoder.token_emb.
242
+ emb[k]. # type: ignore
243
+ weight[0])
244
+
245
+ def forward(
246
+ self,
247
+ x: Tensor,
248
+ attention_mask: Tensor,
249
+ cross_attention_input: Optional[EmbeddedCondition] = None,
250
+ prepend_embeds: Optional[EmbeddedCondition] = None,
251
+ sum_embeds: Optional[EmbeddedCondition] = None,
252
+ ) -> Tensor:
253
+
254
+ # unpack cross_attention
255
+ if cross_attention_input is not None:
256
+ cross_attention_data = cross_attention_input.data
257
+ cross_attention_mask = cross_attention_input.mask
258
+
259
+ # for samples in the batch in which xatt-conditioning is all padding,
260
+ # place dummy zero-valued conditioning vector, with mask set to True
261
+ if cross_attention_mask is not None:
262
+ masked_rows_coords = cross_attention_mask.sum(dim=-1) == 0
263
+ cross_attention_data[masked_rows_coords] = torch.zeros_like(
264
+ cross_attention_data[masked_rows_coords])
265
+ # cross_attention_mask[masked_rows_coords, ..., 0] = True
266
+ cross_attention_mask[masked_rows_coords] = torch.ones_like(
267
+ cross_attention_mask[masked_rows_coords])
268
+ else:
269
+ if self.cross_attend:
270
+ cross_attention_data = torch.zeros(x.shape[0],
271
+ 1,
272
+ self.dim,
273
+ dtype=torch.float32,
274
+ device=x.device)
275
+ cross_attention_mask = torch.ones(x.shape[0],
276
+ 1,
277
+ dtype=torch.bool,
278
+ device=x.device)
279
+ else:
280
+ cross_attention_data = None
281
+ cross_attention_mask = None
282
+
283
+ # unpack prepend embeddings
284
+ prepend_embeds_data, prepend_embeds_mask = None, None
285
+ if prepend_embeds is not None:
286
+ prepend_embeds_data = prepend_embeds.data
287
+ prepend_embeds_mask = prepend_embeds.mask
288
+
289
+ if self.nullwav_embeds is not None:
290
+ raise RuntimeError(
291
+ "CAREFUL! You are using the musicgen-melody hack!")
292
+ nwe = self.nullwav_embeds.repeat(prepend_embeds_data.shape[0],
293
+ 1, 1).to(prepend_embeds_data)
294
+ prepend_embeds_data = torch.cat((nwe, prepend_embeds_data),
295
+ dim=1)
296
+ if prepend_embeds_mask is not None:
297
+ nwm = torch.ones(nwe.shape[0],
298
+ nwe.shape[1],
299
+ device=nwe.device,
300
+ dtype=torch.bool)
301
+ prepend_embeds_mask = torch.cat((nwm, prepend_embeds_mask),
302
+ dim=1)
303
+
304
+ # unpack sum embeddings
305
+ sum_embeds_data, sum_embeds_mask = None, None
306
+ if sum_embeds is not None:
307
+ sum_embeds_data = sum_embeds.data
308
+ sum_embeds_mask = sum_embeds.mask
309
+ if sum_embeds_mask is not None:
310
+ sum_embeds_data[~sum_embeds_mask] = 0
311
+
312
+ assert attention_mask.shape == torch.Size([x.shape[0], x.shape[-1]])
313
+
314
+ # create manually positional embedding indices
315
+ positions = torch.zeros_like(attention_mask, dtype=torch.int64)
316
+ B, S = positions.shape
317
+ first_valid_indices = torch.argmax(attention_mask.long(), dim=-1)
318
+ sequence = torch.arange(S, device=positions.device).unsqueeze(0).expand(
319
+ B, S)
320
+ mask = torch.arange(S, device=positions.device).unsqueeze(
321
+ 0) >= first_valid_indices.unsqueeze(1)
322
+ positions[mask] = (sequence - first_valid_indices.unsqueeze(1))[mask]
323
+ positions = positions.unsqueeze(-1)
324
+
325
+ # create mask
326
+ mask = torch.ones((x.shape[0], x.shape[-1]),
327
+ dtype=torch.bool,
328
+ device=x.device)
329
+
330
+ # positional embeddings are manually applied to prepend data
331
+ # if not self.cross_attend and prepend_embeds_data is not None:
332
+ if prepend_embeds_data is not None:
333
+ positions += prepend_embeds_data.shape[-2]
334
+ prepend_embeds_data = prepend_embeds_data + self.decoder.pos_emb(
335
+ prepend_embeds_data[..., 0].unsqueeze(1))
336
+
337
+ # forward through decoder
338
+ logits = self.decoder(
339
+ x,
340
+ # mask=mask,
341
+ mask=attention_mask,
342
+ pos=positions,
343
+ context=cross_attention_data,
344
+ context_mask=cross_attention_mask,
345
+ prepend_embeds=prepend_embeds_data,
346
+ prepend_mask=prepend_embeds_mask,
347
+ sum_embeds=sum_embeds_data,
348
+ )
349
+ # if something is prepended to the input, remove it from the logits
350
+ if prepend_embeds_data is not None:
351
+ logits = logits[:, :, prepend_embeds_data.shape[-2]:, :]
352
+
353
+ return logits
354
+
355
+
356
+ if __name__ == "__main__":
357
+ # params = hp.PretrainedSmallLmParams()
358
+
359
+ # model = params.instantiate()
360
+
361
+ # model.eval()
362
+ res = ResidualSinusoidalEmbedding(1024)
363
+
364
+ x = torch.rand(1, 1024, 3)
365
+ emb = res(x)
366
+ print(f'{emb.shape=}')
367
+ print(f'{emb=}')
models/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
models/quantization/base.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ Base class for all quantizers.
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ import typing as tp
12
+
13
+ import torch
14
+ from torch import nn
15
+
16
+
17
+ @dataclass
18
+ class QuantizedResult:
19
+ quantized_layers: torch.Tensor # q1, q1+q2, q1+q2+q3 ...
20
+ x: torch.Tensor # final quantized vector: Σ(q_i) == quantized_layers[-1]
21
+ codes: torch.Tensor
22
+ bandwidth: torch.Tensor # bandwidth in kb/s used, per batch item.
23
+ penalty: tp.Optional[torch.Tensor] = None
24
+ sum_loss: tp.Optional[torch.Tensor] = None
25
+ metrics: dict = field(default_factory=dict)
26
+
27
+
28
+ class BaseQuantizer(nn.Module):
29
+ """Base class for quantizers.
30
+ """
31
+
32
+ def forward(self, x: torch.Tensor, frame_rate: int) -> QuantizedResult:
33
+ """
34
+ Given input tensor x, returns first the quantized (or approximately quantized)
35
+ representation along with quantized codes, bandwidth, and any penalty term for the loss.
36
+ Finally, this returns a dict of metrics to update logging etc.
37
+ Frame rate must be passed so that the bandwidth is properly computed.
38
+ """
39
+ raise NotImplementedError()
40
+
41
+ def encode(self, x: torch.Tensor) -> torch.Tensor:
42
+ """Encode a given input tensor with the specified sample rate at the given bandwidth."""
43
+ raise NotImplementedError()
44
+
45
+ def decode(self, codes: torch.Tensor) -> torch.Tensor:
46
+ """Decode the given codes to the quantized representation."""
47
+ raise NotImplementedError()
48
+
49
+ @property
50
+ def total_codebooks(self):
51
+ """Total number of codebooks."""
52
+ raise NotImplementedError()
53
+
54
+ @property
55
+ def num_codebooks(self):
56
+ """Number of active codebooks."""
57
+ raise NotImplementedError()
58
+
59
+ def set_num_codebooks(self, n: int):
60
+ """Set the number of active codebooks."""
61
+ raise NotImplementedError()
62
+
63
+
64
+ class DummyQuantizer(BaseQuantizer):
65
+ """Fake quantizer that actually does not perform any quantization.
66
+ """
67
+
68
+ def __init__(self):
69
+ super().__init__()
70
+
71
+ def forward(self, x: torch.Tensor, frame_rate: int):
72
+ q = x.unsqueeze(1)
73
+ return QuantizedResult(
74
+ x, q,
75
+ torch.tensor(q.numel() * 32 * frame_rate / 1000 / len(x)).to(x))
76
+
77
+ def encode(self, x: torch.Tensor) -> torch.Tensor:
78
+ """Encode a given input tensor with the specified sample rate at the given bandwidth.
79
+ In the case of the DummyQuantizer, the codes are actually identical
80
+ to the input and resulting quantized representation as no quantization is done.
81
+ """
82
+ return x.unsqueeze(1)
83
+
84
+ def decode(self, codes: torch.Tensor) -> torch.Tensor:
85
+ """Decode the given codes to the quantized representation.
86
+ In the case of the DummyQuantizer, the codes are actually identical
87
+ to the input and resulting quantized representation as no quantization is done.
88
+ """
89
+ return codes.squeeze(1)
90
+
91
+ @property
92
+ def total_codebooks(self):
93
+ """Total number of codebooks."""
94
+ return 1
95
+
96
+ @property
97
+ def num_codebooks(self):
98
+ """Total number of codebooks."""
99
+ return self.total_codebooks
100
+
101
+ def set_num_codebooks(self, n: int):
102
+ """Set the number of active codebooks."""
103
+ raise AttributeError(
104
+ "Cannot override the number of codebooks for the dummy quantizer")
models/quantization/core_vq.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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.detach(), 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
+ dists = -(diffs**2).sum(dim=-1)
62
+
63
+ buckets = dists.max(dim=-1).indices
64
+ bins = torch.bincount(buckets, minlength=num_clusters)
65
+ zero_mask = bins == 0
66
+ bins_min_clamped = bins.masked_fill(zero_mask, 1)
67
+
68
+ new_means = buckets.new_zeros(num_clusters, dim, dtype=dtype)
69
+ new_means.scatter_add_(0, repeat(buckets, "n -> n d", d=dim), samples)
70
+ new_means = new_means / bins_min_clamped[..., None]
71
+
72
+ means = torch.where(zero_mask[..., None], means, new_means)
73
+
74
+ return means, bins
75
+
76
+
77
+ def orthogonal_loss_fn(t):
78
+ # eq (2) from https://arxiv.org/abs/2112.00384
79
+ n = t.shape[0]
80
+ normed_codes = l2norm(t)
81
+ identity = torch.eye(n, device=t.device)
82
+ cosine_sim = einsum("i d, j d -> i j", normed_codes, normed_codes)
83
+ return ((cosine_sim - identity)**2).sum() / (n**2)
84
+
85
+
86
+ class EuclideanCodebook(nn.Module):
87
+ """Codebook with Euclidean distance.
88
+
89
+ Args:
90
+ dim (int): Dimension.
91
+ codebook_size (int): Codebook size.
92
+ kmeans_init (bool): Whether to use k-means to initialize the codebooks.
93
+ If set to true, run the k-means algorithm on the first training batch and use
94
+ the learned centroids as initialization.
95
+ kmeans_iters (int): Number of iterations used for k-means algorithm at initialization.
96
+ decay (float): Decay for exponential moving average over the codebooks.
97
+ epsilon (float): Epsilon value for numerical stability.
98
+ threshold_ema_dead_code (int): Threshold for dead code expiration. Replace any codes
99
+ that have an exponential moving average cluster size less than the specified threshold with
100
+ randomly selected vector from the current batch.
101
+ """
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[
116
+ tp.Callable[..., torch.Tensor],
117
+ tp.Any] = uniform_init if not kmeans_init else torch.zeros
118
+ embed = init_fn(codebook_size, dim)
119
+
120
+ self.codebook_size = codebook_size
121
+
122
+ self.kmeans_iters = kmeans_iters
123
+ self.epsilon = epsilon
124
+ self.threshold_ema_dead_code = threshold_ema_dead_code
125
+
126
+ self.register_buffer("inited", torch.Tensor([not kmeans_init]))
127
+ self.register_buffer("cluster_size", torch.zeros(codebook_size))
128
+ self.register_buffer("embed", embed)
129
+ self.register_buffer("embed_avg", embed.clone())
130
+
131
+ # @torch.jit.ignore
132
+ def init_embed_(self, data):
133
+ if self.inited:
134
+ return
135
+
136
+ embed, cluster_size = kmeans(data, self.codebook_size,
137
+ self.kmeans_iters)
138
+ self.embed.data.copy_(embed)
139
+ self.embed_avg.data.copy_(embed.clone())
140
+ self.cluster_size.data.copy_(cluster_size)
141
+ self.inited.data.copy_(torch.Tensor([True]))
142
+ # Make sure all buffers across workers are in sync after initialization
143
+ # flashy.distrib.broadcast_tensors(self.buffers())
144
+
145
+ def replace_(self, samples, mask):
146
+ modified_codebook = torch.where(
147
+ mask[..., None], sample_vectors(samples, self.codebook_size),
148
+ self.embed)
149
+ self.embed.data.copy_(modified_codebook)
150
+
151
+ def expire_codes_(self, batch_samples):
152
+ if self.threshold_ema_dead_code == 0:
153
+ return
154
+
155
+ expired_codes = self.cluster_size < self.threshold_ema_dead_code
156
+ if not torch.any(expired_codes):
157
+ return
158
+
159
+ batch_samples = rearrange(batch_samples, "... d -> (...) d")
160
+ self.replace_(batch_samples, mask=expired_codes)
161
+ # flashy.distrib.broadcast_tensors(self.buffers())
162
+
163
+ def preprocess(self, x):
164
+ x = rearrange(x, "... d -> (...) d")
165
+ return x
166
+
167
+ def quantize(self, x):
168
+ embed = self.embed.t()
169
+ dist = -(x.pow(2).sum(1, keepdim=True) - 2 * x @ embed +
170
+ embed.pow(2).sum(0, keepdim=True))
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().detach(), self.decay)
212
+ cluster_size = (laplace_smoothing(
213
+ self.cluster_size, self.codebook_size, self.epsilon) *
214
+ self.cluster_size.sum())
215
+ embed_normalized = self.embed_avg / cluster_size.unsqueeze(1)
216
+ self.embed.data.copy_(embed_normalized)
217
+
218
+ return quantize, embed_ind
219
+
220
+
221
+ class VectorQuantization(nn.Module):
222
+ """Vector quantization implementation.
223
+ Currently supports only euclidean distance.
224
+
225
+ Args:
226
+ dim (int): Dimension
227
+ codebook_size (int): Codebook size
228
+ codebook_dim (int): Codebook dimension. If not defined, uses the specified dimension in dim.
229
+ decay (float): Decay for exponential moving average over the codebooks.
230
+ epsilon (float): Epsilon value for numerical stability.
231
+ kmeans_init (bool): Whether to use kmeans to initialize the codebooks.
232
+ kmeans_iters (int): Number of iterations used for kmeans initialization.
233
+ threshold_ema_dead_code (int):
234
+ channels_last (bool): Channels are the last dimension in the input tensors.
235
+ commitment_weight (float): Weight for commitment loss.
236
+ orthogonal_reg_weight (float): Orthogonal regularization weights.
237
+ orthogonal_reg_active_codes_only (bool): Apply orthogonal regularization only on active codes.
238
+ orthogonal_reg_max_codes (optional int): Maximum number of codes to consider
239
+ for orthogonal regularization.
240
+ threshold_ema_dead_code (int): Threshold for dead code expiration. Replace any codes
241
+ that have an exponential moving average cluster size less than the specified threshold with
242
+ randomly selected vector from the current batch.
243
+ """
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)
266
+ if requires_projection else nn.Identity())
267
+ self.project_out = (nn.Linear(_codebook_dim, dim)
268
+ if requires_projection else nn.Identity())
269
+
270
+ self.epsilon = epsilon
271
+ self.commitment_weight = commitment_weight
272
+
273
+ self.orthogonal_reg_weight = orthogonal_reg_weight
274
+ self.orthogonal_reg_active_codes_only = orthogonal_reg_active_codes_only
275
+ self.orthogonal_reg_max_codes = orthogonal_reg_max_codes
276
+
277
+ self._codebook = EuclideanCodebook(
278
+ dim=_codebook_dim,
279
+ codebook_size=codebook_size,
280
+ kmeans_init=kmeans_init,
281
+ kmeans_iters=kmeans_iters,
282
+ decay=decay,
283
+ epsilon=epsilon,
284
+ threshold_ema_dead_code=threshold_ema_dead_code)
285
+ self.codebook_size = codebook_size
286
+
287
+ self.channels_last = channels_last
288
+
289
+ @property
290
+ def codebook(self):
291
+ return self._codebook.embed
292
+
293
+ @property
294
+ def inited(self):
295
+ return self._codebook.inited
296
+
297
+ def _preprocess(self, x):
298
+ if not self.channels_last:
299
+ x = rearrange(x, "b d n -> b n d")
300
+ return x
301
+
302
+ def _postprocess(self, quantize):
303
+ if not self.channels_last:
304
+ quantize = rearrange(quantize, "b n d -> b d n")
305
+ return quantize
306
+
307
+ def encode(self, x):
308
+ x = self._preprocess(x)
309
+ x = self.project_in(x)
310
+ embed_in = self._codebook.encode(x)
311
+ return embed_in
312
+
313
+ def decode(self, embed_ind):
314
+ quantize = self._codebook.decode(embed_ind)
315
+ quantize = self.project_out(quantize)
316
+ quantize = self._postprocess(quantize)
317
+ return quantize
318
+
319
+ def forward(self, x):
320
+ device = x.device
321
+ x = self._preprocess(x)
322
+
323
+ x = self.project_in(x)
324
+ quantize, embed_ind = self._codebook(x)
325
+
326
+ if self.training:
327
+ quantize = x + (quantize - x).detach()
328
+
329
+ loss = torch.tensor([0.0], device=device, requires_grad=self.training)
330
+
331
+ if self.training:
332
+ if self.commitment_weight > 0:
333
+ commit_loss = F.mse_loss(quantize.detach(), x)
334
+ loss = loss + commit_loss * self.commitment_weight
335
+
336
+ if self.orthogonal_reg_weight > 0:
337
+ codebook = self.codebook
338
+
339
+ if self.orthogonal_reg_active_codes_only:
340
+ # only calculate orthogonal loss for the activated codes for this batch
341
+ unique_code_ids = torch.unique(embed_ind)
342
+ codebook = codebook[unique_code_ids]
343
+
344
+ num_codes = codebook.shape[0]
345
+ if exists(self.orthogonal_reg_max_codes
346
+ ) and num_codes > self.orthogonal_reg_max_codes:
347
+ rand_ids = torch.randperm(
348
+ num_codes,
349
+ device=device)[:self.orthogonal_reg_max_codes]
350
+ codebook = codebook[rand_ids]
351
+
352
+ orthogonal_reg_loss = orthogonal_loss_fn(codebook)
353
+ loss = loss + orthogonal_reg_loss * self.orthogonal_reg_weight
354
+
355
+ quantize = self.project_out(quantize)
356
+ quantize = self._postprocess(quantize)
357
+
358
+ return quantize, embed_ind, loss
359
+
360
+
361
+ class ResidualVectorQuantization(nn.Module):
362
+ """Residual vector quantization implementation.
363
+
364
+ Follows Algorithm 1. in https://arxiv.org/pdf/2107.03312.pdf
365
+ """
366
+
367
+ def __init__(self, *, num_quantizers, **kwargs):
368
+ super().__init__()
369
+ self.layers = nn.ModuleList(
370
+ [VectorQuantization(**kwargs) for _ in range(num_quantizers)])
371
+
372
+ def forward(self, x, n_q: tp.Optional[int] = None):
373
+ # quantized_out = 0.0
374
+ quantized_out = torch.tensor(0.0)
375
+
376
+ residual = x
377
+
378
+ all_losses = []
379
+ all_indices = []
380
+ quantized_list = []
381
+
382
+ n_q = n_q or len(self.layers)
383
+
384
+ for i, layer in enumerate(self.layers[:n_q]):
385
+ quantized, indices, loss = layer(residual)
386
+ residual = residual - quantized
387
+ quantized_out = quantized_out + quantized
388
+
389
+ # to train sum_loss, also return quantization layers
390
+ quantized_list.append(quantized_out)
391
+
392
+ all_indices.append(indices)
393
+ all_losses.append(loss)
394
+
395
+ out_losses, out_indices = map(torch.stack, (all_losses, all_indices))
396
+ out_layers = torch.stack(quantized_list, dim=1)
397
+ assert out_layers[:, -1, ...].equal(quantized_out)
398
+ return out_layers, quantized_out, out_indices, out_losses
399
+
400
+ def encode(self,
401
+ x: torch.Tensor,
402
+ n_q: tp.Optional[int] = None) -> torch.Tensor:
403
+ residual = x
404
+ all_indices = []
405
+ n_q = n_q or len(self.layers)
406
+ for layer in self.layers[:n_q]:
407
+ indices = layer.encode(residual)
408
+ quantized = layer.decode(indices)
409
+ residual = residual - quantized
410
+ all_indices.append(indices)
411
+ out_indices = torch.stack(all_indices)
412
+ return out_indices
413
+
414
+ def decode(self, q_indices: torch.Tensor) -> torch.Tensor:
415
+ quantized_out = torch.tensor(0.0, device=q_indices.device)
416
+ for i, indices in enumerate(q_indices):
417
+ layer = self.layers[i]
418
+ quantized = layer.decode(indices)
419
+ quantized_out = quantized_out + quantized
420
+ return quantized_out
models/quantization/vq.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 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
+
36
+ def __init__(
37
+ self,
38
+ dimension: int = 256,
39
+ n_q: int = 8,
40
+ q_dropout: bool = False,
41
+ bins: int = 1024,
42
+ decay: float = 0.99,
43
+ kmeans_init: bool = True,
44
+ kmeans_iters: int = 10,
45
+ threshold_ema_dead_code: int = 2,
46
+ orthogonal_reg_weight: float = 0.0,
47
+ orthogonal_reg_active_codes_only: bool = False,
48
+ orthogonal_reg_max_codes: tp.Optional[int] = None,
49
+ ):
50
+ super().__init__()
51
+ self.max_n_q = n_q
52
+ self.n_q = n_q
53
+ self.q_dropout = q_dropout
54
+ self.dimension = dimension
55
+ self.bins = bins
56
+ self.decay = decay
57
+ self.kmeans_init = kmeans_init
58
+ self.kmeans_iters = kmeans_iters
59
+ self.threshold_ema_dead_code = threshold_ema_dead_code
60
+ self.orthogonal_reg_weight = orthogonal_reg_weight
61
+ self.orthogonal_reg_active_codes_only = orthogonal_reg_active_codes_only
62
+ self.orthogonal_reg_max_codes = orthogonal_reg_max_codes
63
+ self.vq = ResidualVectorQuantization(
64
+ dim=self.dimension,
65
+ codebook_size=self.bins,
66
+ num_quantizers=self.n_q,
67
+ decay=self.decay,
68
+ kmeans_init=self.kmeans_init,
69
+ kmeans_iters=self.kmeans_iters,
70
+ threshold_ema_dead_code=self.threshold_ema_dead_code,
71
+ orthogonal_reg_weight=self.orthogonal_reg_weight,
72
+ orthogonal_reg_active_codes_only=self.
73
+ orthogonal_reg_active_codes_only,
74
+ orthogonal_reg_max_codes=self.orthogonal_reg_max_codes,
75
+ channels_last=False)
76
+
77
+ def forward(self, x: torch.Tensor, frame_rate: int):
78
+ n_q = self.n_q
79
+ if self.training and self.q_dropout:
80
+ n_q = int(torch.randint(1, self.n_q + 1, (1,)).item())
81
+ bw_per_q = math.log2(self.bins) * frame_rate / 1000 #kbps
82
+ # to support sum_loss, we also return quantized layers
83
+ quantized_layers, quantized, codes, commit_loss = self.vq(x, n_q=n_q)
84
+ codes = codes.transpose(0, 1)
85
+ # codes is [B, K, T], with T frames, K nb of codebooks.
86
+ bw = torch.tensor(n_q * bw_per_q).to(x)
87
+ return QuantizedResult(quantized_layers,
88
+ quantized,
89
+ codes,
90
+ bw,
91
+ penalty=torch.mean(commit_loss))
92
+
93
+ def encode(self, x: torch.Tensor) -> torch.Tensor:
94
+ """Encode a given input tensor with the specified frame rate at the given bandwidth.
95
+ The RVQ encode method sets the appropriate number of quantizer to use
96
+ and returns indices for each quantizer.
97
+ """
98
+ n_q = self.n_q
99
+ codes = self.vq.encode(x, n_q=n_q)
100
+ codes = codes.transpose(0, 1)
101
+ # codes is [B, K, T], with T frames, K nb of codebooks.
102
+ return codes
103
+
104
+ def decode(self, codes: torch.Tensor) -> torch.Tensor:
105
+ """Decode the given codes to the quantized representation."""
106
+ # codes is [B, K, T], with T frames, K nb of codebooks, vq.decode expects [K, B, T].
107
+ codes = codes.transpose(0, 1)
108
+ quantized = self.vq.decode(codes)
109
+ return quantized
110
+
111
+ @property
112
+ def total_codebooks(self):
113
+ return self.max_n_q
114
+
115
+ @property
116
+ def num_codebooks(self):
117
+ return self.n_q
118
+
119
+ def set_num_codebooks(self, n: int):
120
+ assert n > 0 and n <= self.max_n_q
121
+ self.n_q = n
requirements.txt ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ git+https://github.com/TEAMuP-dev/pyharp.git@v0.3.0
2
+ # model-specific deps:
3
+ torch
4
+ torchaudio
5
+ transformers
6
+ sentencepiece
7
+ safetensors
8
+ lightning
9
+ x-transformers==1.26.0
10
+ frozendict
11
+ loralib
12
+ soundfile
13
+ numpy
14
+ matplotlib
15
+ einops
train_stage_drums.py ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Train STAGE for DRUMS generation, with either a mixture or a metronome track as context
3
+ """
4
+ import torch
5
+ import lightning as L
6
+
7
+ import hyperparameters as hp
8
+ from conditioning.condition_type import ConditionType
9
+ from conditioning.conditioning_method import ConditioningMethod
10
+ from conditioning.prompt_processor import InterleavedContextPromptProcessor
11
+ from conditioning.t5embedder import T5EmbedderGPU
12
+ from data.stem import Stem
13
+ from training.train import train
14
+ import config as cfg
15
+
16
+ RUN_NAME = "stage-drums"
17
+
18
+ # distributed strategy - set to DDP to train on multi-gpu machines
19
+ STRATEGY = None
20
+
21
+
22
+ def launch_train():
23
+ lm_params = hp.PretrainedSmallLmParams(sep_token=2049)
24
+
25
+ conditioning_params = hp.ConditioningParams(
26
+ embedder_types={
27
+ ConditionType.DESCRIPTION: T5EmbedderGPU,
28
+ },
29
+ conditioning_methods={
30
+ ConditionType.DESCRIPTION: ConditioningMethod.CROSS_ATTENTION,
31
+ },
32
+ conditioning_dropout=0.5)
33
+
34
+ prompt_params = hp.PromptProcessorParams(
35
+ keep_only_valid_steps=True,
36
+ model_class=InterleavedContextPromptProcessor,
37
+ context_dropout=0.1)
38
+
39
+ encodec_params = hp.pretrained_encodec_meta_32khz_params
40
+
41
+ model_params = hp.MusicgenParams(encodec_params=encodec_params,
42
+ prompt_processor_params=prompt_params,
43
+ conditioning_params=conditioning_params,
44
+ lm_params=lm_params)
45
+ batch_size_train = 2
46
+ max_steps = 100_000
47
+ max_time = "00:24:00:00"
48
+
49
+ n_samples_per_epoch = 10_000
50
+
51
+ accumulate_grad_batches = 4
52
+
53
+ dataset_params = hp.StemmedDatasetParams(
54
+ clip_length_in_seconds=10,
55
+ sample_rate=32_000,
56
+ root_dir=cfg.moises_path(),
57
+ single_stem=True,
58
+ target_stem=Stem.DRUMS,
59
+ min_context_seconds=5,
60
+ use_style_conditioning=True,
61
+ use_beat_conditioning=True,
62
+ type_of_context="stems or beats",
63
+ add_click=False,
64
+ sync_chunks=False,
65
+ bpm_in_caption=False,
66
+ batch_size_train=batch_size_train,
67
+ batch_size_test=12,
68
+ num_workers=11,
69
+ speed_transform_p=0.5,
70
+ pitch_transform_p=0.5,
71
+ n_samples_per_epoch=n_samples_per_epoch)
72
+
73
+ train(
74
+ model_params=model_params,
75
+ dataset_params=dataset_params,
76
+ max_time=max_time,
77
+ max_steps=max_steps,
78
+ accumulate_grad_batches=accumulate_grad_batches,
79
+ run_name=RUN_NAME,
80
+ distributed_strategy=STRATEGY,
81
+ log=True,
82
+ kill_on_end=False,
83
+ )
84
+
85
+
86
+ if __name__ == "__main__":
87
+ launch_train()
training/callback.py ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ import pickle
3
+ import librosa
4
+ import lightning as L
5
+ from lightning.pytorch.utilities.seed import isolate_rng
6
+ from lightning.pytorch.callbacks import Callback
7
+ from torch import Tensor
8
+ from typing import Dict, Any
9
+ from pathlib import Path
10
+ import random
11
+ import wandb
12
+ import torch
13
+ from lightning.pytorch.utilities import rank_zero_only
14
+
15
+ from conditioning.beat_embedder import Beat
16
+ from utils import audio
17
+ import config as cfg
18
+ # from cocola.contrastive_model import CoCola
19
+ # from cocola.feature_extraction import CoColaFeatureExtractor
20
+ # from cocola import constants as cocola_constants
21
+ # from utils.logging import upload_to_s3
22
+
23
+ # Custom progress bar to refresh only twice per epoch
24
+ # class SlowProgressBar(TQDMProgressBar):
25
+
26
+ # def init_train_tqdm(self):
27
+ # bar = super().init_train_tqdm()
28
+ # # Refresh rate: update the bar only twice per epoch
29
+ # bar.refresh_rate = self.total_train_batches // 2
30
+ # return bar
31
+
32
+
33
+ class SaveDemoOnValidationCallback(Callback):
34
+
35
+ @rank_zero_only
36
+ def __init__(self, save_path: Path, save_model: bool, n_demos: int):
37
+ self.save_path: Path = save_path
38
+ self.save_model: bool = save_model
39
+ self.save_path.mkdir(parents=True, exist_ok=True)
40
+ self.n_demos: int = n_demos
41
+ self.executor = ThreadPoolExecutor(max_workers=3)
42
+
43
+ # @rank_zero_only
44
+ # def on_validation_end(self, trainer: L.Trainer,
45
+ # pl_module: L.LightningModule) -> None:
46
+ # if self.save_model:
47
+ # if not (self.save_path / "params.pkl").exists():
48
+ # with open(self.save_path / "params.pkl", "wb") as f:
49
+ # pickle.dump(pl_module.params, f)
50
+
51
+ # checkpoint_path = self.save_path / f"step={trainer.global_step}.ckpt"
52
+ # trainer.save_checkpoint(checkpoint_path, weights_only=False)
53
+
54
+ # # upload checkpoint to S3
55
+ # run_name = self.save_path.name
56
+ # future = self.executor.submit(
57
+ # upload_to_s3,
58
+ # checkpoint_path,
59
+ # "lag-modular",
60
+ # f"checkpoints/{run_name}/{checkpoint_path.name}",
61
+ # )
62
+
63
+ # def log_upload_status(fut):
64
+ # try:
65
+ # success = fut.result()
66
+ # status = "successful" if success else "failed"
67
+ # print(f"Checkpoint upload on S3 {status}")
68
+ # except Exception as e:
69
+ # print(f"Error during upload of {checkpoint_path}: {e}")
70
+
71
+ # future.add_done_callback(log_upload_status)
72
+
73
+ @rank_zero_only
74
+ def on_validation_epoch_start(self, trainer: L.Trainer,
75
+ pl_module: L.LightningModule) -> None:
76
+
77
+ total_batches = trainer.num_val_batches[0]
78
+ assert isinstance(total_batches, int)
79
+ self.random_batch_idx = random.randint(0, total_batches - 1)
80
+
81
+ def on_train_end(self, trainer, pl_module):
82
+ print("Shutting down uploader")
83
+ self.executor.shutdown(wait=True)
84
+ print("Uploader shut down")
85
+
86
+ @rank_zero_only
87
+ def on_validation_batch_end(self,
88
+ trainer: L.Trainer,
89
+ pl_module: L.LightningModule,
90
+ outputs: Any,
91
+ batch: Dict[str, Any],
92
+ batch_idx: int,
93
+ dataloader_idx: int = 0) -> None:
94
+ if batch_idx == self.random_batch_idx:
95
+ self.generate_and_save_demo(trainer, pl_module, batch)
96
+
97
+ def generate_and_save_demo(
98
+ self,
99
+ trainer: L.Trainer,
100
+ pl_module: L.LightningModule,
101
+ batch: Dict[str, Any],
102
+ ):
103
+ logger = trainer.logger.experiment if trainer.logger is not None else None # type: ignore
104
+
105
+ save_path: Path = self.save_path / "demos" / f"step={trainer.global_step}"
106
+ save_path.mkdir(parents=True, exist_ok=True)
107
+
108
+ # batch_size = min(len(batch["target"]), self.n_demos)
109
+ batch_size = len(batch["target"])
110
+ if self.n_demos < len(batch["target"]):
111
+ chosen_samples = random.sample(list(range(batch_size)),
112
+ self.n_demos)
113
+ else:
114
+ chosen_samples = list(range(batch_size))
115
+ n_samples = len(chosen_samples)
116
+
117
+ with isolate_rng(include_cuda=True):
118
+ # extract conditioning data from batch
119
+ target = batch.get("target")
120
+ assert target is not None
121
+ context = batch.get("context")
122
+ context_dropout_mask = None
123
+ if context is not None:
124
+ if isinstance(context, list):
125
+ context = [
126
+ c for i, c in enumerate(context) if i in chosen_samples
127
+ ]
128
+ else:
129
+ assert isinstance(context, Tensor)
130
+ context = context[chosen_samples]
131
+ context_dropout_mask = torch.full((len(context),),
132
+ True,
133
+ dtype=torch.bool)
134
+ # context_dropout_mask[:n_samples // 2] = False # todo uncomment me
135
+ style = batch.get("style")
136
+ if style is not None:
137
+ style = style[chosen_samples]
138
+ description = batch.get("description")
139
+ if description is not None:
140
+ description = [
141
+ v for i, v in enumerate(description) if i in chosen_samples
142
+ ]
143
+ beat = batch.get("beat")
144
+ if beat is not None:
145
+ beat = [v for i, v in enumerate(beat) if i in chosen_samples]
146
+
147
+ # generate
148
+ L.seed_everything(42)
149
+ with torch.autocast(device_type="cuda"):
150
+ gen_audio = pl_module.generate(
151
+ n_samples=n_samples,
152
+ gen_seconds=10,
153
+ prompt=None,
154
+ context=context,
155
+ context_dropout_mask=context_dropout_mask,
156
+ style=style,
157
+ beat=beat,
158
+ description=description,
159
+ prog_bar=cfg.running_locally())
160
+
161
+ # compute cocola score between generated audio and context/style
162
+ '''if style is not None or context is not None:
163
+ cocola = CoCola(
164
+ embedding_mode=cocola_constants.EmbeddingMode.BOTH)
165
+ cocola.load_state_dict(
166
+ torch.load(cfg.weights_dir() / "cocola-weights.pt",
167
+ weights_only=True))
168
+ # cocola = CoCola.load_from_checkpoint(cfg.weights_dir() /
169
+ # "cocola.pt",
170
+ # map_location="cpu").eval()
171
+ cocola.eval()
172
+ cocola.set_embedding_mode(cocola_constants.EmbeddingMode.BOTH)
173
+ feature_extractor = CoColaFeatureExtractor()
174
+ gen_features = feature_extractor(gen_audio.cpu())
175
+ if style is not None:
176
+ assert len(gen_audio) == len(style)
177
+ style_features = feature_extractor(style.cpu())
178
+ style_score = cocola.score(gen_features, style_features)
179
+ if context is not None and isinstance(context, Tensor):
180
+ assert len(gen_audio) == len(context)
181
+ context_features = feature_extractor(context.cpu())
182
+ context_score = cocola.score(gen_features, context_features)'''
183
+
184
+ columns = ([
185
+ s for s in ("description", "context", "beat") if s in batch
186
+ ] + ["generated"])
187
+ if "context" in columns:
188
+ columns.append("mix")
189
+ # mixed_audio = gen_audio + context
190
+ if "beat" in columns and "context" in columns:
191
+ columns.append("context with beat")
192
+ # if "style" in columns:
193
+ # columns.append("style cocola score")
194
+ # if "context" in columns and isinstance(context, Tensor):
195
+ # columns.append("context cocola score")
196
+
197
+ data = [[] for _ in range(n_samples)]
198
+
199
+ # for idx in range(n_samples):
200
+ for i, idx in enumerate(chosen_samples):
201
+ gen_filename: Path = save_path / f"demo{i}_gen.wav"
202
+ audio.save_audio(gen_audio[i], gen_filename)
203
+
204
+ if description is not None:
205
+ desc_filename: Path = save_path / f"demo{i}_description.txt"
206
+ desc_filename.write_text(description[i])
207
+ '''if style is not None:
208
+ style_filename: Path = save_path / f"demo{i}_style.wav"
209
+ audio.save_audio(style[i], style_filename)'''
210
+ if context is not None and context_dropout_mask[i]:
211
+ context_filename: Path = save_path / f"demo{i}_context.wav"
212
+ mix_filename: Path = save_path / f"demo{i}_mix.wav"
213
+ audio.save_audio(context[i], context_filename)
214
+ mix = torch.nn.functional.pad(
215
+ context[i],
216
+ (0, gen_audio[i].shape[-1] - context[i].shape[-1]),
217
+ value=0) + gen_audio[i]
218
+ audio.save_audio(mix, mix_filename)
219
+ if "target" in batch:
220
+ target_filename: Path = save_path / f"demo{i}_target.wav"
221
+ audio.save_audio(target[idx], target_filename)
222
+
223
+ if beat is not None:
224
+ beat_filename: Path = save_path / f"demo{i}_beat.wav"
225
+ b: Beat = beat[i]
226
+ beats = (b.beats / pl_module.sample_rate).cpu().numpy()
227
+ downbeats = (b.downbeats / pl_module.sample_rate).cpu().numpy()
228
+ clicks = librosa.clicks(times=beats,
229
+ sr=pl_module.sample_rate,
230
+ click_freq=1000,
231
+ length=gen_audio[i].shape[-1])
232
+ high_clicks = librosa.clicks(times=downbeats,
233
+ sr=pl_module.sample_rate,
234
+ click_freq=2000,
235
+ length=gen_audio[i].shape[-1])
236
+ gen_with_beat = gen_audio[i].cpu() + (clicks *
237
+ 0.5) + (high_clicks * 0.5)
238
+ audio.save_audio(gen_with_beat, beat_filename)
239
+ if context is not None and context_dropout_mask[i]:
240
+ context_with_beats_filename: Path = (
241
+ save_path / f"demo{i}_context_with_beats.wav")
242
+ context_with_beats = torch.nn.functional.pad(
243
+ context[i].cpu(),
244
+ (0, len(clicks) - context[i].shape[-1])) + (
245
+ clicks * 0.5) + (high_clicks * 0.5)
246
+ audio.save_audio(context_with_beats,
247
+ context_with_beats_filename)
248
+
249
+ if logger is not None:
250
+ # if pl_module.logger is not None:
251
+ row = []
252
+ if description is not None:
253
+ row.append(description[i])
254
+ '''if style is not None:
255
+ row.append(
256
+ wandb.Audio(str(style_filename),
257
+ sample_rate=pl_module.sample_rate))'''
258
+ if context is not None:
259
+ assert context_dropout_mask is not None
260
+ if context_dropout_mask[i]:
261
+ row.append(
262
+ wandb.Audio(str(context_filename),
263
+ sample_rate=pl_module.sample_rate))
264
+ else:
265
+ row.append(None)
266
+
267
+ if beat is not None:
268
+ row.append(
269
+ wandb.Audio(str(beat_filename),
270
+ sample_rate=pl_module.sample_rate))
271
+
272
+ row.append(
273
+ wandb.Audio(str(gen_filename),
274
+ sample_rate=pl_module.sample_rate))
275
+ if context is not None:
276
+ assert context_dropout_mask is not None
277
+ if context_dropout_mask[i]:
278
+ row.append(
279
+ wandb.Audio(str(mix_filename),
280
+ sample_rate=pl_module.sample_rate))
281
+ else:
282
+ row.append(None)
283
+ if context is not None and beat is not None:
284
+ assert context_dropout_mask is not None
285
+ if context_dropout_mask[i]:
286
+ row.append(
287
+ wandb.Audio(str(context_with_beats_filename),
288
+ sample_rate=pl_module.sample_rate))
289
+ else:
290
+ row.append(None)
291
+ # if style is not None:
292
+ # row.append(style_score[i])
293
+ # if context is not None and isinstance(context, Tensor):
294
+ # row.append(context_score[i])
295
+
296
+ data[i] = row
297
+
298
+ # if pl_module.logger is not None:
299
+ if logger is not None:
300
+ table = wandb.Table(columns=columns, data=data)
301
+ logger.log({f"demos/step:{trainer.global_step}": table})
302
+ # if style is not None:
303
+ # logger.log({"valid/style_cocola": style_score.mean()})
304
+ # if context is not None and isinstance(context, Tensor):
305
+ # logger.log({"valid/context_cocola": context_score.mean()})
306
+ # logger.log_table( # type: ignore
307
+ # key=f"demos_step{trainer.global_step}",
308
+ # columns=columns,
309
+ # data=data)
310
+
311
+
312
+ class PrintLossesCallback(L.Callback):
313
+
314
+ def __init__(self):
315
+ super().__init__()
316
+ self.train_losses = []
317
+ self.val_losses = []
318
+
319
+ def on_train_epoch_end(self, trainer, pl_module):
320
+ # Compute and print the average training loss for the epoch
321
+ train_loss = trainer.callback_metrics.get("train_loss")
322
+ if train_loss is None:
323
+ return
324
+ print(f"Epoch {trainer.current_epoch +1} / "
325
+ f"train loss: {train_loss:.4f}")
326
+
327
+ def on_validation_epoch_end(self, trainer, pl_module):
328
+ # Compute and print the average validation loss for the epoch
329
+ val_loss = trainer.callback_metrics.get("val_loss")
330
+ if val_loss is None:
331
+ return
332
+ print(f"Epoch {trainer.current_epoch + 1} / "
333
+ f"validation loss: {val_loss:.4f}")
training/train.py ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from time import sleep
3
+ import traceback
4
+ from typing import List, Optional
5
+ import lightning as L
6
+ from lightning.pytorch.loggers import WandbLogger
7
+ from lightning.pytorch.callbacks import OnExceptionCheckpoint
8
+ import wandb
9
+ from torch import Tensor
10
+ import torch
11
+
12
+ import hyperparameters as hp
13
+ from conditioning.clap_embedder import LinearClapEmbedder
14
+ from conditioning.condition_type import ConditionType
15
+ from conditioning.conditioning_method import ConditioningMethod
16
+ from conditioning.prompt_processor import InterleavedContextPromptProcessor, StraightContextPromptProcessor
17
+ from conditioning.t5embedder import T5EmbedderCPU, T5EmbedderGPU
18
+ import config as cfg
19
+ from data.stem import Stem
20
+ from models.lightning_musicgen import LightningMusicgen
21
+ from training.callback import PrintLossesCallback, SaveDemoOnValidationCallback
22
+ from lightning.pytorch.callbacks import TQDMProgressBar, ModelCheckpoint
23
+ from utils.inspection import print_params
24
+ from utils.logging import get_or_create_run_id
25
+
26
+
27
+ def train(model_params: hp.ModelParams,
28
+ dataset_params: hp.DatasetParams,
29
+ max_steps: Optional[int] = None,
30
+ max_time: Optional[str] = None,
31
+ max_epochs: Optional[int] = None,
32
+ log: Optional[bool] = None,
33
+ accumulate_grad_batches: int = 1,
34
+ validate_every_n_steps: Optional[int] = None,
35
+ distributed_strategy: Optional[str] = None,
36
+ seed: Optional[int] = None,
37
+ run_name: Optional[str] = None,
38
+ kill_on_end: bool = False,
39
+ n_demos_per_epoch: int = 6,
40
+ devices: int = -1):
41
+
42
+ if log is None:
43
+ log = run_name is not None
44
+ if log and run_name is None:
45
+ raise ValueError("Need a run name to log on wandb")
46
+
47
+ # init model and datamodule
48
+ full_run_params = {
49
+ "model_params": model_params.to_dict(),
50
+ "data_params": dataset_params.to_dict()
51
+ }
52
+ model = model_params.instantiate()
53
+ datamodule = dataset_params.instantiate()
54
+ n_train_batches, n_valid_batches = (datamodule.lengths["train"],
55
+ datamodule.lengths["valid"])
56
+
57
+ # setup callbacks and logger
58
+ logger: WandbLogger | bool = False
59
+ resume_from_checkpoint: Optional[Path] = None
60
+ if log:
61
+ assert run_name is not None
62
+ output_dir = cfg.output_dir() / run_name
63
+ if output_dir.exists():
64
+ print(
65
+ f"Output directory for a run named {run_name} exists. Resuming training..."
66
+ )
67
+ resume_from_checkpoint = output_dir / "last.ckpt"
68
+ wandb_run_id = get_or_create_run_id(output_dir)
69
+ logger = WandbLogger(
70
+ entity=cfg.ENTITY,
71
+ project=cfg.PROJECT,
72
+ name=run_name,
73
+ id=wandb_run_id,
74
+ resume="allow",
75
+ config=full_run_params,
76
+ # settings=wandb.Settings(start_method="fork"),
77
+ )
78
+ callbacks: List[L.Callback] = []
79
+ if not cfg.running_locally():
80
+ # printlossescallback = PrintLossesCallback()
81
+ # callbacks.append(printlossescallback)
82
+ progbar: L.Callback = TQDMProgressBar(refresh_rate=n_train_batches // 2)
83
+ callbacks.append(progbar)
84
+ if run_name is not None:
85
+ output_dir = cfg.output_dir() / run_name
86
+ savedemocallback = SaveDemoOnValidationCallback(
87
+ output_dir, save_model=False, n_demos=n_demos_per_epoch)
88
+ interruptcallback = OnExceptionCheckpoint(output_dir,
89
+ filename="interrupted")
90
+ modelcheckpoint = ModelCheckpoint(dirpath=output_dir,
91
+ save_last=True,
92
+ every_n_epochs=1,
93
+ save_top_k=-1)
94
+ callbacks += [
95
+ interruptcallback,
96
+ savedemocallback,
97
+ modelcheckpoint,
98
+ ]
99
+
100
+ # init trainer
101
+ trainer = L.Trainer(
102
+ enable_model_summary=True,
103
+ accelerator="auto",
104
+ max_steps=max_steps or -1,
105
+ max_epochs=max_epochs,
106
+ max_time=max_time,
107
+ devices=devices,
108
+ strategy=distributed_strategy or "auto",
109
+ gradient_clip_val=1.0,
110
+ accumulate_grad_batches=accumulate_grad_batches,
111
+ gradient_clip_algorithm="value",
112
+ precision="16-mixed",
113
+ callbacks=callbacks,
114
+ logger=logger,
115
+ log_every_n_steps=10,
116
+ val_check_interval=validate_every_n_steps or 1.0,
117
+ # check_val_every_n_epoch=None,
118
+ limit_train_batches=n_train_batches,
119
+ limit_val_batches=n_valid_batches,
120
+ num_sanity_val_steps=-1,
121
+ enable_progress_bar=True,
122
+ )
123
+
124
+ # set seed
125
+ if seed is not None:
126
+ L.seed_everything(seed)
127
+
128
+ # run training
129
+ if kill_on_end:
130
+ try:
131
+ trainer.fit(model, datamodule=datamodule)
132
+ print("Training is finished. Killing myself in five minutes.")
133
+ try:
134
+ wandb.finish()
135
+ sleep(300)
136
+ cfg.shutdown()
137
+ except KeyboardInterrupt:
138
+ print("You saved me! I'll never forget that.")
139
+ return
140
+ except Exception:
141
+ cfg.shutdown()
142
+
143
+ except KeyboardInterrupt:
144
+ print("Received keyboard interrupt. Stopping training "
145
+ "without shutting down...")
146
+ wandb.finish()
147
+
148
+ except Exception as e:
149
+ (cfg.output_dir() / "exception.txt").write_text(
150
+ f"Exception: {str(e)}\n\n "
151
+ f"Stacktrace: {traceback.format_exc()}\n")
152
+ print(f"training broke with exception {e}")
153
+ print(f"Killing myself in five minutes")
154
+ try:
155
+ wandb.finish()
156
+ sleep(300)
157
+ cfg.shutdown()
158
+ except KeyboardInterrupt:
159
+ print("You saved me! I'll never forget that.")
160
+ return
161
+ except Exception:
162
+ cfg.shutdown()
163
+
164
+ else:
165
+ trainer.fit(model, datamodule=datamodule)
166
+ if run_name is not None:
167
+ wandb.finish()
168
+
169
+ return
170
+
171
+
172
+ if __name__ == "__main__":
173
+ from time import time
174
+
175
+ encodec_params = hp.pretrained_encodec_meta_32khz_params
176
+ lm_params = hp.FioraSmallLmParams()
177
+ prompt_processor_params = hp.PromptProcessorParams(
178
+ keep_only_valid_steps=True,
179
+ model_class=InterleavedContextPromptProcessor,
180
+ context_dropout=0.5)
181
+ conditioning_params = hp.ConditioningParams(
182
+ embedder_types={
183
+ ConditionType.DESCRIPTION: T5EmbedderGPU,
184
+ ConditionType.STYLE: LinearClapEmbedder
185
+ },
186
+ conditioning_methods={
187
+ ConditionType.DESCRIPTION: ConditioningMethod.CROSS_ATTENTION,
188
+ ConditionType.STYLE: ConditioningMethod.INPUT_SUM,
189
+ },
190
+ conditioning_dropout=0.5)
191
+
192
+ model_params: hp.MusicgenParams = hp.MusicgenParams(
193
+ encodec_params=encodec_params,
194
+ lm_params=lm_params,
195
+ prompt_processor_params=prompt_processor_params,
196
+ conditioning_params=conditioning_params)
197
+
198
+ dataset_params = hp.MixDatasetParams(clip_length_in_seconds=10,
199
+ sample_rate=32_000,
200
+ root_dir=cfg.mixdata_path(),
201
+ single_stem=True,
202
+ target_stem=Stem.DRUMS,
203
+ min_context_seconds=5,
204
+ use_style_conditioning=True,
205
+ use_beat_conditioning=False,
206
+ type_of_context="stems",
207
+ add_click=False,
208
+ sync_chunks=False,
209
+ bpm_in_caption=False,
210
+ batch_size_train=2,
211
+ batch_size_test=12,
212
+ num_workers=8,
213
+ speed_transform_p=0.5,
214
+ pitch_transform_p=0.5,
215
+ n_samples_per_epoch=2000)
216
+
217
+ device = "cuda"
218
+ model: LightningMusicgen = model_params.instantiate().to(device)
219
+ datamodule = dataset_params.instantiate()
220
+
221
+ # validation step test
222
+ # model.eval()
223
+ # vd = iter(datamodule.val_dataloader())
224
+ # for i in range(2):
225
+ # batch = next(vd)
226
+ # batch = {
227
+ # k: v.to(device) if isinstance(v, Tensor) else v
228
+ # for k, v in batch.items()
229
+ # }
230
+ # t0 = time()
231
+ # with torch.autocast(device_type="cuda"):
232
+ # val_loss = model.validation_step(batch, i)
233
+ # t1 = time()
234
+ # print(f"val step in {t1 - t0} seconds")
235
+
236
+ # training step test
237
+ # td = iter(datamodule.train_dataloader())
238
+ # model.train()
239
+ # for i in range(10):
240
+ # batch = next(td)
241
+ # batch = {
242
+ # k: v.to(device) if isinstance(v, Tensor) else v
243
+ # for k, v in batch.items()
244
+ # }
245
+ # t0 = time()
246
+ # with torch.autocast(device_type="cuda"):
247
+ # train_loss = model.training_step(batch, i)
248
+ # t1 = time()
249
+ # print(f"training step in {t1 - t0} seconds")
250
+
251
+ trainer = L.Trainer(accelerator="auto",
252
+ precision="16-mixed",
253
+ enable_model_summary=True,
254
+ logger=None,
255
+ enable_checkpointing=False,
256
+ num_sanity_val_steps=2,
257
+ limit_train_batches=50,
258
+ limit_val_batches=2,
259
+ max_epochs=10)
260
+ trainer.fit(model, datamodule=datamodule)
utils/__init__.py ADDED
File without changes
utils/audio.py ADDED
@@ -0,0 +1,293 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from functools import partial
2
+ import random
3
+ import time
4
+ from torch import Tensor
5
+ from pathlib import Path
6
+ import torch
7
+ import torchaudio
8
+ import soundfile as sf
9
+ from typing import Dict, List, Optional, Sequence, Tuple, Union
10
+ import concurrent.futures
11
+ import numpy as np
12
+
13
+ from data.stem import Stem
14
+
15
+
16
+ def pad_stack(tensors: List[Tensor],
17
+ pad_value: int | float,
18
+ padding_dim: int = -1,
19
+ pad_start: bool = False,
20
+ stack_dim: int = 0):
21
+ dtypes = {t.dtype for t in tensors}
22
+ if len(dtypes) > 1:
23
+ raise ValueError("Input tensors have different types")
24
+ ndims = {t.ndim for t in tensors}
25
+ if len(ndims) > 1:
26
+ raise ValueError("Input tensors have different number of dimensions")
27
+ if len(tensors) == 0:
28
+ raise ValueError("Input list cannot be empty")
29
+ ndim = len(tensors[0].shape)
30
+ shapes = [t.shape for t in tensors]
31
+ for i in range(1, len(shapes)):
32
+ shape_i = list(tensors[i].shape)
33
+ shape_ii = list(tensors[i - 1].shape)
34
+ shape_i.pop(padding_dim)
35
+ shape_ii.pop(padding_dim)
36
+ if shape_i != shape_ii:
37
+ raise ValueError(
38
+ f"Shape of tensors at indices ({i-1}, {i}) don't match")
39
+
40
+ max_len = max(t.shape[padding_dim] for t in tensors)
41
+ if padding_dim < 0:
42
+ padding_dim = ndim + padding_dim
43
+
44
+ def get_pad_code(n):
45
+ pre = [0] * (2 * (ndim - padding_dim - 1))
46
+ mid = [n, 0] if pad_start else [0, n]
47
+ post = [0] * (2 * (padding_dim))
48
+ pad_code = pre + mid + post
49
+ return pad_code
50
+
51
+ padded_tensors = [
52
+ torch.nn.functional.pad(t,
53
+ get_pad_code(max_len - t.shape[padding_dim]),
54
+ value=pad_value) for t in tensors
55
+ ]
56
+ return torch.stack(padded_tensors, dim=stack_dim)
57
+
58
+
59
+ def to_stereo(audio: Tensor) -> Tensor:
60
+ if audio.dim() == 1:
61
+ return audio.repeat(2, 1)
62
+ if audio.dim() >= 2 and audio.shape[-2] == 2:
63
+ return audio
64
+ return torch.cat((audio, audio), dim=-2)
65
+
66
+
67
+ def to_mono(audio: Tensor) -> Tensor:
68
+ if audio.ndim == 1:
69
+ return audio
70
+ if audio.shape[-2] == 1:
71
+ return audio
72
+ return audio.mean(dim=-2, keepdim=True)
73
+
74
+
75
+ def save_audio(audio: Tensor, path: Path, sample_rate: int = 32_000):
76
+ audio = audio.float()
77
+ if audio.shape[-1] == 1:
78
+ audio.squeeze(-1)
79
+ l = audio.shape[-1]
80
+ audio = audio.reshape(-1, l)
81
+ # audio = mono_to_stereo(audio.detach().cpu())
82
+ audio = to_stereo(audio.detach().cpu())
83
+ path.parent.mkdir(parents=True, exist_ok=True)
84
+ if audio.dim() != 2:
85
+ print(f'{audio.shape=}')
86
+
87
+ torchaudio.save(str(path), audio, sample_rate=sample_rate) # type: ignore
88
+
89
+
90
+ def load_audio(path: Path,
91
+ sample_rate: int = 32_000,
92
+ stereo: bool = False) -> Tensor:
93
+ # soundfile instead of torchaudio.load to avoid torchcodec dependency
94
+ audio_np, orig_sr = sf.read(str(path), always_2d=True)
95
+ audio = torch.from_numpy(audio_np.T).float()
96
+ audio = torchaudio.functional.resample(audio, orig_sr, sample_rate)
97
+ if not stereo:
98
+ audio = to_mono(audio)
99
+ return audio.reshape(1, 1, -1) # type: ignore
100
+
101
+
102
+ def load_audio_chunk(audio_path: Path, start_offset: int, num_frames: int,
103
+ stereo: bool) -> Tensor:
104
+
105
+ # info = torchaudio.info(str(audio_path))
106
+ # length = info.num_frames
107
+ # file_sample_rate = info.sample_rate
108
+
109
+ # assert file_sample_rate == sample_rate
110
+ try:
111
+ wav, sr = torchaudio.load(str(audio_path),
112
+ frame_offset=start_offset,
113
+ num_frames=num_frames,
114
+ backend="soundfile")
115
+ except:
116
+ wav = torch.zeros(2, num_frames)
117
+
118
+ # if start_offset + num_frames >= length:
119
+ # wav = torch.zeros(2, num_frames)
120
+
121
+ # wav = torchaudio.functional.resample(wav, sr, sample_rate)
122
+
123
+ if wav.shape[-1] < num_frames:
124
+ wav = torch.nn.functional.pad(wav,
125
+ pad=(0, num_frames - wav.shape[-1]),
126
+ mode="constant",
127
+ value=0)
128
+
129
+ if not stereo:
130
+ # wav: Tensor = stereo_to_mono(wav).reshape(1, -1)
131
+ wav: Tensor = to_mono(wav).reshape(1, -1)
132
+
133
+ return wav
134
+
135
+
136
+ def is_silent(audio: Tensor, threshold: float = 1e-2):
137
+ return audio.max().item() < threshold
138
+
139
+
140
+ def create_click(shape: Sequence[int],
141
+ sr: int,
142
+ beats: Sequence[int],
143
+ click_freq: int = 440,
144
+ click_length: int = 200) -> Tensor:
145
+ click_track: Tensor = torch.zeros(shape)
146
+ sinewave: Tensor = create_sine_wave(click_freq, sr, click_length)
147
+ for beat in beats:
148
+ if beat >= click_track.shape[-1]:
149
+ break
150
+ for offset in range(200):
151
+ idx = beat + offset
152
+ if idx < click_track.shape[-1]:
153
+ click_track[..., idx] = sinewave[offset]
154
+ return click_track
155
+
156
+
157
+ def create_sine_wave(freq: float, sr: int, length: int) -> Tensor:
158
+ cycle_len = int(sr // freq)
159
+ cycle = torch.linspace(start=0, end=2 * torch.pi, steps=cycle_len)
160
+ cycle = cycle.repeat(length // cycle_len + 1)
161
+ cycle = cycle[:length]
162
+ wave = cycle.sin()
163
+ return wave
164
+
165
+
166
+ def stretch(audio: Tensor, sample_rate: int, speed_factor: float,
167
+ pitch_factor: int) -> Tensor:
168
+ import pylibrb # optional dep, not needed for inference
169
+ stretcher = pylibrb.RubberBandStretcher(
170
+ sample_rate=sample_rate,
171
+ channels=1,
172
+ options=pylibrb.Option.PROCESS_OFFLINE | pylibrb.Option.ENGINE_FASTER,
173
+ initial_time_ratio=speed_factor,
174
+ initial_pitch_scale=pow(2, pitch_factor / 12))
175
+ stretcher.set_max_process_size(audio.shape[-1])
176
+ audio_in = audio.reshape(1, -1).numpy()
177
+ stretcher.study(audio_in, final=True)
178
+ stretcher.process(audio_in, final=True)
179
+ audio_out = torch.from_numpy(stretcher.retrieve_available()).reshape(
180
+ 1, -1).float()
181
+ return audio_out
182
+
183
+
184
+ def stretch_with_timeout(audio: Tensor, sample_rate: int, speed_factor: float,
185
+ pitch_factor: int, timeout_seconds: float):
186
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
187
+ future = executor.submit(stretch,
188
+ audio,
189
+ sample_rate=sample_rate,
190
+ speed_factor=speed_factor,
191
+ pitch_factor=pitch_factor)
192
+ try:
193
+ return future.result(timeout=timeout_seconds)
194
+ except concurrent.futures.TimeoutError:
195
+ print("Timeout occurred in stretching audio.")
196
+ return audio
197
+ except Exception as e:
198
+ print(f"Exception occurred in stretching audio: {e}.")
199
+ return audio
200
+
201
+
202
+ def make_variable_frequency_sinewave(t_end: int,
203
+ peak_indices: Tensor) -> Tensor:
204
+ if not isinstance(peak_indices, Tensor):
205
+ peak_indices = torch.tensor(peak_indices, dtype=torch.int32)
206
+
207
+ device = peak_indices.device
208
+
209
+ if peak_indices.shape[-1] < 2:
210
+ return torch.zeros((t_end,), device=device)
211
+
212
+ # Define the fine-grained time array
213
+ # time = torch.linspace(t_start, t_end, 100_000)
214
+ time = torch.arange(t_end, device=device)
215
+
216
+ # Calculate frequencies for each interval
217
+ intervals = torch.diff(peak_indices) # Time intervals between beats
218
+ frequencies = 1 / intervals # Frequencies for each interval
219
+
220
+ # Find segment indices for each time point
221
+ segment_indices = torch.searchsorted(peak_indices, time, right=True) - 1
222
+ segment_indices = torch.clamp(segment_indices, 0, len(frequencies) - 1)
223
+
224
+ # Compute sinewave for all time points
225
+ phase_shift = torch.pi / 2
226
+ relative_time = time - peak_indices[segment_indices]
227
+ wave = torch.sin(2 * torch.pi * frequencies[segment_indices] *
228
+ relative_time + phase_shift)
229
+
230
+ # Extend before the first peak
231
+ before_mask = time < peak_indices[0]
232
+ freq_before = 1 / (peak_indices[1] - peak_indices[0])
233
+ wave[before_mask] = torch.sin(2 * torch.pi * freq_before *
234
+ (time[before_mask] - peak_indices[0]) +
235
+ phase_shift)
236
+
237
+ # Extend after the last peak
238
+ after_mask = time >= peak_indices[-1]
239
+ freq_after = 1 / (peak_indices[-1] - peak_indices[-2])
240
+ wave[after_mask] = torch.sin(2 * torch.pi * freq_after *
241
+ (time[after_mask] - peak_indices[-1]) +
242
+ phase_shift)
243
+
244
+ return wave
245
+
246
+
247
+ def normalize(audio: Tensor, new_min, new_max):
248
+ if len(audio.shape) == 2 and audio.shape[0] == 2:
249
+ audio = audio.mean(dim=-1)
250
+ audio = to_mono(audio)
251
+ # Calculate the min and max of the original array
252
+ old_min = audio.min()
253
+ old_max = audio.max()
254
+
255
+ # Apply the normalization formula
256
+ normalized_arr = (audio - old_min) / (old_max - old_min) * (
257
+ new_max - new_min) + new_min
258
+ return normalized_arr
259
+
260
+
261
+ def play(waveform: torch.Tensor, sr: int):
262
+ import IPython.display
263
+ import sounddevice as sd
264
+ waveform = to_stereo(waveform)
265
+ waveform_np = waveform.cpu().float().detach().numpy()
266
+ if is_interactive():
267
+ IPython.display.display(IPython.display.Audio(waveform_np, rate=sr))
268
+ else:
269
+ sd.play(waveform_np.T, sr)
270
+ sd.wait()
271
+
272
+
273
+ def is_interactive():
274
+ import sys
275
+ return "ipykernel" in sys.modules
276
+
277
+
278
+ def inject_clicks(audio_tensor: Tensor, beat_positions: Tensor,
279
+ sample_rate: int):
280
+ """
281
+ Add short clicks at `beat_positions` (sample indices) in `audio_tensor`.
282
+ If audio is multi-channel, we'll apply the same clicks to each channel.
283
+ """
284
+ import librosa
285
+ beat_positions_seconds = beat_positions / sample_rate
286
+ click_track: Tensor = torch.tensor(
287
+ librosa.clicks(times=beat_positions_seconds.cpu().detach().numpy(),
288
+ hop_length=1,
289
+ length=audio_tensor.shape[-1],
290
+ sr=sample_rate))
291
+
292
+ click_track = click_track.broadcast_to(audio_tensor.shape)
293
+ return click_track + audio_tensor.detach().cpu()
utils/aws.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ This is the script that runs on the "controller" machine: the lightweight
3
+ machine that requests the big boi instance(s) from EC2 and configures the
4
+ training job. In this example, I want the controller machine to be my home
5
+ computer. This means that I can run this script from my computer and start a
6
+ training job on the remote big boi instance, without an intermediary machine
7
+ from aws.
8
+
9
+ This script can run in two modes, local and remote, depending on the value of
10
+ `run_locally`.
11
+
12
+ VERY IMPORTANT NOTE: running this script locally is not at all the same as
13
+ running a normal training locally (i.e: running locally the file `train.py`):
14
+ Running this script in local mode still creates a sagemaker job and does
15
+ everything in the same exact way as it would to run on a remote instance,
16
+ EXCEPT that the big boi instance in EC2 is replaced by your local machine.
17
+
18
+ So the script creates a docker container, downloads it, creates the virtual
19
+ environment, and runs the training job in the same exact way as it would on the
20
+ remote instance. Running this training job in local mode successfuly should
21
+ GUARANTEE that it will run successfully on a remote instance.
22
+ """
23
+
24
+ import sagemaker.pytorch
25
+ import sagemaker.local
26
+ import sagemaker
27
+ from datetime import datetime
28
+
29
+
30
+ def launch_aws_job(
31
+ experiment_name: str,
32
+ instance_type: str,
33
+ max_training_hours: int,
34
+ entry_point: str,
35
+ input_mode: str = "File",
36
+ data_dir: str = "",
37
+ ):
38
+ # True: run training job locally for testing purposes (needs docker installed)
39
+ # False: run training job on an AWS instance
40
+ run_locally: bool = False
41
+
42
+ # define names for project, experiment and run
43
+ project_name = "latent-accompaniment-generation"
44
+ # experiment_name = "bart-drums-mixdata"
45
+
46
+ checkpoint_ec2_path = "/opt/ml/checkpoints"
47
+ # checkpoint_s3_path = "s3://latent-accompaniment-generation/checkpoints/ash-drums"
48
+
49
+ # NOTE: job name has to be unique
50
+ now = datetime.now()
51
+ datestring = now.strftime('%Y-%m-%d-%H-%M')
52
+ job_name = experiment_name + "-" + datestring
53
+
54
+ # define session and role
55
+ sagemaker_session = (sagemaker.local.LocalSession()
56
+ if run_locally else sagemaker.Session())
57
+ role = ("arn:aws:iam::076456026604:role/service-role/"
58
+ "AmazonSageMaker-ExecutionRole-20240407T195108")
59
+
60
+ # define input and output directories
61
+ if run_locally:
62
+ data_path: str = "file://./data/mtg-jamendo-low/"
63
+ output_path: str = ("file://./checkpoints/"
64
+ f"{project_name}/{experiment_name}/{job_name}")
65
+ else:
66
+ # NOTE: whatever directory is specified in here will be downloaded to the
67
+ # training machine in its entirety (if using "File" input mode, which is
68
+ # the default). So make sure that the s3 directory specified here only
69
+ # contains data that is needed for the training.
70
+ # WRONG: s3://audio-data-bucket/data/ (.../MNIST, .../moisesdb)
71
+ # OK: s3://audio-data-bucket/data/MNIST
72
+ data_path: str = "s3://audio-data-bucket/data/lag-data/" + data_dir
73
+ weights_path: str = "s3://latent-accompaniment-generation/weights/"
74
+ output_path = f"s3://{project_name}/{experiment_name}"
75
+ # ckp_path = checkpoint_s3_path
76
+
77
+ # instance_type: str = "ml.g4dn.xlarge"
78
+ # instance_type: str = "ml.m5.xlarge"
79
+ # instance_type: str = "ml.g5.2xlarge" # 1x nvidia A10
80
+ # instance_type: str = "ml.g5.12xlarge" # 4x nvidia A10
81
+ # instance_type: str = "ml.p4d.24xlarge" # 8x nvidia A100 40gb 💀
82
+ # instance_type: str = "ml.p4de.24xlarge" # 8x nvidia A100 80gb 💀
83
+ # instance_type: str = "ml.trn1.2xlarge"
84
+
85
+ # max_training_time = 3 * 24 * 60 * 60 # 3 days
86
+ # max_training_time = 16 * 60 * 60 # 16 hours
87
+ max_training_time = max_training_hours * 60 * 60
88
+
89
+ estimator = sagemaker.pytorch.PyTorch(
90
+ # entry_point="scripts/train_bart.py",
91
+ entry_point=entry_point,
92
+ role=role,
93
+ max_run=max_training_time,
94
+ instance_count=1,
95
+ framework_version="2.1.0",
96
+ py_version="py310",
97
+ dependencies=["requirements.txt"],
98
+ source_dir="src",
99
+ output_path=output_path,
100
+ instance_type="local" if run_locally else instance_type,
101
+ local_code=run_locally,
102
+ # input_mode="FastFile",
103
+ # input_mode="File",
104
+ input_mode=input_mode,
105
+ checkpoint_local_path=checkpoint_ec2_path,
106
+ # checkpoint_s3_uri=checkpoint_s3_path,
107
+ # distribution={"pytorchddp": {
108
+ # "enabled": "true"
109
+ # }},
110
+ )
111
+
112
+ estimator.fit(
113
+ {
114
+ "data": data_path,
115
+ # "ckp": ckp_path,
116
+ "weights": weights_path,
117
+ },
118
+ job_name=job_name,
119
+ )