harp-dev Claude Opus 5.5 commited on
Commit
4e203ad
·
verified ·
1 Parent(s): 1988a49

Load synchronously in the worker, and keep a lossless container

Browse files

Loads the model synchronously in the worker, moves the model card first, and returns a FLAC or AIFF input in its own container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +35 -43
app.py CHANGED
@@ -8,7 +8,6 @@ import contextlib
8
  import tempfile
9
  import torch
10
  import os
11
- import threading
12
  import librosa
13
  import soundfile as sf
14
  import gradio as gr
@@ -18,18 +17,38 @@ from pyharp.tags import Subcategory
18
 
19
  from anyaccomp.inference_utils import Sing2SongInferencePipeline
20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  repo_id = "amphion/anyaccomp"
22
  base_dir = os.path.dirname(os.path.abspath(__file__))
23
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
24
 
25
- # Model loading state. Populated by the background thread.
26
  inference_pipeline = None
27
- model_loading = True
28
  model_error = None
29
 
30
 
31
  def load_model():
32
- global inference_pipeline, model_loading, model_error
33
  try:
34
  checkpoint_marker = os.path.join(base_dir, "pretrained", "flow_matching")
35
  if not os.path.exists(checkpoint_marker):
@@ -47,8 +66,7 @@ def load_model():
47
  vocoder_checkpoint_path = os.path.join(model_dir, "pretrained/vocoder")
48
 
49
  print("Initializing AnyAccomp InferencePipeline...", flush=True)
