mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Signed-off-by: Tyler Longwell <tlongwell@squareup.com> Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
120 lines
4.4 KiB
Rust
120 lines
4.4 KiB
Rust
//! Cold-vs-warm latency bench for Pocket TTS.
|
||
//!
|
||
//! This duplicates the small config-building snippet from `huddle::pocket` so it
|
||
//! doesn't depend on changing module visibility for a one-off dev tool.
|
||
//! Keep in sync with `huddle::pocket::load_text_to_speech`.
|
||
//!
|
||
//! Run with the model files in a directory (defaults to /tmp/pocket-tts-bench):
|
||
//! cargo run --release --example pocket_bench
|
||
//! cargo run --release --example pocket_bench /path/to/pocket-tts
|
||
|
||
use std::path::PathBuf;
|
||
use std::time::Instant;
|
||
|
||
use sherpa_onnx::{
|
||
self, GenerationConfig, OfflineTts, OfflineTtsConfig, OfflineTtsModelConfig,
|
||
OfflineTtsPocketModelConfig, Wave,
|
||
};
|
||
|
||
const SAMPLE_RATE: u32 = 24_000;
|
||
const TEST_TEXT: &str =
|
||
"Hello, this is a test of the new Pocket TTS engine running on sherpa-onnx.";
|
||
|
||
fn main() {
|
||
let model_dir = std::env::args()
|
||
.nth(1)
|
||
.unwrap_or_else(|| "/tmp/pocket-tts-bench".to_string());
|
||
println!("Model dir: {model_dir}");
|
||
|
||
let dir = PathBuf::from(&model_dir);
|
||
let p = |name: &str| dir.join(name).to_string_lossy().into_owned();
|
||
|
||
let t0 = Instant::now();
|
||
let cfg = OfflineTtsConfig {
|
||
model: OfflineTtsModelConfig {
|
||
pocket: OfflineTtsPocketModelConfig {
|
||
lm_main: Some(p("lm_main.int8.onnx")),
|
||
lm_flow: Some(p("lm_flow.int8.onnx")),
|
||
encoder: Some(p("encoder.onnx")),
|
||
decoder: Some(p("decoder.int8.onnx")),
|
||
text_conditioner: Some(p("text_conditioner.onnx")),
|
||
vocab_json: Some(p("vocab.json")),
|
||
token_scores_json: Some(p("token_scores.json")),
|
||
voice_embedding_cache_capacity: 16,
|
||
},
|
||
num_threads: 1,
|
||
debug: false,
|
||
..Default::default()
|
||
},
|
||
..Default::default()
|
||
};
|
||
let engine = OfflineTts::create(&cfg).expect("engine create");
|
||
let load_ms = t0.elapsed().as_secs_f32() * 1000.0;
|
||
println!("Engine load: {load_ms:.1} ms");
|
||
|
||
let t0 = Instant::now();
|
||
let voice_path = dir.join("reference_sample.wav");
|
||
let wave = Wave::read(voice_path.to_str().unwrap()).expect("voice WAV");
|
||
let samples = wave.samples().to_vec();
|
||
let sr = wave.sample_rate();
|
||
let voice_ms = t0.elapsed().as_secs_f32() * 1000.0;
|
||
println!("Voice load: {voice_ms:.1} ms");
|
||
|
||
let gen = || GenerationConfig {
|
||
speed: 1.05,
|
||
num_steps: 1,
|
||
silence_scale: 1.0, // production setting (huddle::pocket::SYNTH_SILENCE_SCALE)
|
||
reference_audio: Some(samples.clone()),
|
||
reference_sample_rate: sr,
|
||
..Default::default()
|
||
};
|
||
|
||
// Cold synth — first call after engine creation.
|
||
let t0 = Instant::now();
|
||
let cold = engine
|
||
.generate_with_config(TEST_TEXT, &gen(), None::<fn(&[f32], f32) -> bool>)
|
||
.expect("cold synth");
|
||
let cold_ms = t0.elapsed().as_secs_f32() * 1000.0;
|
||
let cold_audio_ms = (cold.samples().len() as f32 / SAMPLE_RATE as f32) * 1000.0;
|
||
let cold_rtf_x = cold_audio_ms / cold_ms;
|
||
println!(
|
||
"Cold synth: {cold_ms:.1} ms → {cold_audio_ms:.1} ms audio → {cold_rtf_x:.2}× realtime"
|
||
);
|
||
|
||
// Warm synth — second call.
|
||
let t0 = Instant::now();
|
||
let warm = engine
|
||
.generate_with_config(TEST_TEXT, &gen(), None::<fn(&[f32], f32) -> bool>)
|
||
.expect("warm synth");
|
||
let warm_ms = t0.elapsed().as_secs_f32() * 1000.0;
|
||
let warm_audio_ms = (warm.samples().len() as f32 / SAMPLE_RATE as f32) * 1000.0;
|
||
let warm_rtf_x = warm_audio_ms / warm_ms;
|
||
println!(
|
||
"Warm synth: {warm_ms:.1} ms → {warm_audio_ms:.1} ms audio → {warm_rtf_x:.2}× realtime"
|
||
);
|
||
|
||
// Write warm output for listening.
|
||
let out_path = "/tmp/pocket_bench_out.wav";
|
||
let ok = sherpa_onnx::write(out_path, warm.samples(), SAMPLE_RATE as i32);
|
||
println!(
|
||
"Wrote {} ({} samples, ok={ok})",
|
||
out_path,
|
||
warm.samples().len()
|
||
);
|
||
|
||
let delta_ms = cold_ms - warm_ms;
|
||
let delta_pct = (delta_ms / warm_ms) * 100.0;
|
||
println!();
|
||
println!("Cold/warm delta: {delta_ms:+.1} ms ({delta_pct:+.1}%)");
|
||
println!(
|
||
"Decision: warmup {}.",
|
||
if delta_ms > 200.0 {
|
||
"RECOMMENDED — significant cold-call penalty"
|
||
} else if delta_ms > 50.0 {
|
||
"OPTIONAL — small cold-call penalty"
|
||
} else {
|
||
"UNNECESSARY — cold and warm essentially equal"
|
||
}
|
||
);
|
||
}
|