A newer version of the Gradio SDK is available: 6.20.0
FINETUNE.md β midnight-static-writer LoRA
Goal: Nemotron 3 Nano 4B that writes idiomatic, performable radio scripts in our schema on the first try. Guided decoding already guarantees validity; the LoRA buys genre voice, pacing, castability, and SFX/music prompt quality.
Dataset spec
- Size: 400 examples. 60 per genre Γ 6 genres + 40 adversarial.
- Format: chat SFT β
[system: writer prompt w/ genre pack][user: premise] [assistant: schema-valid JSON]. Assistant turn is raw JSON, no markdown fences (the runtime never sees fences; don't train them in). - Premise diversity (per genre, roughly even):
- object-centric ("a phone that only receives calls from 1962")
- relationship-centric ("two rival lighthouse keepers, one light")
- place-centric ("the last petrol pump before the salt flats")
- one-word premises ("static", "monsoon") β users will do this
- overlong rambling premises (train truncation-to-essence behavior)
- Adversarial 40: premises that tempt rule-breaking β real celebrity names (writer must genericize), gore-bait (must go atmospheric), requests for 10-minute epics (must compress), non-English premises (Hinglish for RAAT, translate-then-write otherwise).
- hindi_melodrama: dialogue in romanized Hinglish ONLY (Kokoro constraint). Filmi idiom: "Kasam khao!", interrupted weddings, lost-and-found lockets.
Generation procedure (Codex-attributed β this is prize evidence)
modal/gen_dataset.pydrives generation: golden fixture for the genre as the style exemplar + premise from a seeded premise bank (write ~70 premises/genre by hand+Codex; cheap, high-leverage).- Generator model: anything goes at dev time (no param cap on tooling) β use the strongest model available to you; quality of training data is the single biggest lever in this project.
- Validation gauntlet (auto-reject):
Script.model_validate()+ cross-field checks from SCHEMA.md- runtime estimate 55β95s
- voice-roster distinctness: no two cast members share a VoiceID
- banned-content regex (slurs list, explicit terms)
- dedupe: title trigram overlap <0.6 vs accepted set
- Human pass: skim 5 random samples/genre. You are checking for performability β read a scene aloud. If it's stilted, fix the genre pack prompt, regen that genre. Budget: 45 minutes, no more.
- Publish dataset to HF Hub (
ik-labs/midnight-static-scripts) β this is the "Sharing is Caring" bonus criterion and blog material.
Training runbook (modal/finetune.py)
- Framework: Unsloth, QLoRA 4-bit base, LoRA r=16, alpha=32, dropout=0.05, target modules: q,k,v,o,gate,up,down projections.
- Hyperparams: 3 epochs, lr 2e-4 cosine, batch 8 (grad-accum to fit), max_seq_len 4096, packing off (examples are whole conversations).
- Hardware: Modal A100-40GB, expected wall time β€45 min, cost β a few dollars of the $250 credit.
- Outputs: LoRA adapter β HF Hub
ik-labs/midnight-static-writer(public β Bonus Quest "Well-Tuned" criterion) + merged fp16 checkpoint pushed privately for vLLM serving (vLLM can serve base+adapter too; merged is simpler on ZeroGPU β decide by what cold-starts faster).
Eval (30 minutes, not a research project)
Hold out 5 premises/genre (never in training). For base vs LoRA, score:
- Validity-unassisted: generate WITHOUT guided decoding; % parseable. (Proxy for how hard the grammar must fight the model. LoRA should roughly double base.)
- Performability spot-check: render 6 broadcasts (one per genre) end-to-end; listen. Ship/no-ship is a vibe call β you have ears.
- Roster discipline: % scripts using β₯2 distinct deliveries and a narrator-type. LoRA target β₯80%.
If LoRA underperforms base on listening test β ship base + grammar, keep the published dataset/adapter for the Bonus Quest narrative, and say so honestly in the blog (negative results are good Field Notes content).
Day-1 dependency
VoiceID enum must be verified against installed Kokoro voices BEFORE dataset generation. The LoRA memorizes the roster; a renamed voice after training = silent miscasting forever.