| # 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) |
| |
| 1. `modal/gen_dataset.py` drives 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). |
| 2. 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. |
| 3. **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 |
| 4. **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. |
| 5. 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: |
|
|
| 1. **Validity-unassisted**: generate WITHOUT guided decoding; % parseable. |
| (Proxy for how hard the grammar must fight the model. LoRA should |
| roughly double base.) |
| 2. **Performability spot-check**: render 6 broadcasts (one per genre) |
| end-to-end; listen. Ship/no-ship is a vibe call β you have ears. |
| 3. **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. |
|
|