#![allow(clippy::type_complexity)]
#![allow(clippy::large_enum_variant)]
use std::path::PathBuf;
use std::time::Instant;
use anyhow::{Context, Result};
use inferencelayer::GpuCtx;
use inferencelayer::whisper::{Whisper, WhisperEncoder};
use inferencelayer::whisper_gpu::{WhisperDecoderGpu, WhisperEncoderGpu};
enum Enc {
Cpu,
Gpu(GpuCtx, WhisperEncoderGpu, Option<WhisperDecoderGpu>, String),
}
fn read_wav(path: &PathBuf) -> Result<Vec<f32>> {
let bytes = std::fs::read(path).with_context(|| format!("read {}", path.display()))?;
let dp = bytes
.windows(4)
.position(|w| w == b"data")
.context("no `data` chunk — expected a 16 kHz mono PCM16 WAV")?
+ 8;
Ok(bytes[dp..]
.chunks_exact(2)
.map(|c| i16::from_le_bytes(c.try_into().unwrap()) as f32 / 32768.0)
.collect())
}
fn main() -> Result<()> {
let mut args = std::env::args().skip(1);
let usage = "usage: whisper <model-dir> <audio.wav> (16 kHz mono PCM16)";
let dir = PathBuf::from(args.next().context(usage)?);
let wav = PathBuf::from(args.next().context(usage)?);
let samples = read_wav(&wav)?;
let audio_secs = samples.len() as f32 / 16_000.0;
let model = Whisper::load(&dir).context("load model")?;
let want_gpu = std::env::var("WHISPER_GPU")
.map(|v| v != "0")
.unwrap_or(true);
let want_gpu_decode = std::env::var("WHISPER_GPU_DECODE")
.map(|v| v == "1")
.unwrap_or(false);
let engine = match want_gpu.then(GpuCtx::new).transpose() {
Ok(Some(ctx)) => {
let gpu = WhisperEncoderGpu::new(&ctx, WhisperEncoder::load(&dir)?, 1500)?;
let dec = if want_gpu_decode {
Some(WhisperDecoderGpu::new(&ctx, &model)?)
} else {
None
};
let device = format!("gpu ({})", ctx.backend);
Enc::Gpu(ctx, gpu, dec, device)
}
_ => Enc::Cpu,
};
let t0 = Instant::now();
let enc = match &engine {
Enc::Gpu(ctx, gpu, _, _) => gpu.forward(ctx, &model.encoder.logmel(&samples))?,
Enc::Cpu => model.encoder.encode(&samples),
};
let enc_ms = t0.elapsed().as_secs_f32() * 1e3;
let enc_dev = match &engine {
Enc::Gpu(_, _, _, d) => d.as_str(),
Enc::Cpu => "cpu",
};
let t1 = Instant::now();
let (ids, dec_dev) = match &engine {
Enc::Gpu(ctx, _, Some(dec), _) => (dec.generate(ctx, &enc, model.max_target())?, "gpu"),
_ => (model.generate_from_enc(&enc, model.max_target()), "cpu"),
};
let text = model.decode_text(&dir, &ids)?;
let dec_ms = t1.elapsed().as_secs_f32() * 1e3;
let total = enc_ms + dec_ms;
eprintln!(
"[whisper] {audio_secs:.1}s audio · encoder {enc_dev} {enc_ms:.0} ms · decode {dec_dev} \
{dec_ms:.0} ms · total {total:.0} ms ({:.0}x realtime)",
audio_secs * 1000.0 / total.max(1e-3),
);
println!("{text}");
Ok(())
}