Spaces:
Running on Zero
Running on Zero
File size: 6,017 Bytes
21e9df7 3eb209f ec2f6de 3eb209f 21e9df7 55cbfca 21e9df7 55cbfca 21e9df7 55cbfca 21e9df7 55cbfca 21e9df7 0c50d1c 21e9df7 55cbfca 960c0e3 21e9df7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 | try:
import spaces
except ImportError:
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
class spaces:
class GPU:
def __init__(self, func=None, duration=60):
self.func = func
def __call__(self, *args, **kwargs):
if self.func is not None:
return self.func(*args, **kwargs)
func = args[0]
return func
import sys
sys.stdout.reconfigure(line_buffering=True)
import tempfile
import threading
import gradio as gr
import soundfile as sf
import torch
import torchaudio
from huggingface_hub import hf_hub_download
from pyharp import ModelCard, build_endpoint
from src.constants import CODEBOOK_SIZE, SAMPLE_RATE
from src.models.instructmusicgenadapter_module import InstructMusicGenAdapterLitModule
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
ckpt_path = None
ckpt_ready = False
ckpt_error = None
model = None
model_ready = False
model_lock = threading.Lock()
def download_checkpoint():
"""Fetch the finetuned adapter weights in the background so the server can
start immediately instead of blocking on a 14.6GB download."""
global ckpt_path, ckpt_ready, ckpt_error
try:
ckpt_path = hf_hub_download(repo_id="ldzhangyx/instruct-MusicGen", filename="finetuned.ckpt")
print("Checkpoint downloaded.")
except Exception as e:
ckpt_error = str(e)
print(f"Download error: {e}")
finally:
ckpt_ready = True
threading.Thread(target=download_checkpoint, daemon=True).start()
model_card = ModelCard(
name="Instruct-MusicGen",
description="Edits a music recording to follow a text instruction, e.g. adding, "
"removing, or isolating an instrument.",
author="Yixiao Zhang, Yukara Ikemiya, Woosung Choi, Naoki Murata, "
"Marco A. Martinez-Ramirez, Liwei Lin, Gus Xia, Wei-Hsiang Liao, "
"Yuki Mitsufuji, Simon Dixon",
tags=["music editing", "instruction tuning"],
)
def load_model():
"""Build the model and load the finetuned weights.
Must run inside @spaces.GPU. Construction itself builds the backbone on CUDA,
so it can't happen in a background thread on ZeroGPU.
"""
global model
# weights_only=False bc ckpt has non-tensor objects
model = InstructMusicGenAdapterLitModule.load_from_checkpoint(
ckpt_path, weights_only=False
)
model.eval()
@spaces.GPU
@torch.inference_mode()
def process_fn(input_audio_path: str, instruction: str) -> str:
"""Edit the input audio according to the instruction.
Adapted from generate_edited_audio in the original repo's src/inference.py.
"""
global model, model_ready
if not ckpt_ready:
raise gr.Error("Checkpoint is still downloading, please wait a moment and try again.")
if ckpt_error is not None:
raise gr.Error(f"Checkpoint download failed: {ckpt_error}")
with model_lock:
if not model_ready:
load_model()
model_ready = True
if not instruction or not instruction.strip():
raise gr.Error("Instruction cannot be empty.")
# model was trained on "Music piece. Instruct: <desired_edit>."
# only <desired_edit> is variable, so rest is hardcoded
instruction = f"Music piece. Instruct: {instruction.strip().rstrip('.')}."
input_audio, sample_rate = sf.read(input_audio_path)
input_audio_tensor = torch.tensor(input_audio).float()
if input_audio_tensor.ndim == 2:
# downmix to mono -- the original inference.py assumes a mono waveform!!
input_audio_tensor = input_audio_tensor.mean(dim=-1)
input_audio_tensor = input_audio_tensor.unsqueeze(0).unsqueeze(0)
if sample_rate != SAMPLE_RATE:
# the original inference.py never resamples, but the compression model
# expects SAMPLE_RATE (32kHz); a DAW export at 44.1/48kHz needs converting first
input_audio_tensor = torchaudio.functional.resample(input_audio_tensor, sample_rate, SAMPLE_RATE)
input_audio_tensor = input_audio_tensor.to(DEVICE)
instruction_list = [instruction]
with torch.autocast("cuda", dtype=torch.float16):
description, cond_code = model.model.musicgen._prepare_tokens_and_attributes(
instruction_list, input_audio_tensor
)
cond_code = torch.cat(
[cond_code, torch.ones_like(cond_code[:, :, 0:1]) * CODEBOOK_SIZE], dim=-1
)
with torch.autocast("cuda", dtype=torch.float16):
audio_values = model.model.generate(
text_description=instruction_list,
condition_audio_code=cond_code,
num_samples=1,
)
generated_audio = (
model.model.musicgen.compression_model.decode(audio_values, None)
.squeeze()
.float() # decode runs under autocast, so this is still float16, but soundfile needs float32
.cpu()
.detach()
.numpy()
)
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
output_audio_path = f.name
sf.write(output_audio_path, generated_audio, SAMPLE_RATE)
return output_audio_path
with gr.Blocks() as demo:
input_components = [
gr.Audio(type="filepath", label="Input Audio").harp_required(True),
gr.Textbox(
label="Instruction",
value="Only Drums",
info="What to change: add/only/no + an instrument, e.g. 'Only Drums', 'No Bass', "
"'Add Piano' (per the model's demo examples; piano/bass/drums/guitar work best)",
),
]
output_components = [
gr.Audio(type="filepath", label="Edited Audio").set_info(
"Edited version of the input audio reflecting the instruction."
),
]
build_endpoint(
model_card=model_card,
input_components=input_components,
output_components=output_components,
process_fn=process_fn,
)
if __name__ == "__main__":
demo.queue().launch(pwa=True)
|