Spaces:
Running on Zero
fix(deploy): symlink hf preloads into fork's checkpoint dir + strip prompt tokens in lyrics
Browse files1. The fork's AceStepHandler resolves checkpoints relative to its own
install dir (vendor/ace-step/checkpoints/), not ./models/<org>/<repo>/.
Previous symlink path was unused β fork triggered async auto-download
during initialize_service(), returned before it finished, then the
generate_music() call hit 'Model not fully initialized'.
Now we symlink the preloaded Ace-Step1.5 snapshot's contents flat into
vendor/ace-step/checkpoints/ and the acestep-v15-xl-sft snapshot as
the matching subdir, so the resolver sees real files at module init.
2. _HFLM.generate stripped the prompt via string startswith match, but
tokenizer.decode(skip_special_tokens=True) removes the <|im_start|>
markers from the decoded output β so the prefix never matched and
the system + user turns leaked into the lyrics output. Now slice at
the token level: out[0][prompt_len:] decoded separately.
- app.py +34 -46
- lyrics_lm.py +8 -3
|
@@ -39,7 +39,6 @@ local dev uses ``setup.sh``'s site-packages symlink instead.
|
|
| 39 |
from __future__ import annotations
|
| 40 |
|
| 41 |
import os
|
| 42 |
-
|
| 43 |
import sys as _sys
|
| 44 |
|
| 45 |
print("[ams] python process started", flush=True, file=_sys.stderr)
|
|
@@ -67,7 +66,6 @@ print(f"[ams] sys.path patched (vendor exists: {_VENDORED_ACE_STEP.exists()})",
|
|
| 67 |
import hashlib
|
| 68 |
import random
|
| 69 |
import shutil # noqa: F401 (reserved for future cleanup paths)
|
| 70 |
-
import subprocess
|
| 71 |
|
| 72 |
import gradio as gr
|
| 73 |
|
|
@@ -112,64 +110,54 @@ _PRELOAD_REPOS = (
|
|
| 112 |
)
|
| 113 |
|
| 114 |
|
| 115 |
-
def
|
| 116 |
-
|
|
|
|
|
|
|
| 117 |
|
|
|
|
|
|
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
|
|
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
paying the storage cost twice.
|
| 126 |
"""
|
| 127 |
-
src = Path.home() / ".cache" / "huggingface"
|
| 128 |
-
dst = _hf_cache_rw_dir()
|
| 129 |
-
if dst.exists():
|
| 130 |
-
return
|
| 131 |
-
if not src.exists():
|
| 132 |
-
# Nothing preloaded yet β create the empty target so HF_HOME points somewhere valid.
|
| 133 |
-
dst.mkdir(parents=True, exist_ok=True)
|
| 134 |
-
return
|
| 135 |
-
# `cp -al` = archive + hardlinks β fast, no duplicate bytes.
|
| 136 |
-
subprocess.run(["cp", "-al", str(src), str(dst)], check=True)
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
def _symlink_snapshots_into_models() -> None:
|
| 140 |
-
"""Create ./models/<org>/<repo>/ β latest snapshot dir for each preloaded repo."""
|
| 141 |
from huggingface_hub import snapshot_download
|
| 142 |
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
|
|
|
|
|
|
| 150 |
if target.exists() or target.is_symlink():
|
| 151 |
continue
|
| 152 |
-
target.symlink_to(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
|
| 155 |
def _bootstrap_spaces_cache() -> None:
|
| 156 |
-
"""On HF Spaces,
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
hardlinks fail with EXDEV ("Invalid cross-device link"). We don't need
|
| 162 |
-
the mirror in practice β inference-only workloads READ from the cache,
|
| 163 |
-
never write to it. Just leave HF_HOME alone (default ~/.cache/huggingface)
|
| 164 |
-
and symlink the preloaded snapshots into ./models/<org>/<repo>/ for the
|
| 165 |
-
ACE-Step checkpoint resolver.
|
| 166 |
-
|
| 167 |
-
Skipped locally β local dev uses setup.sh's site-packages symlink instead, since
|
| 168 |
-
the apple-silicon fork hardcodes its checkpoint resolver to its own install dir.
|
| 169 |
"""
|
| 170 |
if not os.getenv("SPACE_ID"):
|
| 171 |
return
|
| 172 |
-
|
| 173 |
|
| 174 |
|
| 175 |
def _warm_demucs_on_spaces() -> None:
|
|
|
|
| 39 |
from __future__ import annotations
|
| 40 |
|
| 41 |
import os
|
|
|
|
| 42 |
import sys as _sys
|
| 43 |
|
| 44 |
print("[ams] python process started", flush=True, file=_sys.stderr)
|
|
|
|
| 66 |
import hashlib
|
| 67 |
import random
|
| 68 |
import shutil # noqa: F401 (reserved for future cleanup paths)
|
|
|
|
| 69 |
|
| 70 |
import gradio as gr
|
| 71 |
|
|
|
|
| 110 |
)
|
| 111 |
|
| 112 |
|
| 113 |
+
def _symlink_ace_step_checkpoints() -> None:
|
| 114 |
+
"""Pre-populate the fork's hardcoded checkpoint dir with symlinks to
|
| 115 |
+
HF-preloaded snapshots so it doesn't trigger its built-in auto-download
|
| 116 |
+
on first inference.
|
| 117 |
|
| 118 |
+
The fork's AceStepHandler resolves checkpoints relative to its own
|
| 119 |
+
install dir (here, vendor/ace-step/checkpoints/). Expected layout:
|
| 120 |
|
| 121 |
+
vendor/ace-step/checkpoints/
|
| 122 |
+
βββ <Ace-Step1.5 contents> β vae/, encoder/, 5Hz-lm/, β¦ (flat)
|
| 123 |
+
βββ acestep-v15-xl-sft/ β the XL SFT DiT variant
|
| 124 |
|
| 125 |
+
Without this, initialize_service() kicks off an async auto-download,
|
| 126 |
+
returns before it finishes, then generate_music() hits
|
| 127 |
+
"Model not fully initialized" on the first user click.
|
|
|
|
| 128 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
from huggingface_hub import snapshot_download
|
| 130 |
|
| 131 |
+
checkpoints_dir = _VENDORED_ACE_STEP / "checkpoints"
|
| 132 |
+
checkpoints_dir.mkdir(parents=True, exist_ok=True)
|
| 133 |
+
|
| 134 |
+
# Umbrella repo β symlink each top-level entry flat into checkpoints/.
|
| 135 |
+
# snapshot_download is a no-op when files are already preloaded into the
|
| 136 |
+
# HF cache; it just returns the snapshot dir on disk.
|
| 137 |
+
umbrella = Path(snapshot_download(repo_id="ACE-Step/Ace-Step1.5"))
|
| 138 |
+
for child in umbrella.iterdir():
|
| 139 |
+
target = checkpoints_dir / child.name
|
| 140 |
if target.exists() or target.is_symlink():
|
| 141 |
continue
|
| 142 |
+
target.symlink_to(child)
|
| 143 |
+
|
| 144 |
+
# XL SFT DiT variant β as the subdir name the fork looks for.
|
| 145 |
+
xl_snap = Path(snapshot_download(repo_id="ACE-Step/acestep-v15-xl-sft"))
|
| 146 |
+
xl_target = checkpoints_dir / "acestep-v15-xl-sft"
|
| 147 |
+
if not (xl_target.exists() or xl_target.is_symlink()):
|
| 148 |
+
xl_target.symlink_to(xl_snap)
|
| 149 |
|
| 150 |
|
| 151 |
def _bootstrap_spaces_cache() -> None:
|
| 152 |
+
"""On HF Spaces, point the fork's checkpoint resolver at preloaded snapshots.
|
| 153 |
+
|
| 154 |
+
Skipped locally β local dev uses setup.sh's site-packages symlink instead,
|
| 155 |
+
since the apple-silicon fork hardcodes its checkpoint resolver to its own
|
| 156 |
+
install dir.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
"""
|
| 158 |
if not os.getenv("SPACE_ID"):
|
| 159 |
return
|
| 160 |
+
_symlink_ace_step_checkpoints()
|
| 161 |
|
| 162 |
|
| 163 |
def _warm_demucs_on_spaces() -> None:
|
|
@@ -195,9 +195,14 @@ class _HFLM:
|
|
| 195 |
repetition_penalty=float(kw.get("repetition_penalty", 1.1)),
|
| 196 |
do_sample=True,
|
| 197 |
)
|
| 198 |
-
|
| 199 |
-
#
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
|
| 202 |
|
| 203 |
def generate_lyrics(
|
|
|
|
| 195 |
repetition_penalty=float(kw.get("repetition_penalty", 1.1)),
|
| 196 |
do_sample=True,
|
| 197 |
)
|
| 198 |
+
# Slice off the prompt tokens at the *token* level. Doing it at the
|
| 199 |
+
# string level (full.startswith(prompt)) is brittle because
|
| 200 |
+
# ``skip_special_tokens=True`` strips the ChatML markers from
|
| 201 |
+
# ``full`` but they're still present in ``prompt`` β so the prefix
|
| 202 |
+
# match fails and the system + user turns leak into the output.
|
| 203 |
+
prompt_len = int(inputs["input_ids"].shape[1])
|
| 204 |
+
generated_ids = out[0][prompt_len:]
|
| 205 |
+
return self.tokenizer.decode(generated_ids, skip_special_tokens=True)
|
| 206 |
|
| 207 |
|
| 208 |
def generate_lyrics(
|