techfreakworm commited on
Commit
03ddd85
Β·
unverified Β·
1 Parent(s): 1de4459

fix(deploy): symlink hf preloads into fork's checkpoint dir + strip prompt tokens in lyrics

Browse files

1. 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.

Files changed (2) hide show
  1. app.py +34 -46
  2. lyrics_lm.py +8 -3
app.py CHANGED
@@ -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 _hf_cache_rw_dir() -> Path:
116
- return Path.home() / "hf-cache-rw"
 
 
117
 
 
 
118
 
119
- def _mirror_hf_cache() -> None:
120
- """Hardlink-mirror the build-user HF hub cache to a runtime-writable location.
 
121
 
122
- HF Spaces ships the preloaded weights under ~/.cache/huggingface/hub owned by
123
- the build user (read-only at runtime). Hardlink them to ~/hf-cache-rw so the
124
- runtime user can write new files alongside the preloaded snapshots without
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
- project_models = Path("./models").resolve()
144
- for repo_id in _PRELOAD_REPOS:
145
- # snapshot_download is a no-op when the files are already cached. It returns
146
- # the resolved snapshot dir on disk.
147
- snap = Path(snapshot_download(repo_id=repo_id, cache_dir=os.environ.get("HF_HOME")))
148
- target = project_models / repo_id # e.g. ./models/ACE-Step/Ace-Step1.5
149
- target.parent.mkdir(parents=True, exist_ok=True)
 
 
150
  if target.exists() or target.is_symlink():
151
  continue
152
- target.symlink_to(snap)
 
 
 
 
 
 
153
 
154
 
155
  def _bootstrap_spaces_cache() -> None:
156
- """On HF Spaces, prepare ./models/<org>/<repo>/ so ACE-Step finds preloaded weights.
157
-
158
- Earlier versions tried to ``cp -al`` (hardlink-mirror) the build-user-owned
159
- ~/.cache/huggingface into a runtime-writable ~/hf-cache-rw, but on ZeroGPU
160
- the HF cache and the home directory live on different filesystems, so
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
- _symlink_snapshots_into_models()
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:
lyrics_lm.py CHANGED
@@ -195,9 +195,14 @@ class _HFLM:
195
  repetition_penalty=float(kw.get("repetition_penalty", 1.1)),
196
  do_sample=True,
197
  )
198
- full = self.tokenizer.decode(out[0], skip_special_tokens=True)
199
- # Strip the prompt prefix so only the generated text remains.
200
- return full[len(prompt) :] if full.startswith(prompt) else full
 
 
 
 
 
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(