A newer version of the Gradio SDK is available: 6.20.0
ARCHITECTURE.md β Midnight Static
System diagram
βββββββββββββββββββββββββββββββββββββββββββββββ
β GRADIO SPACE (ZeroGPU) β
β β
mic βββΊ Nemotron ASR ββ β
βββΊ Nemotron Nano 4B + LoRA βββΊ Script JSON β
text ββββββββββββββββββ (vLLM, guided JSON) β β
βΌ β
β βββββββββββββ fan-out βββββββββββββ β
β β TTS: Kokoro per line β β
β β SFX: embed β cache match ββhitβββΊβ β
β β ββmissββΊ SAO Small gen β β
β β Music: embed β bed/sting library β β
β ββββββββββββββββ¬ββββββββββββββββββββ β
β βΌ β
β Mixer (pydub/ffmpeg) β
β βΌ β
β broadcast.mp3 + (stretch) FLUX poster β
βββββββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββ MODAL (training & batch only) ββββββββββββ
β gen_dataset.py β 400 synthetic scripts (Codex-driven sessions) β
β finetune.py β LoRA on Nemotron Nano 4B (Unsloth, A100) β
β batch_audio.py β SFX library (~150 clips) + music beds/stings β
β (6 genres Γ 4 beds Γ 3 stings) + embeddings β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Model inventory (Tiny Titan evidence β keep exact and current)
| Model | Params | Role | Runtime location |
|---|---|---|---|
| Nemotron 3 Nano 4B + LoRA | 4.0B | scriptwriter | ZeroGPU (vLLM) |
| Nemotron 3 ASR | <1B | call-in transcription | ZeroGPU |
| Kokoro-82M | 0.082B | TTS, all voices | ZeroGPU (near-CPU fast) |
| MusicGen-small | 0.30B | music (batch only) | Modal batch |
| Stable Audio Open Small | ~0.34B | SFX (batch + cache-miss) | Modal batch + ZeroGPU |
| Embedding model (e.g. bge-small, 33M) | 0.033B | SFX/music matching | ZeroGPU CPU |
| FLUX.2 Klein 4B (stretch) | 4.0B | episode posters | ZeroGPU, async |
| VoxCPM2 (gate G1) | <4B (verify) | TTS co-engine | ZeroGPU |
Ceiling check: every model individually β€4B β (Tiny Titan), sum β 9B βͺ 32B β .
ZeroGPU strategy
- One Space,
@spaces.GPU(duration=...)on innermost calls only:writer.generate()durationβ30sfx.generate_miss()durationβ25 (rare path)poster.generate()durationβ30 (async, never blocks audio)
- Kokoro + embeddings run on CPU β do NOT burn GPU quota on them.
- Models lazy-load on first use, then persist in process globals.
- vLLM engine kept warm via module-level init guarded by a flag; if ZeroGPU cold-starts hurt (>20s), switch writer to transformers + xgrammar (slower tokens, faster init) β decide empirically Day 1.
Asset library design (assets/)
assets/
manifest.json # [{id, kind: sfx|bed|sting, genre?, prompt,
# file, embedding: [..]}]
sfx/*.wav # ~150 clips, 24kHz mono, 1β6s
music/{genre}/*.wav # 4 beds (30β45s, loopable) + 3 stings (3β6s) per genre
- Matching: embed the script's text prompt (bge-small), cosine vs manifest.
- SFX: threshold β₯0.62 β use cached; else generate (then append to manifest at runtime β the library learns).
- Music: ALWAYS nearest match within the script's genre, never generate at runtime (beds are too slow to generate live).
- Manifest + wavs ship in the Space repo via Git LFS (~80MB budget).
Pipeline orchestration (src/pipeline.py)
- Async stages with a
StageEvent(stage, status, detail)queue consumed by the UI frequency-scan loader. Stage order:asr? β write β cast_validate β tts β₯ sfx β₯ music β mix β on_air β poster?. - TTS/SFX/music run concurrently (asyncio + thread pool; they're different devices: CPU/GPU/disk).
- Single retry policy: schema/cross-field validation failure β one retry with error appended; second failure β swap in genre fixture's structure with user's premise woven into title/logline (never show an error page; the station "improvises").
Latency budget (p50, warm)
| Stage | Target |
|---|---|
| ASR (if voice) | 2s |
| Script (4B, ~1.4k tok) | 12s |
| TTS (~18 lines, CPU, parallel) | 5s |
| SFX (cache hits) | 0.3s |
| Music (library) | 0.1s |
| Mix + encode | 3s |
| Total (text premise) | ~21s |
| Cache-miss SFX penalty | +8s each (β€2 misses typical) |
p50 β€35s target from SPEC.md has ~14s headroom for cold starts.
Failure modes & responses
| Failure | Response |
|---|---|
| vLLM OOM / engine crash | restart engine once; then transformers fallback path |
| ZeroGPU quota exhausted (judge traffic) | pre-cached showcases still play (CPU); banner: "Station at capacity β enjoy a rerun" |
| ffmpeg mix error | re-mix without SFX layer (dialogue+music only) |
| ASR garbage | show transcript for confirm/edit before writing |
Security/content
- Premise input length-capped (300 chars) and passed through the writer's PG-13 system constraints; no user text is ever shell-interpolated (ffmpeg called with arg lists, never shell=True).