50
- # Loaded on the CPU, since this runs in a background thread. The worker
51
- # moves the modules to the GPU once loading is done (see below).
52
  pipeline = Sing2SongInferencePipeline(
53
  checkpoint_path, cfg_path,
54
  vocoder_checkpoint_path, vocoder_cfg_path,
@@ -60,42 +78,16 @@ def load_model():
60
  except Exception as e:
61
  model_error = str(e)
62
  print(f"Error loading model: {e}", flush=True)
63
- finally:
64
- model_loading = False
65
 
66
 
67
- # Create a ModelCard describing this endpoint for HARP.
68
- model_card = ModelCard(
69
- name="AnyAccomp",
70
- description=(
71
- "Upload a 3-30 second vocal or instrument track and the model will "
72
- "generate an accompaniment. The code is MIT and the weights are "
73
- "CC-BY-4.0, which requires attribution. Generation runs at 24 kHz in "
74
- "mono; both outputs are resampled back to the input's rate and copied "
75
- "across its channels, so they match the format they came in with but "
76
- "carry no stereo image. Repository: "
77
- "https://github.com/AmphionTeam/AnyAccomp Paper: "
78
- "https://arxiv.org/abs/2509.14052"
79
- ),
80
- author="Junan Zhang, Yunjia Zhang, Xueyao Zhang, Zhizheng Wu",
81
- tags=[Subcategory.ACCOMPANIMENT, SampleRate(24000), Channels(1), 'music'],
82
- )
83
-
84
- # Start model loading in the background so the server can start immediately.
85
- _loader = threading.Thread(target=load_model, daemon=True)
86
- _loader.start()
87
-
88
- # pyharp's worker imports this file to reach process_fn (as __mp_main__ rather than
89
- # __main__), so it finishes loading here, before process_fn can reserve a GPU, instead
90
- # of racing the check there. The server itself keeps loading in the background, which
91
- # lets it start at once.
92
  if __name__ != "__main__":
93
- _loader.join()
94
-
95
- # ZeroGPU wants models placed on CUDA at module level, where its emulation records each
96
- # placement for every GPU call to replay, rather than inside the call. The worker is the
97
- # process that runs process_fn, so it places them here, on its main thread, once the
98
- # loader is done. The server never runs process_fn, so its copy stays on the CPU.
99
  if inference_pipeline is not None:
100
  for module in (inference_pipeline.model, inference_pipeline.coco_model,
101
  inference_pipeline.vocoder):
@@ -106,8 +98,6 @@ if __name__ != "__main__":
106
  def process_fn(vocal_filepath, n_timesteps): # add , cfg_scale to re-enable
107
  # Checked before the GPU is reserved, so a request the model cannot take spends none of the
108
  # caller's ZeroGPU allowance
109
- if model_loading:
110
- raise gr.Error("Model is still loading, please wait a moment and try again.")
111
  if inference_pipeline is None:
112
  raise gr.Error(f"Model failed to load: {model_error}")
113
 
@@ -154,9 +144,11 @@ def accompany(vocal_filepath, n_timesteps):
154
  wav = librosa.util.fix_length(data=wav, size=len(vocal_audio))
155
  mixture_wav = wav + vocal_audio
156
 
157
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
 
 
158
  accompaniment_path = f.name
159
- with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as f:
160
  mixture_path = f.name
161
 
162
  def _restore(audio):
 
8
  import tempfile
9
  import torch
10
  import os
 
11
  import librosa
12
  import soundfile as sf
13
  import gradio as gr
 
17
 
18
  from anyaccomp.inference_utils import Sing2SongInferencePipeline
19
 
20
+ # Create a ModelCard describing this endpoint for HARP.
21
+ model_card = ModelCard(
22
+ name="AnyAccomp",
23
+ description=(
24
+ "Upload a 3-30 second vocal or instrument track and the model will "
25
+ "generate an accompaniment. The code is MIT and the weights are "
26
+ "CC-BY-4.0, which requires attribution. Generation runs at 24 kHz in "
27
+ "mono; both outputs are resampled back to the input's rate and copied "
28
+ "across its channels, so they match the format they came in with but "
29
+ "carry no stereo image. Repository: "
30
+ "https://github.com/AmphionTeam/AnyAccomp Paper: "
31
+ "https://arxiv.org/abs/2509.14052"
32
+ ),
33
+ author="Junan Zhang, Yunjia Zhang, Xueyao Zhang, Zhizheng Wu",
34
+ tags=[Subcategory.ACCOMPANIMENT, SampleRate(24000), Channels(1), 'music'],
35
+ )
36
+
37
+ # A lossless input keeps its container. A lossy one comes back as WAV, since encoding the result
38
+ # lossily again would expose the artifacts of the first encode and add a second set.
39
+ LOSSLESS = {".wav", ".flac", ".aiff", ".aif"}
40
+
41
  repo_id = "amphion/anyaccomp"
42
  base_dir = os.path.dirname(os.path.abspath(__file__))
43
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
44
 
45
+ # Model loading state, populated in the worker
46
  inference_pipeline = None
 
47
  model_error = None
48
 
49
 
50
  def load_model():
51
+ global inference_pipeline, model_error
52
  try:
53
  checkpoint_marker = os.path.join(base_dir, "pretrained", "flow_matching")
54
  if not os.path.exists(checkpoint_marker):
 
66
  vocoder_checkpoint_path = os.path.join(model_dir, "pretrained/vocoder")
67
 
68
  print("Initializing AnyAccomp InferencePipeline...", flush=True)
69
+ # Loaded on the CPU, and moved to the GPU once loading is done (see below)
 
70
  pipeline = Sing2SongInferencePipeline(
71
  checkpoint_path, cfg_path,
72
  vocoder_checkpoint_path, vocoder_cfg_path,
 
78
  except Exception as e:
79
  model_error = str(e)
80
  print(f"Error loading model: {e}", flush=True)
 
 
81
 
82
 
83
+ # pyharp runs process_fn in a worker, which imports this file as __mp_main__ (rather than
84
+ # __main__) and starts with the server, so the model is downloaded and loaded there alone, before
85
+ # process_fn can reserve a GPU. A failure is kept rather than raised, so that each request can
86
+ # report it. ZeroGPU wants models placed on CUDA at module level, where its emulation records each
87
+ # placement for every GPU call to replay, rather than inside the call, so the worker places them
88
+ # here, on its main thread, once loading is done.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  if __name__ != "__main__":
90
+ load_model()
 
 
 
 
 
91
  if inference_pipeline is not None:
92
  for module in (inference_pipeline.model, inference_pipeline.coco_model,
93
  inference_pipeline.vocoder):
 
98
  def process_fn(vocal_filepath, n_timesteps): # add , cfg_scale to re-enable
99
  # Checked before the GPU is reserved, so a request the model cannot take spends none of the
100
  # caller's ZeroGPU allowance
 
 
101
  if inference_pipeline is None:
102
  raise gr.Error(f"Model failed to load: {model_error}")
103
 
 
144
  wav = librosa.util.fix_length(data=wav, size=len(vocal_audio))
145
  mixture_wav = wav + vocal_audio
146
 
147
+ input_ext = os.path.splitext(vocal_filepath)[1].lower()
148
+ output_ext = input_ext if input_ext in LOSSLESS else ".wav"
149
+ with tempfile.NamedTemporaryFile(suffix=output_ext, delete=False) as f:
150
  accompaniment_path = f.name
151
+ with tempfile.NamedTemporaryFile(suffix=output_ext, delete=False) as f:
152
  mixture_path = f.name
153
 
154
  def _restore(audio):