eloigil6 Claude Opus 4.8 commited on
Commit
77907ff
·
1 Parent(s): af047f2

Report a time-based brew estimate so the progress bar moves on ZeroGPU (Stage 3)

Browse files

The @spaces.GPU worker is a separate process, so it can't push per-chunk progress back to /api/progress - the brew bar froze at 0% then jumped to 100% on the Space. /api/progress now returns a smooth time-based estimate (fractional done, capped at 92%) computed from the brew's start + a rough per-length budget, snapping to 100% when the tape lands. Local/stub paths keep their real per-chunk progress (the estimate only kicks in while a ZeroGPU brew is active).

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

Files changed (1) hide show
  1. app.py +37 -9
app.py CHANGED
@@ -23,6 +23,7 @@ import io
23
  import json
24
  import os
25
  import threading
 
26
  import wave
27
  from pathlib import Path
28
 
@@ -81,6 +82,11 @@ ALLOWED_SECONDS = (30, 60, 90)
81
  DEFAULT_SECONDS = int(os.getenv("LOFINITY_DURATION", "30"))
82
  OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434")
83
  OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3.2:3b")
 
 
 
 
 
84
 
85
  app = Server(title="LoFinity")
86
 
@@ -89,6 +95,10 @@ app = Server(title="LoFinity")
89
  # frontend polls /api/progress to fill its brewing bar.
90
  _PROGRESS = {"done": 0, "total": 1}
91
 
 
 
 
 
92
  # --- prompt enrichment --------------------------------------------------------
93
 
94
  ENRICH_SYSTEM = """\
@@ -419,9 +429,10 @@ def _gpu_budget(prompt: str, seconds: int = CHUNK_S) -> int:
419
  def gpu_brew(prompt: str, seconds: int = CHUNK_S) -> tuple:
420
  """ZeroGPU entry point — enrichment (MiniCPM) AND MusicGen on the real GPU in
421
  a single acquisition. Takes the raw vibe and returns
422
- (music_prompt, title, bed, samples, rate). No progress_cb: this body runs in
423
- a separate GPU worker, so _PROGRESS can't reach /api/progress yet (Stage 3)
424
- the brewing garden jumps to done on the Space. This path is Space-only."""
 
425
  music_prompt, title, bed = enrich_prompt(prompt)
426
  samples, rate = musicgen_engine(music_prompt, seconds)
427
  return music_prompt, title, bed, samples, rate
@@ -463,11 +474,20 @@ def generate_song(prompt: str, seconds: int = DEFAULT_SECONDS) -> dict:
463
  _PROGRESS.update(done=0, total=chunks)
464
 
465
  if IS_ZEROGPU and ENGINE != "stub":
466
- # On ZeroGPU both enrichment (MiniCPM) and MusicGen need the real GPU, so
467
- # they share ONE @spaces.GPU acquisition. That worker runs in a separate
468
- # process, so progress can't stream back yet (Stage 3): the bar jumps.
469
- print(f"[lofinity] brewing on GPU :: {prompt!r} ({seconds}s)")
470
- music_prompt, title, bed, samples, rate = gpu_brew(prompt, seconds)
 
 
 
 
 
 
 
 
 
471
  print(f"[lofinity] brewed {title!r} :: {music_prompt} [+ {bed}]")
472
  else:
473
  # Local / stub: enrich in-process (Ollama or fallback), then render with
@@ -493,7 +513,15 @@ def generate_song(prompt: str, seconds: int = DEFAULT_SECONDS) -> dict:
493
 
494
  @app.get("/api/progress")
495
  def progress() -> dict:
496
- """Chunks finished / total for the brew currently running, for the UI bar."""
 
 
 
 
 
 
 
 
497
  return dict(_PROGRESS)
498
 
499
 
 
23
  import json
24
  import os
25
  import threading
26
+ import time
27
  import wave
28
  from pathlib import Path
29
 
 
82
  DEFAULT_SECONDS = int(os.getenv("LOFINITY_DURATION", "30"))
83
  OLLAMA_URL = os.getenv("OLLAMA_URL", "http://localhost:11434")
84
  OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "llama3.2:3b")
85
+ # A ZeroGPU brew renders in a separate GPU worker process, so /api/progress can't
86
+ # read real per-chunk progress from it; it reports a time-based estimate from this
87
+ # rough per-length budget instead (tunable; only affects the cosmetic brew bar).
88
+ GPU_WARMUP_S = 5.0 # enrichment + cold-start/queue allowance before audio flows
89
+ GPU_SECS_PER_CHUNK = 10.0 # rough GPU render time per 30s chunk
90
 
91
  app = Server(title="LoFinity")
92
 
 
95
  # frontend polls /api/progress to fill its brewing bar.
96
  _PROGRESS = {"done": 0, "total": 1}
97
 
98
+ # A ZeroGPU brew's wall-clock start + estimated total, so /api/progress can report
99
+ # a smooth time-based estimate while the GPU worker is busy (see progress()).
100
+ _BREW = {"active": False, "start": 0.0, "est": 1.0, "total": 1}
101
+
102
  # --- prompt enrichment --------------------------------------------------------
103
 
104
  ENRICH_SYSTEM = """\
 
