First working piece of the Moonshine v2 candle port. New src/moonshine.rs module with: - MoonshineConfig::tiny() — hyperparameters from HF config.json - ConvStem (Conv1d × 3) — audio stem, raw 16 kHz → 288-d hidden - load_conv_stem() — VarBuilder from HF safetensors Conv layout (verified against HF source): conv1: in=1, out=288, k=127, stride=64, no bias conv2: in=288, out=576, k=7, stride=3, bias conv3: in=576, out=288, k=3, stride=2, bias Activations: tanh after conv1, gelu_erf after conv2 / conv3 Smoke test (`examples/moonshine_smoke`): - Downloads UsefulSensors/moonshine-tiny from HF - Synthetic 10 s @ 16 kHz audio (silence + sine pulse) - input (1, 1, 160000) -> output (1, 415, 288) - Expected T_seq=415 ((160000-127)/64+1 -> 2498 -> 831 -> 415) - Output max abs = 23.17 (real signal, weights loaded correctly) Also extends `examples/moonshine_inspect` to dump conv shapes explicitly (was being truncated by the per-prefix `take(8)` cap). Next ship: encoder transformer block (partial RoPE, GELU MLP) and output layer norm. Tracked in Phase 8 plan; ~2-3 hours of work. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
97 lines
3.1 KiB
Rust
97 lines
3.1 KiB
Rust
//! Phase 8.4 spike — download Moonshine-tiny from HuggingFace and dump
|
|
//! the safetensors layout. Used to design the candle module shape.
|
|
//!
|
|
//! Architecture (from HF config.json):
|
|
//! encoder-decoder transformer, hidden_size=288, intermediate=1152,
|
|
//! 6 enc + 6 dec layers, 8 heads (head_dim=36 + pad to 40),
|
|
//! partial_rotary_factor=0.9, vocab=32768, max_pos=194, audio input
|
|
//! is raw waveform (no mel-spec preprocessing).
|
|
//!
|
|
//! Usage:
|
|
//! ```bash
|
|
//! cargo run -p rtx-csm --release --example moonshine_inspect
|
|
//! ```
|
|
|
|
use anyhow::{Context, Result};
|
|
use hf_hub::api::sync::Api;
|
|
|
|
fn main() -> Result<()> {
|
|
let api = Api::new().context("hf_hub init")?;
|
|
let repo = api.model("UsefulSensors/moonshine-tiny".to_string());
|
|
let weights_path = repo
|
|
.get("model.safetensors")
|
|
.context("download model.safetensors")?;
|
|
println!("downloaded: {}", weights_path.display());
|
|
let bytes = std::fs::read(&weights_path)?;
|
|
println!(
|
|
"file size: {} bytes ({:.1} MB)",
|
|
bytes.len(),
|
|
bytes.len() as f64 / 1_000_000.0
|
|
);
|
|
|
|
// Parse the safetensors header to enumerate tensor names + shapes.
|
|
let st = safetensors::SafeTensors::deserialize(&bytes)
|
|
.context("parse safetensors header")?;
|
|
let names = st.names();
|
|
println!("\n=== {} tensors ===", names.len());
|
|
|
|
// Group by prefix so the layered structure is visible.
|
|
let mut grouped: std::collections::BTreeMap<String, Vec<(String, Vec<usize>, &str)>> =
|
|
Default::default();
|
|
for name in names {
|
|
let tensor = st.tensor(name)?;
|
|
let prefix = name
|
|
.split('.')
|
|
.take(2)
|
|
.collect::<Vec<_>>()
|
|
.join(".");
|
|
let dtype = match tensor.dtype() {
|
|
safetensors::Dtype::F32 => "f32",
|
|
safetensors::Dtype::F16 => "f16",
|
|
safetensors::Dtype::BF16 => "bf16",
|
|
other => match other {
|
|
_ => "?",
|
|
},
|
|
};
|
|
grouped
|
|
.entry(prefix)
|
|
.or_default()
|
|
.push((name.to_string(), tensor.shape().to_vec(), dtype));
|
|
}
|
|
|
|
for (prefix, items) in grouped.iter() {
|
|
println!("\n[{prefix}] ({} tensors)", items.len());
|
|
for (name, shape, dtype) in items.iter().take(8) {
|
|
println!(" {name:<60} {dtype} {shape:?}");
|
|
}
|
|
if items.len() > 8 {
|
|
println!(" ... {} more", items.len() - 8);
|
|
}
|
|
|
|
// Special: dump everything matching `conv` since we need
|
|
// exact strides for the audio stem.
|
|
for (name, shape, dtype) in items.iter().filter(|(n, _, _)| n.contains("conv")) {
|
|
if !items.iter().take(8).any(|(n2, _, _)| n2 == name) {
|
|
println!(" {name:<60} {dtype} {shape:?} (conv detail)");
|
|
}
|
|
}
|
|
}
|
|
|
|
let total_params: usize = st
|
|
.names()
|
|
.iter()
|
|
.map(|n| {
|
|
st.tensor(n)
|
|
.map(|t| t.shape().iter().product::<usize>())
|
|
.unwrap_or(0)
|
|
})
|
|
.sum();
|
|
println!(
|
|
"\ntotal parameters: {} ({:.1} M)",
|
|
total_params,
|
|
total_params as f64 / 1_000_000.0
|
|
);
|
|
|
|
Ok(())
|
|
}
|