Closes the LoRA inference path that was previously stubbed. Two new
public APIs in rtx-csm:
1. lora::load_lora_set_from_safetensors(path, config) -> LoraSet
Reads a trained adapter file (produced by training::
save_lora_adapter[_with_metadata]). Pairs the .lora_a / .lora_b
tensors by base-weight prefix into LoraAdapter entries.
2. lora::merge_into_safetensors(base, lora, scale, output)
Reads the base CSM safetensors, folds in the LoRA deltas at the
given scale (typically alpha/rank from training), writes a merged
safetensors. Original dtype preserved (F16 on Metal, BF16 on
CUDA, F32 on CPU). Tensors LoRA doesn't target are passed
through unchanged.
3. Generator::load_csm_1b_from_path(path, device)
Variant of load_csm_1b that takes an explicit weights path
instead of going through the HF cache. Mimi + tokenizer still
resolve via the hub. This is the path consumers use to load a
merged checkpoint.
MergeReport struct restructured to expose merged/skipped/passthrough
counts so callers can verify the adapter actually targeted weights.
The previous typed-error test is replaced with a missing-base-file
test that exercises the real code path.
Used by zeroclaw-channel-voice's `--lora-adapter` flag to bake a
LoRA adapter into a per-process merged checkpoint at boot, with
zero per-inference overhead.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
8-gen bench (4 emotions × 2 corpora) at seed=42 against firdhokk
Whisper-LV3:
target RAVDESS CREMA-D
happy happy (0.999) ✓ happy (0.999) ✓
angry neutral (0.92) sad (0.99)
fearful happy (0.998) fearful (0.984) ✓
sad angry (0.99) fearful (0.99)
CREMA-D 2/4 vs RAVDESS 1/4. Larger / more naturalistic corpus
produces more class-pure fearful direction. Neither corpus solves
angry or sad — recipe shifts into 'vague expressivity' rather than
class-specific corners.
Practical: prefer CREMA-D when available; A/B both per emotion if
class precision matters.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Both backbones (FP csm_fork + Q8 csm_quantized) now expose 7 LoRA hooks
per layer: q/k/v/o on attention plus gate/up/down (Llama w1/w3/w2) on
the SwiGLU MLP. LoraConfig::default() still returns q+v only (backward
compat for existing trained adapters); LoraConfig::extended() returns
the full 7-module set. lora_train + lora_finetune_step take a
--extended-lora flag.
Verified end-to-end on Metal: injection across all 16 backbone layers
× 7 modules = 224 adapter Vars, 5.6M trainable params (~6.6× q+v alone,
still tiny vs the 1B base). Step-0 loss matches the q+v baseline
exactly (B=0 init is also a no-op for the new hooks). Forward + backward
+ AdamW + refresh_lora cycle runs without errors.
LoRA test suite: 9 pass (added config_extended_targets_full_attn_and_mlp);
full lib suite still 92/92.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>