Spaces:
Running on Zero
Running on Zero
Commit History
-
huggingface-cli download abalanescu/flow2 app.py --repo-type space --local-dir
Verified signatureaefe85b
-
fix(zerogpu): persist quota ledger so a restart stops claiming phantom minutes
The adaptive lease sizing added in b5a8e39 clamps the requested GPU lease to the remaining daily allowance, but the tracker behind it was in-memory only. Every Space reload therefore reported a full 40 minutes while Hugging Face was already refusing leases, so the clamp saw headroom that did not exist and re-requested the full window: exactly the denial it was meant to avoid, and the reason the UI could show '31/40 min left' next to a GPU-limit 429. Persist the ledger to the Space's writable data directory (opt out with FLOW_QUOTA_LEDGER_PATH, disabled under pytest) and report zero remaining while a fresh platform refusal is on record, since an observed refusal outranks a ledger that only knows the leases this process watched.db08952 -
fix(zerogpu): size GPU lease to remaining quota instead of a fixed 120s
Hugging Face bills the whole requested lease and denies any request larger than the remaining daily allowance. Every chat call asked for a fixed 120s, so once the day was nearly spent the platform refused the lease outright even though a short completion would have fit comfortably. That denial was reported upstream as a bare failure, which downstream routers then mislabeled as a per-credential/model cooldown. Clamp the requested lease to the actual remaining headroom (with a floor so an exhausted day still attempts one real lease, since a denied lease does not consume quota) and report the quota ceiling as a structured 429 carrying requested/remaining/daily seconds. Duration is now a per-invocation callable across all ZeroGPU entry points.b5a8e39 -
fix(api): expose /v1/warmup on the served Gradio app
AndrianBalanescuProduction serves demo.app, which builds its own route table by hand. /v1/warmup only existed on fastapi_app, so the endpoint every test mounts answered 200 locally and 404 on the live Space. Register it alongside the other REST routes and add a parity test that fails whenever the two route tables drift apart.c91a92a -
feat(models): add Qwen3.8-27B Turbo Coder to the ZeroGPU catalog
AndrianBalanescuAdds the DavidAU TURBO NEO-CODER MAX build as a 27B option. The regular Q4_K_M quant is picked over the MTP variant on purpose: this integration never enables draft decoding, so MTP heads would only add weight. Also moves GGUF fetching out of the GPU lease. A cold 17 GB download far exceeds the 120 s budget, so a first call could time out and burn quota; weights are now fetched on CPU before gpu_tracked_call opens the lease. Fixes a test that touched the live Space: the remote MiniMax mock read .full_url unconditionally, so the string download leg raised, the route fell through to ZeroGPU, and the test depended on the daily quota.b08fc22
-
fix(api): harden empty-content handling for ZeroGPU chat
AndrianBalanescullama-cpp returns content=None whenever it resolves a native tool call, and some GGUF chat templates emit an empty string instead of null. The UI handler, warmup probe, and chat endpoint all assumed a string and could crash or echo "None" back. Coerce the value once per call site and cover the empty-content and nested-JSON-arguments paths with regression tests.a86614a -
fix(api): recover Qwen tool calls on ZeroGPU
AndrianBalanescuThe XML/think regexes required literal spaces around the tags, but Qwen emits newlines, so agentic calls were dropped and returned as plain text with finish_reason=stop. llama-cpp native message.tool_calls were also ignored because only message.content was parsed. Match arbitrary whitespace in both tool_call formats, split reasoning on the bare closing tag, and prefer structured native tool calls, converting arguments back to JSON strings. Adds regression tests for both paths.09957a9
-
fix(cron): reserve ten ZeroGPU minutes for interactive use
AndrianBalanescu6b2fd64 -
fix(space): keep optional YuE wheel out of base build stage
AndrianBalanescuebc46ef -
fix(space): install vendored YuE wheel during private build
AndrianBalanescu1fa206f -
test: send FLOW_API_KEY bearer in phonetic e2e client fixture
AndrianBalanescuThe studio auth middleware (P0-1) returns 401 for unauthenticated requests. test_phonetic_e2e.py lives in tests/ (integration), so the unit-fixture sweep missed it. Add the conditional Authorization header matching the other studio test fixtures. verify_all.sh now fully green.8ec66e3 -
fix(p1): break infinite recursion in singleton accessors
AndrianBalanescu_get_dictionary_manager() and _get_dispatcher() called themselves instead of instantiating DictionaryManager()/ModelDispatcher(), causing a C-stack-overflow SIGABRT on any call. Instantiate the real classes. Verified: full unit suite 351 passed, 5 skipped, exit 0 in both deterministic and random order.33d061b -
security+perf: enforce studio auth and cache singletons
AndrianBalanescuP0-1: add FLOW_API_KEY bearer-token middleware to studio_server.py (parity with app.py), with static/OPTIONS exemption and ?key= fallback. P1: cache DictionaryManager + ModelDispatcher via mtime/settings invalidation instead of per-request instantiation. Tests: update fixtures to send Authorization; add test_studio_auth_enforcement.py (7 tests). Closes P0-1 and P1 from audit.1a4837b -
secrets: remove hardcoded OmniRoute API key, source from env
AndrianBalanescu- scripts/omniroute_workflows_smoke.py: read OMNIROUTE_API_KEY from env - docs/OMNIROUTE_MUSIC_PROVIDERS.md: reference $OMNIROUTE_API_KEY - .env.example: document FLOW_API_KEY and OMNIROUTE_API_KEY Closes P0-2 from security audit (leaked sk-omni-macbook-9b2f4a7c1e).91f1e9a -
docs(farm): document the captions-first chain and transcript topics
AndrianBalanescuThe usage section still described transcription as homelab-only and the source list did not mention youtube_transcript. Updated, in Romanian: - source order: youtube-captions (primary, 0 GPU) -> homelab-http -> local-cpu, with the --no-subtitles / --no-local-fallback opt-outs and setup_local_whisper.sh provisioning - flow diagram now shows the 2b transcript source (max 3 topics, genre/persona from the corpus category join) - every claim re-verified before writing: --help shows both flags, setup script confirmed idempotent, saved transcripts carry the source field (legacy ones honestly noted as missing it)08038f7 -
fix(farm): derive transcript genre/persona from corpus category
AndrianBalanescuTranscript topics hardcoded the news persona for every transcript, but the corpus already carries an authoritative category per video and the established design is category-first (CATEGORY_STYLE). A comedy video's transcript would have reached the farm as protv_anchor instead of the standup persona. Transcripts now join the corpus by video_id: category maps through CATEGORY_STYLE, published_at comes from the corpus record, and the hardcoded pair remains only as the fallback for videos that left the corpus. Both paths are covered by tests, and hardcoding the category again fails the comedy-join test.cc2fce9 -
feat(farm): feed harvested transcript content into live topics
AndrianBalanescuTranscription was write-only: the pipeline saved transcripts under data/transcripts/ but live topics came only from video titles, so the actual spoken material never reached the song farm. The bulletin's real content ("prețul motorinei s-a majorat cu aproape 2 RON") was harvested and then ignored. live_topics now reads transcript files as a first-class source (youtube_transcript, quota 3) and extracts one representative sentence per transcript. Extraction is data-driven per the no-hardcoded-lists rule: - document frequency across harvested transcripts down-weights boilerplate (greetings, "vă prezint știrile") without any stopword list, and - sentences must anchor on a specific title term (7+ chars, longer terms weigh more), so an unrelated transcript contributes nothing rather than an arbitrary sentence. Measured on real data: the PRO TV bulletin transcript yields "Republica Moldova este dependentă în totalitate de importurile de produse petroliere..." as a farm …1af93fd -
fix(harvest): use YouTube captions instead of GPU-bound transcription
AndrianBalanescuTranscription was reported as an unfixable external blocker. It was not. Measured on a real 12k-view PRO TV bulletin (Chfv2fMJ-mk): the audio download returns HTTP 403 (YouTube bot wall) and the homelab Whisper service returns CUDA OOM because its 8 GB GPU is oversubscribed (TTS 2414 MiB + whisper 5222 MiB = 7636 of 8151 MiB used). Both paths that existed failed, so the whole harvest produced nothing. The video's own ASR caption track, however, downloads fine and yielded 5,527 words of real broadcast Romanian in ~4s with no GPU and no media download. Captions are now the primary source; audio+Whisper is the fallback: 1. youtube-captions -- yt-dlp --write-auto-subs, json3/vtt parser 2. homelab-http -- existing homelab Whisper service 3. local-cpu -- local faster-whisper int8 on CPU The json3 payload emits overlapping rolling windows, so consecutive duplicate lines are dropped instead of emitted several times. For videos without captions, scripts/setup_local_whis…ec9315c
-
docs(farm): document harvesting -> farm flow and its measurements
AndrianBalanescuAdds docs/HARVEST_TO_FARM.md (RO) covering what the harvesting infrastructure is, how topics reach the farm, and how to use it, with measured before/after figures for every fix and an explicit corrections section recording claims that were overstated on a first pass. Also includes 18 newly harvested corpus entries and a README pointer to the doc.16813b3 -
fix(studio): keep song structure when trimming, add remote MiniMax route
AndrianBalanescuTwo defects found while wiring the farm, neither in the harvesting path: - lyricist_engine: _cap_lyric_sections deleted whole structural sections to save a single line, so songs could lose their Outro entirely. Trimming now keeps at least one line per section and only drops trailing non-core sections if still over budget. This also fixes the pre-existing red test test_bake_multilevel_lyrics_structure. - model_dispatcher: implement the remote MiniMax Music 3 route that an orphaned TDD spec (5 failing tests) already described, tried before ZeroGPU so the free endpoint costs no quota and falls back on any error. This commit also carries an in-flight lyric-metadata sanitization change in lyricist_engine.py from a concurrent writer in the same checkout (its own test file is still untracked upstream of this commit). It was staged together inadvertently while this file was being edited. The combined state is coherent: 331 tests pass at this commit.f688cb3 -
fix(pipeline): stop reporting success over broken harvest stages
AndrianBalanescuThe weekly harvest reported "COMPLETED SUCCESSFULLY" while transcription had been producing nothing for weeks: 8 of the 12 recorded runs transcribed zero videos, and 15 of 16 runs printed the success banner anyway. - transcribe: return attempted/processed/failed counts and exit 1 when work was attempted but nothing was produced, so a total failure is visible. - transcribe: the configured endpoint pointed at homelab:8086, where nothing listens; Whisper is served via Caddy at /api/v1/whisper/v1/audio/ transcriptions, and requires the multipart part to be named "file". - transcribe: select the real downloaded media file. The old check accepted any non-".part" file, which matched yt-dlp's ".ytdl" metadata JSON and made Whisper answer HTTP 500 "Invalid data". - transcribe: drop --impersonate (combined with browser cookies it removes audio-only formats, so the fallback downloaded a 475 MB 1080p video) and normalize everything to 16 kHz mono WAV with ffmpeg. - transcribe: enfor…58cb127 -
feat(farm): wire harvested Moldovan content into song topics
AndrianBalanescuLive topics were sourced from stale or mis-parsed material, so the farm's "real-time" topics were not real: the news digest was picked by reverse filename sort (always the legacy digest_*.json), the YouTube corpus was ranked by all-time view_count (decade-old viral videos), and Harbuz TV was parsed from the PRO TV news rundown instead of its own satire section, so dedup always erased it. - live_topics: select digests by mtime, rank corpus by publish date with a freshness window anchored on the corpus, per-source quotas instead of a global cap, and a Bresenham accumulator so the 15% live ratio spreads evenly instead of bursting into the first indices. - live_topics: map genre/persona from the corpus's authoritative `category` field. Title-keyword matching missed real comedy ("ABS #53"), making the Comedy Rap / Standup genre and standup persona unreachable. - farm_songs: fix --loop spinning forever on a dead endpoint (it only broke the inner loop while logging "Stopping") an…cdafb70 -
Fix lyrics baker UX + farm speed
AndrianBalanescu- lyrics.html: replace thousands-of-checkboxes model list with searchable dropdown select; default baker model -> antigravity/gemini-3.7-flash-tiered - lyrics.html: make lyric lines contenteditable; onblur syncs back to draft.song + updates right inspector panel (fixes 'panel doesn't update') - farm_songs.py: switch to /generate-direct (single POST, WAV in body) with fallback to 2-step /generate; saves a download round-trip per song - index.html: farm widget token field (from prior commit)dba7bab -
Add farm token field to widget + recalibrate lyric length to 20s/line
AndrianBalanescu- widget: add Bearer token input (endpoint needs auth; empty token -> 401) - engine: endpoint sings ~20s/line not 15s/line (12 lines measured 241s); 180s target now -> 9 lines - farm token passed in start body -> MINIMAX_REMOTE_TOKEN enve62ffc8 -
Fix song farm duration: size lyrics to hit 2:30-3:30
AndrianBalanescuMiniMax Music 3 sings the lyrics through ONCE at ~15s/line, so track duration = line count, NOT the audio_duration param (which it ignores). Earlier farm songs came out 66-115s because the LLM baked 30-50 line telenovela lyrics. - bake_multilevel_lyrics: compute target_lines from duration_seconds (180s -> 12 lines), inject a hard length rule into the prompt, and cap parsed sections as a backstop (LLM ignored the prompt alone) - _cap_lyric_sections: trim to target, preserving intro/verse/chorus spine - tests: 281 passed (cap unit test + target_lines scaling)ac4a7ec -
Add Song Farm control center widget + remote MiniMax retry
AndrianBalanescu- farm_songs.py: UUID song ids (collision fix), on-disk vault sidecars (WAV + JSON) so the studio app inventories farm output, MiniMax generate_remote retry + generous timeout - studio_server.py: /api/farm/{status,start,stop,log} control endpoints (pid probe, disk vault counters, subprocess lifecycle) - index.html: new Song Farm tab (live status, start/stop, tunable songs/concurrency/duration/model/endpoint, recent songs, log tail, 5s auto-poll) - tests/unit/test_song_farm.py: 6 offline route-contract tests iBrowse QA: PASS (Farm tab renders all sections, 0 console errors).b162b1b -
refactor(space): fully decouple Moldovan studio from ZeroGPU Space
AndrianBalanescu- Remove sys.path injection of moldovan-qwen/ from app.py - Remove create_moldovan_song_handler, chat_moldovan_persona_handler, convert_dialect_handler (engines now live in local studio :8090 / studio-pb :8096) - Remove the Moldovan Creative Media & Personas Gradio tab (3 sub-tabs) - Neutralize Moldovan-specific default prompts in audio job worker and /v1/audio/speech; replace Music tab placeholders with generic examples app.py shrinks 2643 -> 2476 lines. 281 tests pass.9d8ac0c
-
chore(security): set visibility to private, sanitize internal Tailscale IPs, and drop qa_evidence tracking
AndrianBalanescu4fb8728
-
test(music): archive Moldovan music A/B verdict
flowmasterMeasured duration/rate/loudness + Whisper-ro lyric alignment for the 3 models (MiniMax 0/4 on a 15s clip - intro hums, YuE2-3B 2/4, ACE-Step 1/4), with notes on the short-clip artifact and the ZeroGPU auth/billing quirk. Raw WAVs stay local (14MB, *.wav is gitignored).f54ed01 -
test(music): add objective vocal-alignment probe to the A/B harness
flowmasterForce Romanian Whisper transcription of each rendered test song and count expected Moldovan words. Turns the A/B from a listening impression into a measurable signal: MiniMax 0/4 (vocalised as hums at 15s), YuE2-3B 2/4, ACE-Step 1/4.5fbb200 -
test(music): authenticate ZeroGPU calls and send lyrics in the probe
flowmasterTwo bugs that made the quota ladder report false negatives: - Requests carried no HF token, so HF's edge issued an anonymous IP-token and ZeroGPU charged the tiny IP quota instead of the PRO account. Pass HF_TOKEN as a bearer header on join + SSE poll; the probe now runs against the real 40min/day budget. - The probe sent empty lyrics, which MiniMax rejects inside the GPU body, so a healthy Space looked like a quota failure. Send a short non-empty probe line.946a961 -
test(music): add A/B comparison harness for the 3 test-song WAVs
flowmasterffprobe metrics per model file (duration/rate/channels/codec), aligned with run_music_tests.sh slugify() output, missing-file reporting, and the lyric-quality scoring checklist (diacritics, alignment, vibe, intelligibility, artifacts). Exit 2 until all three WAVs exist.90907c1 -
test(music): log probe outcomes to probe_history.log
flowmasterTimestamped UTC lines per probe so the quota ladder's outcomes are auditable across wakes (probe start / QUOTA OK / QUOTA EXHAUSTED).91b29df -
test(music): add ZeroGPU smoke-test runner with quota probe
flowmasterscripts/run_music_tests.sh probes the 40min/day quota with a cheap 5s pre-cached MiniMax job (exit 2 when exhausted) then renders the named models over the Gradio SSE API and downloads the WAVs. Reusable after quota reset and for future A/B rounds.3bf091a -
fix(zerogpu): revert music lease to 120s and pre-cache YuE2/ACE on CPU
flowmasterZeroGPU on Blackwell RTX PRO 6000 multiplies requested duration by 1.5 (duration_factor), so 240s was rejected as 360s > max allowed ('ZeroGPU illegal duration'). Revert to 120s (180s effective) and move the 5-10GB YuE2/ACE weight downloads to startup CPU pre-cache so the GPU lease only pays for load+generation.a477dbc -
perf(music): raise ZeroGPU lease to 240s for music endpoints
flowmasterCold-start for YuE2-3B (4.9GB) and ACE-Step (9.7GB) downloads + BF16 load + generation exceeded the 120s lease; ZeroGPU killed the job mid-generate and the Gradio session was GC'd (404 Session not found). 240s covers cold start plus full-length generation.00f6003 -
fix(build): reference vendored yue2 wheel by HF resolve URL
flowmasterThe Space build mounts only requirements.txt at pip time (relative wheels/ path is not present in the image yet), so pip reported the file missing. Point the requirement at the wheel's HF resolve URL instead; the file itself stays vendored in-repo (non-LFS).02c7547 -
fix(build): keep vendored yue2 wheel out of LFS so Docker sees real bytes
flowmasterThe *.whl LFS rule stored the 66KB wheel as a pointer; the Space build mounted the pointer text, so pip found no wheel at wheels/. Add a gitattributes exception for wheels/*.whl and commit the real blob.9c8864d -
fix(deps): vendor repackaged yue2 wheel without Requires-Dist pins
flowmasterHF Spaces builds requirements.txt with a single pip install command, so pip-level --no-deps is unavailable. Repackage the official yue2_infer 0.1.5 wheel with its Requires-Dist lines stripped (torch==2.10.0 and huggingface-hub==0.36.2 would conflict with the pinned diffusers set). Pure-python wheel (66KB); runtime deps torch/transformers/numpy/ soundfile already satisfied by the Space; add tiktoken explicitly.bf1985b -
fix(music): wire YuE2-3B via its yue2 wheel and fix ACE-Step output API
flowmaster- YuE2-3B is not a diffusers ModularPipeline (repo has no model_index.json): use YuE2Pipeline.from_pretrained from the bundled yue2_infer wheel, called with style/lyrics/cot/seed; audio written from SongResult. - Install wheel with --no-deps so its torch==2.10.0 / hub==0.36.2 pins cannot break the Space's pinned diffusers set; add tiktoken. - ACE-Step: read output.audios[0] (channels, samples), transpose on write, use pipe.sample_rate (AudioPipelineOutput has no .audio attr), enable VAE tiling to bound decode memory.0a6dbf5 -
chore: sync deploy state
AndrianBalanescuab60391 -
feat: add YuE2-3B + ACE-Step v1.5 to ZeroGPU music pipeline (commercial-safe ACE-Step, editable YuE2)
AndrianBalanescud542710 -
fix(dispatcher): re-raise ZeroGPUQuotaError caught by generic except handlers
AndrianBalanescuQuota stops raised inside the Gradio SSE loop and the async-jobs poll loop were swallowed by their own 'except Exception' handlers, downgraded to None and finally to a generic RuntimeError. The cron therefore marked all 6 tracks 'failed' on 09-12/09-13 instead of saving resume state and deferring to the next day, burning zero of the remaining quota. Re-raise ZeroGPUQuotaError explicitly in both loops so the nightly cron persists resume state and recovers the album automatically.fc7efb2
-
fix(hub): add model alias resolution and persist canonical ibrowse qa evidence
AndrianBalanescuf725c6a -
fix(zerogpu): prevent nested @spaces.GPU lease in chat and drop GPU decorator on health probe
AndrianBalanescudc5d324
-
chore: sync master lyricist lexicon markdown
AndrianBalanescu0a5f48d -
docs(lexicon): sync master lyricist lexicon markdown with master dictionary (730 terms)
AndrianBalanescubd9a68e -
feat(lyrics-baker): streaming SSE arena, Pydantic schemas, dictionary expansion
AndrianBalanescu- Add BakeMultilevelLyricsRequest Pydantic schema for validated input - Stream arena endpoint /api/media/lyrics/baker/stream-arena with SSE - Frontend: real-time streaming display with fallback to batch - Expand moldovan_master_dictionary.json (+309 entries) - Dialect converter: cleaner level transitions - Refactor test_lyrics_baker.py: reduce duplication, align with new schemas - lyrics.html: streaming reader + tab rendering per draft8e0d687 -
test: cron-level regression for quota stop saving resume state and aborting album
AndrianBalanescuSimulates the real Space behaviour where the lease attempt itself raises ZeroGPUQuotaError ('exceeded your Pro ZeroGPU quota (180s requested vs 64s left)') while the /v1/gpu/status counter stays blind. Asserts the album aborts at the failing track, marks it deferred_quota, sets interrupted_by_quota, and persists resume state with next_track_index and the exact planned track order.443904f -
fix: classify ZeroGPU quota stops surfaced via Gradio queue output errors and async-jobs failed status
AndrianBalanescuThe cron treated hard account-level quota stops as generic track failures: Try 1 (Gradio SSE) printed 'Gradio queue output error: You have exceeded your Pro ZeroGPU quota (180s requested vs 64s left)' but returned None, and Try 2 (async jobs) did the same with 'GPU task aborted'. generate_music only classified the final aggregated gpu_error, and only for two specific strings. Result: track marked audio_status=failed, resume state wiped, album 'finished'. Now both failure branches raise ZeroGPUQuotaError on quota text, and both generic except handlers re-raise it, so the cron saves resume state and stops the album immediately. Regression test added.9d67601