//! Phase 13.8 — slice 2d smoke test. //! //! Loads the real `emotion2vec/emotion2vec_plus_base` pickle from HF Hub, //! runs a synthetic-audio forward pass, and dumps the 9 raw class logits //! plus the argmax label. This is the first real-weight integration of //! the candle port: every key in the upstream state dict must map to a //! candle param of matching shape, or `load_from_pickle` will fail. //! //! Usage: //! ```bash //! cargo run -p rtx-csm --release --features metal --example emotion2vec_smoke //! ``` use anyhow::{Context, Result}; use candle_core::{Device, Tensor}; use hf_hub::api::sync::Api; use rtx_csm::emotion2vec::{Classifier, Emotion2Vec}; const REPO: &str = "emotion2vec/emotion2vec_plus_base"; const PT_FILE: &str = "model.pt"; fn main() -> Result<()> { tracing_subscriber::fmt().init(); let device = if candle_core::utils::metal_is_available() { Device::new_metal(0)? } else { Device::Cpu }; eprintln!("device: {device:?}"); // Resolve the cached .pt (or download on first run). let api = Api::new().context("hf_hub init")?; let path = api .model(REPO.to_string()) .get(PT_FILE) .with_context(|| format!("download {PT_FILE} from {REPO}"))?; eprintln!("pickle: {}", path.display()); // Load the candle port from the real pickle. let load_t = std::time::Instant::now(); let model = Emotion2Vec::load_from_pickle(&path, &device)?; eprintln!("loaded model in {:.2}s", load_t.elapsed().as_secs_f32()); // Synthesize 2 seconds of low-amplitude noise as a smoke input. // (Real evaluation in slice 3 will use a known-emotion clip.) let audio = Tensor::randn(0f32, 0.05, (1, 1, 32_000), &device)?; let fwd_t = std::time::Instant::now(); let logits = model.forward(&audio)?; eprintln!("forward in {:.0} ms", fwd_t.elapsed().as_millis()); let logits_vec = logits.flatten_all()?.to_vec1::()?; let labels = [ "angry", "disgusted", "fearful", "happy", "neutral", "other", "sad", "surprised", "", ]; println!(); println!("=== logits (raw, before softmax) ==="); for (i, l) in logits_vec.iter().enumerate() { println!(" {}: {:>9.4} ({})", i, l, labels[i]); } // Argmax → emotion label. let argmax = logits_vec .iter() .enumerate() .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .map(|(i, _)| i) .unwrap_or(0); let bucket = Classifier::tag_for_class(argmax as u32); println!(); println!( "argmax: class {argmax} ({}) → 5-bucket label {}", labels[argmax], bucket.as_tag() ); Ok(()) }