Spaces:
Running on Zero
Running on Zero
Vansh Chugh commited on
Commit ·
2e1dc7f
1
Parent(s): c2b0125
initial deploy
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitignore +4 -0
- README.md +4 -6
- __init__.py +2 -0
- app.py +218 -0
- conditioning/__init__.py +6 -0
- conditioning/beat_embedder.py +249 -0
- conditioning/condition_dispatcher.py +157 -0
- conditioning/condition_provider.py +91 -0
- conditioning/condition_type.py +26 -0
- conditioning/conditioning_method.py +14 -0
- conditioning/embedded_condition.py +13 -0
- conditioning/embedder.py +50 -0
- conditioning/prompt_processor.py +625 -0
- conditioning/t5embedder.py +211 -0
- config.py +77 -0
- data/auto_labelling.py +147 -0
- data/dataset_mixed.py +79 -0
- data/labels.py +424 -0
- data/stem.py +62 -0
- data/stemmed_datamodule.py +190 -0
- data/stemmed_dataset.py +801 -0
- hyperparameters.py +244 -0
- inference.py +54 -0
- loader.py +42 -0
- models/__init__.py +1 -0
- models/encodec.py +436 -0
- models/lightning_musicgen.py +622 -0
- models/loss.py +48 -0
- models/modules/__init__.py +23 -0
- models/modules/codebooks_patterns.py +548 -0
- models/modules/conv.py +346 -0
- models/modules/decoder.py +155 -0
- models/modules/lstm.py +28 -0
- models/modules/norm.py +28 -0
- models/modules/rope.py +125 -0
- models/modules/seanet.py +257 -0
- models/modules/streaming.py +131 -0
- models/modules/transformer.py +755 -0
- models/musicgen_lm.py +367 -0
- models/quantization/__init__.py +9 -0
- models/quantization/base.py +104 -0
- models/quantization/core_vq.py +420 -0
- models/quantization/vq.py +121 -0
- requirements.txt +15 -0
- train_stage_drums.py +87 -0
- training/callback.py +333 -0
- training/train.py +260 -0
- utils/__init__.py +0 -0
- utils/audio.py +293 -0
- 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:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.
|
| 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 |
+
)
|