use crate::cpu::{Q8, matvecs};
use crate::error::{Result, bail};
use crate::gguf::Gguf;
pub struct Decoder {
hidden: usize,
joint: usize,
classes: usize,
blank: usize,
durations: Vec<usize>,
max_symbols: usize,
embed: Q8,
lstm: Vec<Lstm>,
pred_w: Q8,
pred_b: Vec<f32>,
out_w: Q8,
out_b: Vec<f32>,
}
struct Lstm {
wx: Q8,
wh: Q8,
bias: Vec<f32>,
}
impl Decoder {
pub fn new(g: &Gguf, classes: usize, blank: usize) -> Result<Self> {
let u = |key: &str| g.get::<u32>(&format!("stt.parakeet.{key}")).map(|v| v as usize);
let (hidden, joint) = (u("predictor.hidden")?, u("joint.hidden")?);
let durations = g.array::<i32>("stt.parakeet.tdt.durations")?;
let max_symbols = u("tdt.max_symbols")?;
g.check("stt.parakeet.joint.activation", &["relu"])?;
g.check("stt.parakeet.predictor.vocab", &[classes as u32])?;
if u("joint.num_extra_outputs")? != durations.len() || durations.iter().any(|&d| d < 0) {
bail!("TDT durations {durations:?} don't match the joint's extra outputs");
}
if max_symbols == 0 {
bail!("stt.parakeet.tdt.max_symbols is 0");
}
let t = |name: &str, dims: &[usize]| g.tensor(name, dims).and_then(|t| t.to_f32());
let q = |name: &str, dims: &[usize]| g.tensor(name, dims).and_then(|t| Q8::new(&t));
let lstm = (0..u("predictor.n_layers")?)
.map(|l| {
Ok(Lstm {
wx: q(&format!("pred.lstm.{l}.Wx"), &[hidden, 4 * hidden])?,
wh: q(&format!("pred.lstm.{l}.Wh"), &[hidden, 4 * hidden])?,
bias: t(&format!("pred.lstm.{l}.bias"), &[4 * hidden])?,
})
})
.collect::<Result<_>>()?;
let outputs = classes + durations.len();
Ok(Self {
hidden,
joint,
classes,
blank,
durations: durations.into_iter().map(|d| d as usize).collect(),
max_symbols,
embed: q("pred.embed.weight", &[hidden, classes])?,
lstm,
pred_w: q("joint.pred.weight", &[hidden, joint])?,
pred_b: t("joint.pred.bias", &[joint])?,
out_w: q("joint.out.weight", &[joint, outputs])?,
out_b: t("joint.out.bias", &[outputs])?,
})
}
pub fn decode(&self, enc: &[f32], width: usize) -> Vec<(u32, u32)> {
let j = self.joint;
let mut state = vec![0.0; 2 * self.lstm.len() * self.hidden];
let (mut next, mut pred) = self.predict(None, &state);
greedy(enc.len() / width, self.blank, &self.durations, self.max_symbols, |t, emitted| {
if let Some(token) = emitted {
state = std::mem::take(&mut next);
(next, pred) = self.predict(Some(token), &state);
}
self.joint(&enc[t * width..][..j], &pred)
})
}
fn predict(&self, token: Option<usize>, state: &[f32]) -> (Vec<f32>, Vec<f32>) {
let n = self.hidden;
let mut x = token.map_or(vec![0.0; n], |t| self.embed.row(t));
let mut next = vec![0.0; state.len()];
let sigmoid = |v: f32| 1.0 / (1.0 + (-v).exp());
for ((l, s), out) in
self.lstm.iter().zip(state.chunks_exact(2 * n)).zip(next.chunks_exact_mut(2 * n))
{
let (h, c) = s.split_at(n);
let [gx, gh] = matvecs([(&l.wx, &x), (&l.wh, h)]);
let gate = |i: usize| gx[i] + gh[i] + l.bias[i];
let (nh, nc) = out.split_at_mut(n);
for k in 0..n {
let (i, f, g, o) = (gate(k), gate(n + k), gate(2 * n + k), gate(3 * n + k));
nc[k] = sigmoid(f) * c[k] + sigmoid(i) * g.tanh();
nh[k] = sigmoid(o) * nc[k].tanh();
}
x = nh.to_vec();
}
let [pred] = matvecs([(&self.pred_w, &x)]);
let pred = pred.iter().zip(&self.pred_b).map(|(d, b)| d + b).collect();
(next, pred)
}
fn joint(&self, enc: &[f32], pred: &[f32]) -> (usize, usize) {
let z: Vec<f32> = enc.iter().zip(pred).map(|(e, p)| (e + p).max(0.0)).collect();
let [d] = matvecs([(&self.out_w, &z)]);
let logits: Vec<f32> = d.iter().zip(&self.out_b).map(|(d, b)| d + b).collect();
let (tokens, durations) = logits.split_at(self.classes);
(argmax(tokens), argmax(durations))
}
}
fn argmax(v: &[f32]) -> usize {
let mut best = 0;
for (i, &x) in v.iter().enumerate() {
if x > v[best] {
best = i;
}
}
best
}
fn greedy(
frames: usize,
blank: usize,
durations: &[usize],
max_symbols: usize,
mut joint: impl FnMut(usize, Option<usize>) -> (usize, usize),
) -> Vec<(u32, u32)> {
let (mut out, mut t, mut symbols, mut emitted) = (Vec::new(), 0, 0, None);
while t < frames {
let (token, d) = joint(t, emitted.take());
if token != blank {
out.push((token as u32, t as u32));
emitted = Some(token);
}
t += durations[d];
symbols += 1;
if durations[d] != 0 {
symbols = 0;
} else if symbols >= max_symbols || token == blank {
t += 1;
symbols = 0;
}
}
out
}
#[cfg(test)]
mod tests {
const BLANK: usize = 9;
type Calls = Vec<(usize, Option<usize>)>;
fn run(
frames: usize,
durations: &[usize],
script: &[(usize, usize)],
) -> (Vec<(u32, u32)>, Calls) {
let mut calls = Vec::new();
let mut script = script.iter();
let out = super::greedy(frames, BLANK, durations, 10, |t, emitted| {
calls.push((t, emitted));
*script.next().expect("script too short")
});
assert!(script.next().is_none(), "script too long");
(out, calls)
}
#[test]
fn blank_moves_on_and_tokens_stay() {
let script = [(BLANK, 0), (5, 0), (6, 2), (BLANK, 1), (7, 1)];
let (out, calls) = run(6, &[0, 1, 3], &script);
assert_eq!(out, [(5, 1), (6, 1), (7, 5)]);
assert_eq!(calls, [(0, None), (1, None), (1, Some(5)), (4, Some(6)), (5, None)]);
}
#[test]
fn ten_decisions_per_frame() {
let mut script = vec![(5, 0); 19];
script.extend([(BLANK, 0), (6, 1)]);
let (out, calls) = run(3, &[0, 1], &script);
let frames: Vec<u32> = out.iter().map(|&(_, t)| t).collect();
assert_eq!(frames, [vec![0; 10], vec![1; 9], vec![2]].concat());
assert_eq!(calls.iter().filter(|c| c.0 == 1).count(), 10);
}
}