rtx-csm: Phase 13.7 — --lora support in tts_server

Closes the gap where converse_server (Phase 12.4) supported LoRA but
the simpler HTTP-only tts_server didn't. Same flag set
(--lora / --lora-rank / --lora-alpha / --extended-lora) and same
apply_lora_adapter shared helper. Combines with --quantized-gguf
(Phase 12.6) for Q8 + voice clone over plain HTTP.

Verified end-to-end on Metal: tts_server --quantized-gguf … --lora …
boots, injects LoRA into the quantized backbone (q=16 k=16 v=16 o=16
+ MLP w1/w2/w3 = 224 tensors), listens. Single HTTP POST /v1/tts
returned 200 OK with a 146KB 24kHz mono WAV. Lib suite 110/110.

tts_server is now the simplest production deploy for a personalized
voice: HTTP-only, no STT/LLM overhead, LoRA + Q8 + AudioSeal/SilentCipher
+ WavLM-SV all available behind one binary.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-04-27 22:14:20 -07:00
co-authored by Claude Opus 4.7
parent 01081eb45a
commit a4fca2b126
@@ -92,6 +92,28 @@ struct Cli {
/// /v1/speaker_embed and /v1/speaker_compare are enabled. /// /v1/speaker_embed and /v1/speaker_compare are enabled.
#[arg(long)] #[arg(long)]
wavlm_sv: Option<PathBuf>, wavlm_sv: Option<PathBuf>,
/// LoRA voice adapter (safetensors, output of `lora_train` or
/// `lora_train_emotional`). Loaded into the backbone (FP or quantized
/// — combines with --quantized-gguf per Phase 12.6) before serving so
/// every TTS request uses the trained voice.
#[arg(long)]
lora: Option<PathBuf>,
/// LoRA rank override. When omitted, auto-detected from the adapter's
/// embedded metadata (Phase 12.5); falls back to 8 for older adapters.
#[arg(long)]
lora_rank: Option<usize>,
/// LoRA alpha override. Auto-detected from metadata when omitted.
#[arg(long)]
lora_alpha: Option<f32>,
/// Force extended LoRA coverage (q+k+v+output_proj + MLP). Auto-
/// detected from metadata when omitted; setting it on a classic q+v
/// adapter just allocates extra unused B=0 slots.
#[arg(long, default_value_t = false)]
extended_lora: bool,
} }
fn parse_message(s: &str) -> Result<u16> { fn parse_message(s: &str) -> Result<u16> {
@@ -484,6 +506,19 @@ async fn main() -> Result<()> {
}; };
tracing::info!("CSM-1B loaded"); tracing::info!("CSM-1B loaded");
// Optional LoRA adapter (Phase 12.4 + 12.5 self-describing metadata).
if let Some(lora_path) = cli.lora.as_ref() {
let extended_override = if cli.extended_lora { Some(true) } else { None };
rtx_csm::training::apply_lora_adapter(
&mut generator,
lora_path,
cli.lora_rank,
cli.lora_alpha,
extended_override,
&device,
)?;
}
// Optional AudioSeal — install inline + keep a detector instance. // Optional AudioSeal — install inline + keep a detector instance.
let audioseal_message = parse_message(&cli.audioseal_message)?; let audioseal_message = parse_message(&cli.audioseal_message)?;
let audioseal_detector = if let (Some(g), Some(d)) = ( let audioseal_detector = if let (Some(g), Some(d)) = (