Files
rustytorch/crates/models/rtx-csm/examples/wavlm_sv_inspect.rs
T
osobhandClaude Opus 4.7 e61bd70e03 rtx-csm: WavLM-SV bug fixes — gelu_erf + gru_rel_pos_const loading
Two real bugs found via code inspection against HF source:

1. candle's .gelu() is the tanh approximation; PyTorch's default 'gelu'
   activation (used in WavLM via ACT2FN['gelu']) is the exact erf-based
   version. Switched all 3 sites (feature extractor convs, pos_conv,
   FFN) from .gelu() to .gelu_erf() to match the reference.

2. gru_rel_pos_const lookup used vb.pp("name").get(shape, "") which
   resolves to "<prefix>.name." (trailing dot) and fails to find the
   tensor. The .or_else(|_| zeros) silently swallowed the failure,
   leaving all 12 layers' gating constants at zero instead of the
   trained values. Fixed to attn.get(shape, "gru_rel_pos_const") which
   resolves correctly.

examples/wavlm_sv_inspect.rs: utility for sanity-checking specific
tensors inside converted safetensors (e.g. layer_weights).

Same-content same-speaker cosine: 0.9985 -> 0.9963 (≈unchanged).
Cross-content same-speaker cosine: 0.4882 -> 0.4118 (still drifting).
Phase 5d (Python reference comparison) remains the gate for
identifying the residual numerical drift.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2026-04-25 22:00:59 -07:00

53 lines
1.7 KiB
Rust

//! Inspect specific tensors inside a converted WavLM-SV safetensors.
//! Useful for sanity-checking weight loading and key naming.
use anyhow::Result;
use candle_core::Device;
use clap::Parser;
use std::path::PathBuf;
#[derive(Debug, Parser)]
struct Cli {
#[arg(long)]
weights: PathBuf,
/// Tensor name to dump (e.g. "layer_weights", "projector.weight").
#[arg(long)]
key: String,
/// Apply softmax along dim=0 before printing (for layer_weights).
#[arg(long)]
softmax: bool,
/// Number of leading elements to print (default: all).
#[arg(long)]
head: Option<usize>,
}
fn main() -> Result<()> {
let cli = Cli::parse();
let tensors = candle_core::safetensors::load(&cli.weights, &Device::Cpu)?;
let t = tensors
.get(&cli.key)
.ok_or_else(|| anyhow::anyhow!("key not found: {}", cli.key))?;
println!("{}: dtype={:?}, shape={:?}", cli.key, t.dtype(), t.dims());
let to_print = if cli.softmax {
candle_nn::ops::softmax(t, 0)?
} else {
t.clone()
};
let v: Vec<f32> = to_print
.flatten_all()?
.to_dtype(candle_core::DType::F32)?
.to_vec1()?;
let limit = cli.head.unwrap_or(v.len()).min(v.len());
let head: Vec<&f32> = v.iter().take(limit).collect();
println!("first {limit} values: {head:?}");
if v.len() > limit {
let tail: Vec<&f32> = v.iter().rev().take(8).collect();
println!("(last 8 values, reversed): {tail:?}");
}
let sum: f32 = v.iter().sum();
let max = v.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
let min = v.iter().cloned().fold(f32::INFINITY, f32::min);
println!("sum={sum:.6}, min={min:.6}, max={max:.6}");
Ok(())
}