Spaces:
Running on Zero
Running on Zero
Build the default size in the worker, and check the variant
Browse filesBuilds the medium model, the default, in the worker rather than only downloading every size there, builds another size when a request picks it, moves the beat checkpoint link into the worker, and refuses an unknown variant. It also imports spaces plainly, since it is always installed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app.py
CHANGED
|
@@ -1,22 +1,9 @@
|
|
|
|
|
|
|
|
| 1 |
import sys
|
| 2 |
|
| 3 |
sys.stdout.reconfigure(line_buffering=True)
|
| 4 |
|
| 5 |
-
try:
|
| 6 |
-
import spaces
|
| 7 |
-
except ImportError:
|
| 8 |
-
# keep @spaces.GPU usable as a no-op; ZeroGPU requires this exact name.
|
| 9 |
-
class spaces:
|
| 10 |
-
class GPU:
|
| 11 |
-
def __init__(self, func=None, duration=60):
|
| 12 |
-
self.func = func
|
| 13 |
-
|
| 14 |
-
def __call__(self, *args, **kwargs):
|
| 15 |
-
if self.func is not None:
|
| 16 |
-
return self.func(*args, **kwargs)
|
| 17 |
-
func = args[0]
|
| 18 |
-
return func
|
| 19 |
-
|
| 20 |
import contextlib
|
| 21 |
import tempfile
|
| 22 |
from pathlib import Path
|
|
@@ -57,38 +44,46 @@ _WEIGHT_REVISIONS = {
|
|
| 57 |
_weights: dict[str, Path] = {}
|
| 58 |
_models: dict[str, TranscriptionModel] = {}
|
| 59 |
|
| 60 |
-
|
| 61 |
-
# request downloads inside its GPU reservation. The server never runs process_fn.
|
| 62 |
-
if __name__ != "__main__":
|
| 63 |
-
for _size, _revision in _WEIGHT_REVISIONS.items():
|
| 64 |
-
_weights[_size] = Path(snapshot_download(
|
| 65 |
-
f"MuScriptor/muscriptor-{_size}", revision=_revision
|
| 66 |
-
)) / "model.safetensors"
|
| 67 |
-
|
| 68 |
-
# Tempo detection runs beat_this, which downloads its checkpoint from the JKU cloud on first use,
|
| 69 |
-
# and that has failed both inside a GPU call (no name resolution) and at startup (503). The same
|
| 70 |
-
# file is mirrored on the Hub and linked where torch.hub looks before downloading.
|
| 71 |
-
_beat_checkpoint = Path(torch.hub.get_dir()) / "checkpoints" / "beat_this-final0.ckpt"
|
| 72 |
-
if not _beat_checkpoint.exists():
|
| 73 |
-
_beat_checkpoint.parent.mkdir(parents=True, exist_ok=True)
|
| 74 |
-
with contextlib.suppress(FileExistsError):
|
| 75 |
-
_beat_checkpoint.symlink_to(hf_hub_download(
|
| 76 |
-
"teamup-tech/Beat-This-weights", "final0.ckpt",
|
| 77 |
-
revision="4afbe376535aaab71b76ae2ca2d7c16b08877321",
|
| 78 |
-
))
|
| 79 |
|
| 80 |
|
| 81 |
def _get_model(variant: str) -> TranscriptionModel:
|
| 82 |
-
"""Builds the TranscriptionModel of one size,
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
|
|
|
| 86 |
"""
|
| 87 |
if variant not in _models:
|
| 88 |
_models[variant] = TranscriptionModel.load_model(_weights[variant], device=DEVICE)
|
| 89 |
return _models[variant]
|
| 90 |
|
| 91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
def _resolve_instruments(text: str) -> tuple[list[str] | None, str]:
|
| 93 |
"""Resolve the comma-separated instruments box into exact group names.
|
| 94 |
|
|
@@ -143,6 +138,8 @@ def process_fn(
|
|
| 143 |
"""Transcribe the input audio to MIDI, plus a note on the Instruments field."""
|
| 144 |
# Checked before the GPU is reserved, so a request the model cannot take spends none of the
|
| 145 |
# caller's ZeroGPU allowance
|
|
|
|
|
|
|
| 146 |
if not input_audio_path:
|
| 147 |
raise gr.Error("Provide the music to transcribe.")
|
| 148 |
try:
|
|
@@ -192,7 +189,7 @@ if __name__ == "__main__":
|
|
| 192 |
)),
|
| 193 |
gr.Dropdown(
|
| 194 |
choices=["small", "medium", "large"],
|
| 195 |
-
value=
|
| 196 |
label="Model Size",
|
| 197 |
info="small = fastest, least accurate. medium = balanced (default, per repo). large = most accurate, slower.",
|
| 198 |
),
|
|
|
|
| 1 |
+
import spaces # imported first, as ZeroGPU requires
|
| 2 |
+
|
| 3 |
import sys
|
| 4 |
|
| 5 |
sys.stdout.reconfigure(line_buffering=True)
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import contextlib
|
| 8 |
import tempfile
|
| 9 |
from pathlib import Path
|
|
|
|
| 44 |
_weights: dict[str, Path] = {}
|
| 45 |
_models: dict[str, TranscriptionModel] = {}
|
| 46 |
|
| 47 |
+
DEFAULT_SIZE = "medium"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
def _get_model(variant: str) -> TranscriptionModel:
|
| 51 |
+
"""Builds the TranscriptionModel of one size, once.
|
| 52 |
|
| 53 |
+
The default size is built in the worker at startup. Holding all three would keep about 7 GB
|
| 54 |
+
there, so another size is built when a request picks it, inside the call, where ZeroGPU builds
|
| 55 |
+
it again on every call, which takes seconds from local weights.
|
| 56 |
"""
|
| 57 |
if variant not in _models:
|
| 58 |
_models[variant] = TranscriptionModel.load_model(_weights[variant], device=DEVICE)
|
| 59 |
return _models[variant]
|
| 60 |
|
| 61 |
|
| 62 |
+
# Fetched in the worker, which runs process_fn and starts as the server comes up, so that no
|
| 63 |
+
# request downloads inside its GPU reservation, and the default size is built and placed there, on
|
| 64 |
+
# its main thread, where ZeroGPU's emulation records the placement. The server never runs
|
| 65 |
+
# process_fn.
|
| 66 |
+
if __name__ != "__main__":
|
| 67 |
+
for _size, _revision in _WEIGHT_REVISIONS.items():
|
| 68 |
+
_weights[_size] = Path(snapshot_download(
|
| 69 |
+
f"MuScriptor/muscriptor-{_size}", revision=_revision
|
| 70 |
+
)) / "model.safetensors"
|
| 71 |
+
|
| 72 |
+
# Tempo detection runs beat_this, which downloads its checkpoint from the JKU cloud on first
|
| 73 |
+
# use, and that has failed both inside a GPU call (no name resolution) and at startup (503).
|
| 74 |
+
# The same file is mirrored on the Hub and linked where torch.hub looks before downloading.
|
| 75 |
+
_beat_checkpoint = Path(torch.hub.get_dir()) / "checkpoints" / "beat_this-final0.ckpt"
|
| 76 |
+
if not _beat_checkpoint.exists():
|
| 77 |
+
_beat_checkpoint.parent.mkdir(parents=True, exist_ok=True)
|
| 78 |
+
with contextlib.suppress(FileExistsError):
|
| 79 |
+
_beat_checkpoint.symlink_to(hf_hub_download(
|
| 80 |
+
"teamup-tech/Beat-This-weights", "final0.ckpt",
|
| 81 |
+
revision="4afbe376535aaab71b76ae2ca2d7c16b08877321",
|
| 82 |
+
))
|
| 83 |
+
|
| 84 |
+
_get_model(DEFAULT_SIZE)
|
| 85 |
+
|
| 86 |
+
|
| 87 |
def _resolve_instruments(text: str) -> tuple[list[str] | None, str]:
|
| 88 |
"""Resolve the comma-separated instruments box into exact group names.
|
| 89 |
|
|
|
|
| 138 |
"""Transcribe the input audio to MIDI, plus a note on the Instruments field."""
|
| 139 |
# Checked before the GPU is reserved, so a request the model cannot take spends none of the
|
| 140 |
# caller's ZeroGPU allowance
|
| 141 |
+
if variant not in _WEIGHT_REVISIONS:
|
| 142 |
+
raise gr.Error(f"Unknown model size: {variant}")
|
| 143 |
if not input_audio_path:
|
| 144 |
raise gr.Error("Provide the music to transcribe.")
|
| 145 |
try:
|
|
|
|
| 189 |
)),
|
| 190 |
gr.Dropdown(
|
| 191 |
choices=["small", "medium", "large"],
|
| 192 |
+
value=DEFAULT_SIZE,
|
| 193 |
label="Model Size",
|
| 194 |
info="small = fastest, least accurate. medium = balanced (default, per repo). large = most accurate, slower.",
|
| 195 |
),
|