mod decoder;
mod encoder;
mod frontend;
mod search;
mod tokenizer;
use std::sync::{Mutex, PoisonError};
use decoder::Decoder;
use encoder::Encoder;
use frontend::{Frontend, HOP, N_FFT, SAMPLES};
use search::Ids;
use tokenizer::Tokenizer;
use crate::error::{Result, bail};
use crate::gguf::Gguf;
use crate::gpu::{Buffer, Gpu, Groups};
use crate::{Token, Transcript};
const NO_SPEECH: f32 = 0.6;
const LOGPROB: f32 = -1.0;
pub struct Whisper {
gpu: Gpu,
frontend: Frontend,
tok: Tokenizer,
ids: Ids,
sot: usize,
transcribe: usize,
no_speech: usize,
languages: Vec<usize>,
encoder: Encoder,
decoder: Decoder,
arena: Mutex<Arena>,
}
struct Arena {
enc: encoder::Buffers,
dec: decoder::Buffers,
read: Buffer,
window: Groups,
steps: Groups,
}
impl Whisper {
pub fn load(g: &Gguf) -> Result<Self> {
let u = |key: &str| g.get::<u32>(&format!("stt.whisper.{key}")).map(|v| v as usize);
for (key, want) in [
("stt.frontend.type", "mel"),
("stt.frontend.window", "hann_periodic"),
("stt.frontend.normalize", "whisper_logmel"),
("stt.frontend.pad_mode", "reflect"),
("stt.whisper.encoder.activation", "gelu"),
("stt.whisper.decoder.activation", "gelu"),
] {
g.check(key, &[want])?;
}
for (key, want) in [
("stt.frontend.sample_rate", 16000),
("stt.frontend.n_fft", N_FFT),
("stt.frontend.win_length", N_FFT),
("stt.frontend.hop_length", HOP),
("stt.frontend.n_samples", SAMPLES),
("stt.frontend.nb_max_frames", SAMPLES / HOP),
("stt.whisper.encoder.max_source_positions", SAMPLES / HOP / 2),
] {
g.check(key, &[want as u32])?;
}
g.check("stt.whisper.decoder.tie_word_embeddings", &[true])?;
g.check("stt.whisper.decoder.scale_embedding", &[false])?;
let d = u("encoder.d_model")?;
let enc = encoder::Config {
mels: u("encoder.num_mel_bins")?,
d,
heads: u("encoder.n_heads")?,
layers: u("encoder.n_layers")?,
d_ff: u("encoder.ffn_dim")?,
frames: SAMPLES / HOP / 2,
};
let dec = decoder::Config {
d: u("decoder.d_model")?,
heads: u("decoder.n_heads")?,
layers: u("decoder.n_layers")?,
d_ff: u("decoder.ffn_dim")?,
vocab: u("decoder.vocab_size")?,
context: u("decoder.max_target_positions")?,
};
for heads in [enc.heads, dec.heads] {
let hd = d / heads.max(1);
if dec.d != d || hd * heads != d || hd > 64 || !hd.is_multiple_of(8) || dec.context > 2048 {
bail!("unsupported shape: d_model {d}, {heads} heads, {} positions", dec.context);
}
}
if !(3 * enc.mels).is_multiple_of(32) {
bail!("{} mels: conv0's 3·mels inputs must be a multiple of 32", enc.mels);
}
let tok = Tokenizer::new(g)?;
if tok.len() != dec.vocab {
bail!("vocabulary of {} tokens, decoder.vocab_size {}", tok.len(), dec.vocab);
}
let list = |key: &str| g.array::<i32>(key).map(|v| v.iter().map(|&i| i as usize).collect());
let no_timestamps = u("no_timestamps_token_id")?;
let ids = Ids {
eot: g.get::<u32>("tokenizer.ggml.eos_token_id")? as usize,
no_timestamps,
begin: no_timestamps + 1,
suppress: list("stt.whisper.suppress_tokens")?,
begin_suppress: list("stt.whisper.begin_suppress_tokens")?,
};
let languages = (g.array::<&str>("general.languages")?.iter())
.map(|code| tok.find(&format!("<|{code}|>")))
.collect::<Option<Vec<_>>>();
let Some(languages) = languages else { bail!("a language of general.languages has no token") };
let all = [&ids.suppress[..], &ids.begin_suppress, &languages, &[ids.eot, no_timestamps]];
if all.iter().flat_map(|v| v.iter()).any(|&i| i >= dec.vocab) {
bail!("special token ids out of the vocabulary");
}
let gpu = Gpu::new(true)?;
let frontend = Frontend::new(g, enc.mels)?;
let encoder = Encoder::load(&gpu, g, enc)?;
let decoder = Decoder::load(&gpu, g, dec)?;
let frames = encoder.cfg.frames;
let arena = Arena {
enc: encoder.buffers(&gpu),
dec: decoder.buffers(&gpu, frames),
read: gpu.readback(decoder.cfg.vocab),
window: Vec::new(),
steps: Vec::new(),
};
let model = Self {
sot: u("sot_token_id")?,
transcribe: u("transcribe_token_id")?,
no_speech: no_timestamps - 1,
languages,
gpu,
frontend,
tok,
ids,
encoder,
decoder,
arena: Mutex::new(arena),
};
if model.gpu.warm_up() {
model.window(&mut model.lock(), None, 64)?;
}
Ok(model)
}
fn lock(&self) -> std::sync::MutexGuard<'_, Arena> {
self.arena.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn transcribe(&self, pcm: &[f32]) -> Result<Transcript> {
let mels = self.encoder.cfg.mels;
let window = 2 * self.encoder.cfg.frames;
let mel = self.frontend.compute(pcm);
let total = mel.len() / mels;
let mut a = self.lock();
let (mut seek, mut language) = (0, None);
let (mut text, mut tokens) = (Vec::new(), Vec::new());
while seek < total {
let frames = (total - seek).min(window);
let mut chunk = mel[seek * mels..(seek + frames) * mels].to_vec();
chunk.resize(window * mels, 0.0);
let sot = self.window(&mut a, Some(&chunk), window / 2)?;
let first = self.languages[0];
let language = *language.get_or_insert_with(|| {
self.languages.iter().fold(first, |best, &id| if sot[id] > sot[best] { id } else { best })
});
let no_speech = search::probability(&sot, self.no_speech);
let prompt = [self.sot, language, self.transcribe];
let logits = self.steps(&mut a, &prompt[1..], 1)?;
let (generated, logprob) =
search::decode(&self.ids, logits, prompt.len(), no_speech > NO_SPEECH, |token, pos| {
self.steps(&mut a, &[token], pos)
})?;
if no_speech > NO_SPEECH && logprob < LOGPROB {
seek += frames;
continue;
}
let (segments, advance) = search::retrieve(&self.ids, &generated, frames);
for s in segments {
let pieces = tokenizer::pieces(&self.tok, &s.text);
if tokenizer::trim(&pieces.concat()).is_empty() {
continue;
}
let start_ms = (seek * 10 + s.start * 20) as u32;
let token = |(&id, piece): (&usize, String)| Token { id: id as u32, piece, start_ms };
tokens.extend(s.text.iter().zip(pieces).map(token));
}
text.extend(
generated.iter().filter(|&&id| id < self.ids.eot).flat_map(|&id| self.tok.bytes(id)),
);
seek += if advance == 0 { frames } else { advance };
}
let text = tokenizer::trim(&String::from_utf8_lossy(&text)).to_string();
Ok(Transcript { text, tokens })
}
fn window(&self, a: &mut Arena, mel: Option<&[f32]>, t: usize) -> Result<Vec<f32>> {
let Arena { enc, dec, read, window, .. } = a;
if let Some(mel) = mel {
self.gpu.write(&enc.mel, mel);
}
self.gpu.write(&enc.x, &self.encoder.positions[..t * self.encoder.cfg.d]);
self.gpu.run(window, &dec.logits, read, self.decoder.cfg.vocab, |p| {
self.encoder.record(p, enc, t);
self.decoder.record_cross(p, dec, &enc.y, &enc.h, t);
self.decoder.record_step(p, dec, self.sot, 0, t);
})
}
fn steps(&self, a: &mut Arena, tokens: &[usize], pos: usize) -> Result<Vec<f32>> {
let frames = self.encoder.cfg.frames;
let Arena { dec, read, steps, .. } = a;
self.gpu.run(steps, &dec.logits, read, self.decoder.cfg.vocab, |p| {
for (i, &token) in tokens.iter().enumerate() {
self.decoder.record_step(p, dec, token, pos + i, frames);
}
})
}
}