429
  def gpu_brew(prompt: str, seconds: int = CHUNK_S) -> tuple:
430
  """ZeroGPU entry point — enrichment (MiniCPM) AND MusicGen on the real GPU in
431
  a single acquisition. Takes the raw vibe and returns
432
+ (music_prompt, title, bed, samples, rate). It runs in a separate GPU worker
433
+ process and can't push per-chunk progress back to the web process, so
434
+ /api/progress reports a time-based estimate for the bar. This path is
435
+ Space-only."""
436
  music_prompt, title, bed = enrich_prompt(prompt)
437
  samples, rate = musicgen_engine(music_prompt, seconds)
438
  return music_prompt, title, bed, samples, rate
 
474
  _PROGRESS.update(done=0, total=chunks)
475
 
476
  if IS_ZEROGPU and ENGINE != "stub":
477
+ # On ZeroGPU enrichment (MiniCPM) and MusicGen share ONE @spaces.GPU
478
+ # acquisition in a separate worker process, which can't push real progress
479
+ # back so /api/progress reports a smooth time-based ESTIMATE driven by
480
+ # this brew's start + budget (capped <100% until the tape actually lands).
481
+ est = GPU_WARMUP_S + GPU_SECS_PER_CHUNK * chunks
482
+ _BREW.update(active=True, start=time.monotonic(), est=est, total=chunks)
483
+ print(f"[lofinity] brewing on GPU :: {prompt!r} ({seconds}s, ~{est:.0f}s est)")
484
+ try:
485
+ music_prompt, title, bed, samples, rate = gpu_brew(prompt, seconds)
486
+ finally:
487
+ # top the bar off BEFORE clearing active, so a poll landing in between
488
+ # reads 100% (from _PROGRESS), never the 0% this brew started at
489
+ _PROGRESS.update(done=chunks, total=chunks)
490
+ _BREW.update(active=False)
491
  print(f"[lofinity] brewed {title!r} :: {music_prompt} [+ {bed}]")
492
  else:
493
  # Local / stub: enrich in-process (Ollama or fallback), then render with
 
513
 
514
  @app.get("/api/progress")
515
  def progress() -> dict:
516
+ """Progress for the brewing bar. Local/stub report real per-chunk progress via
517
+ _PROGRESS. A ZeroGPU brew runs in a separate GPU worker that can't push
518
+ progress back, so report a smooth time-based ESTIMATE instead: a fractional
519
+ `done` (the frontend fills the bar to done/total) capped below 100% until the
520
+ real tape lands and _PROGRESS tops it off."""
521
+ if _BREW["active"] and _BREW["est"] > 0:
522
+ elapsed = time.monotonic() - _BREW["start"]
523
+ frac = min(0.92, elapsed / _BREW["est"])
524
+ return {"done": round(frac * _BREW["total"], 3), "total": _BREW["total"]}
525
  return dict(_PROGRESS)
526
 
527