rtx-csm: WavLM-SV converter + end-to-end speaker similarity

Phase 5c — pure-Rust converter for microsoft/wavlm-base-plus-sv with
auto-detecting weight_norm merger; verified on real 100M-param weights:
load + embed + cosine-similarity round-trip works on Metal.

- wavlm_sv_convert.rs: candle_core::pickle reads pytorch_model.bin
  directly. merge_weight_norm_auto picks the kept dim from g's shape
  (dim=0 for AudioSeal SEANet, dim=2 for WavLM pos_conv_embed). Skips
  classifier.*/objective.* (train-only AMSoftmax head).
- examples/wavlm_sv_convert: HF download + convert CLI. Verified output:
  1 weight_norm pair merged + 261 passthrough + 3 skipped = 262 tensors.
- examples/wavlm_sv_demo: load + embed pair of WAVs + cosine similarity.
- examples/audioseal_inspect: gains --which wavlm-sv variant for key
  discovery.
- hub.rs: REPO_WAVLM_SV + resolve_wavlm_sv() helper.
- wavlm_sv::XVectorHead bug fix: layer_weights is top-level, not nested
  under prefix.

Verified end-to-end on Metal: cosine sim 0.9985 on same-speaker pair
(CSM vs CSM-watermarked, 10s @ 24 kHz resampled to 16 kHz). Numerical
parity vs HF reference is Phase 5d.

3 converter tests + 12 wavlm_sv tests; 78 lib tests total green.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
osobh
2026-04-25 19:57:52 -07:00
co-authored by Claude Opus 4.7
parent 3165b9339b
commit d1dba7a05c
8 changed files with 364 additions and 1 deletions
+1 -1
View File
@@ -579,7 +579,7 @@ impl XVectorHead {
/// vb is rooted at the *top* of the WavLMForXVector state_dict (so we
/// read `layer_weights`, `projector`, `tdnn.{0..4}`, `feature_extractor`).
pub fn new(vb: VarBuilder) -> candle_core::Result<Self> {
let layer_weights = vb.pp("layer_weights").get(NUM_LAYERS + 1, "")?;
let layer_weights = vb.get(NUM_LAYERS + 1, "layer_weights")?;
let projector = linear(HIDDEN_DIM, FEATURE_DIM, vb.pp("projector"))?;
// TDNN specs: (in, out, kernel, dilation)
let tdnn_specs: [(usize, usize, usize, usize); 5] = [