Skip to main content

cortiq_engine/
mimo_audio.rs

1//! MiMo-V2.6 audio input (spec milestones M8, M9 and the tower half of M10).
2//!
3//! The chain, in order:
4//!
5//! 1. [`decode_wav`]: RIFF/WAVE with PCM 8/16/24/32, IEEE float 32/64 and
6//!    `WAVE_FORMAT_EXTENSIBLE`, any channel count. Samples become f32 in
7//!    [−1, 1] the way ffmpeg (torchcodec, the serving decoder) scales them.
8//! 2. [`resample_sinc`]: a port of torchaudio's `Resample` defaults
9//!    (`sinc_interp_hann`, width 6, rolloff 0.99, gcd-reduced), including the
10//!    float32 rounding of the target length. Every channel is resampled,
11//!    then the channels are averaged ([`wav_to_mono_24k`]).
12//! 3. [`log_mel`]: `MelSpectrogram(24000, n_fft 960, hop 240, 128 HTK mels,
13//!    norm None, power 1, center reflect)` followed by `ln(max(x, 1e-7))`,
14//!    as rows `[M, 128]` with `M = 1 + N/240`.
15//! 4. [`AudioTokenizer`]: the MiMo audio tokenizer encoder — conv stem, 24
16//!    pre-LN layers (causal, even layers windowed to 128 back), the skip
17//!    from layer index 2, pooler, then a 20-level residual VQ in f32. Every
18//!    6000-frame mel segment is encoded on its own (positions restart at 0).
19//! 5. [`AudioEncoder`]: the LLM side — the 20 speech embedding tables summed,
20//!    groups of 4 frames through a 6-layer bidirectional Qwen2, then the
21//!    two-layer GELU projection to the LLM width. One output row per
22//!    `<|audio_pad|>` placeholder.
23//!
24//! Weights are read by their source names: `audio_tokenizer.encoder.*`,
25//! `audio_encoder.*` and `speech_embeddings.*`, from a companion (or a
26//! single-file multimodal) CMF via [`MimoAudio::from_model`], or straight
27//! from the HF checkpoint directory via [`MimoAudio::from_hf_dir`] (the
28//! development loader; it reads only the tensors it needs).
29
30use crate::dit::Proj;
31use crate::pool::{Pool, SendMut};
32use cortiq_core::CmfModel;
33use serde_json::Value;
34use std::collections::HashMap;
35use std::path::Path;
36use std::sync::Arc;
37
38/// `<|audio_pad|>`: one per audio embedding row.
39pub const AUDIO_PAD_ID: u32 = 151669;
40/// `<|mimo_audio_start|>`.
41pub const AUDIO_START_ID: u32 = 151673;
42/// `<|mimo_audio_end|>`.
43pub const AUDIO_END_ID: u32 = 151674;
44
45/// The tokenizer's input rate.
46pub const SAMPLE_RATE: u32 = 24_000;
47/// STFT size and window length.
48pub const N_FFT: usize = 960;
49/// STFT hop.
50pub const HOP: usize = 240;
51/// Mel bands.
52pub const N_MELS: usize = 128;
53/// Mel frames per independently encoded tokenizer segment.
54pub const SEGMENT_FRAMES: usize = 6000;
55/// The mel floor before the natural log.
56pub const MEL_FLOOR: f64 = 1e-7;
57
58// ───────────────────────────── WAV decode ─────────────────────────────
59
60/// A decoded WAV file: one f32 vector per channel, all the same length.
61#[derive(Clone, Debug)]
62pub struct Wav {
63    pub sample_rate: u32,
64    pub channels: Vec<Vec<f32>>,
65}
66
67impl Wav {
68    pub fn frames(&self) -> usize {
69        self.channels.first().map_or(0, Vec::len)
70    }
71}
72
73#[derive(Clone, Copy, Debug, PartialEq, Eq)]
74enum SampleKind {
75    /// Integer PCM; 8-bit is unsigned, wider is signed two's complement.
76    Pcm,
77    /// IEEE float.
78    Float,
79}
80
81/// Decode a RIFF/WAVE byte buffer.
82///
83/// Scaling follows ffmpeg's sample-format conversion (what torchcodec feeds
84/// the reference processor): unsigned 8-bit is `(x − 128) / 128`, signed
85/// n-bit is `x / 2^(n−1)`; float samples pass through unchanged. For
86/// `WAVE_FORMAT_EXTENSIBLE` the container width (`block_align / channels`)
87/// sets the scale, which equals the valid-bit scale for left-justified
88/// samples. A `data` chunk whose declared size runs past the end of the
89/// buffer (streamed writers put 0xFFFFFFFF there) is read to the end.
90pub fn decode_wav(bytes: &[u8]) -> Result<Wav, String> {
91    if bytes.len() < 12 || &bytes[0..4] != b"RIFF" || &bytes[8..12] != b"WAVE" {
92        return Err("audio: not a RIFF/WAVE file (only WAV input is supported)".into());
93    }
94    let le16 = |b: &[u8], o: usize| u16::from_le_bytes([b[o], b[o + 1]]);
95    let le32 = |b: &[u8], o: usize| u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]);
96    let mut fmt: Option<(SampleKind, usize, u32, usize)> = None; // kind, channels, rate, container bytes
97    let mut data: Option<&[u8]> = None;
98    let mut pos = 12usize;
99    while pos + 8 <= bytes.len() {
100        let id = &bytes[pos..pos + 4];
101        let size = le32(bytes, pos + 4) as usize;
102        let start = pos + 8;
103        let end = start.saturating_add(size).min(bytes.len());
104        let body = &bytes[start..end];
105        match id {
106            b"fmt " => {
107                if body.len() < 16 {
108                    return Err("audio: WAV fmt chunk is shorter than 16 bytes".into());
109                }
110                let mut tag = le16(body, 0);
111                let channels = le16(body, 2) as usize;
112                let rate = le32(body, 4);
113                let block_align = le16(body, 12) as usize;
114                let bits = le16(body, 14) as usize;
115                if tag == 0xFFFE {
116                    if body.len() < 40 {
117                        return Err("audio: WAVE_FORMAT_EXTENSIBLE fmt chunk is truncated".into());
118                    }
119                    // The sub-format GUID starts with the plain format tag.
120                    tag = le16(body, 24);
121                }
122                let kind = match tag {
123                    1 => SampleKind::Pcm,
124                    3 => SampleKind::Float,
125                    other => {
126                        return Err(format!(
127                            "audio: WAV format tag {other:#06x} is not supported (PCM and IEEE float only)"
128                        ));
129                    }
130                };
131                if channels == 0 {
132                    return Err("audio: WAV declares zero channels".into());
133                }
134                if rate == 0 {
135                    return Err("audio: WAV declares a zero sample rate".into());
136                }
137                let container = if block_align > 0 && block_align % channels == 0 {
138                    block_align / channels
139                } else {
140                    bits.div_ceil(8)
141                };
142                let ok = match kind {
143                    SampleKind::Pcm => matches!(container, 1..=4),
144                    SampleKind::Float => matches!(container, 4 | 8),
145                };
146                if !ok {
147                    return Err(format!(
148                        "audio: WAV {kind:?} with {container}-byte samples ({bits} bits) is not supported"
149                    ));
150                }
151                fmt = Some((kind, channels, rate, container));
152            }
153            b"data" => {
154                data = Some(body);
155                if fmt.is_some() {
156                    break;
157                }
158            }
159            _ => {}
160        }
161        // Chunks are word aligned: an odd size carries one pad byte.
162        pos = start.saturating_add(size).saturating_add(size & 1);
163    }
164    let (kind, nch, rate, cb) = fmt.ok_or("audio: WAV has no fmt chunk")?;
165    let data = data.ok_or("audio: WAV has no data chunk")?;
166    let frame = nch * cb;
167    let frames = data.len() / frame;
168    let mut channels = vec![Vec::with_capacity(frames); nch];
169    for f in 0..frames {
170        for (c, ch) in channels.iter_mut().enumerate() {
171            let o = f * frame + c * cb;
172            let s = &data[o..o + cb];
173            let v = match (kind, cb) {
174                (SampleKind::Pcm, 1) => (s[0] as f32 - 128.0) / 128.0,
175                (SampleKind::Pcm, 2) => i16::from_le_bytes([s[0], s[1]]) as f32 / 32768.0,
176                (SampleKind::Pcm, 3) => {
177                    let v = i32::from_le_bytes([0, s[0], s[1], s[2]]) >> 8;
178                    v as f32 / 8_388_608.0
179                }
180                (SampleKind::Pcm, 4) => {
181                    (i32::from_le_bytes([s[0], s[1], s[2], s[3]]) as f64 / 2_147_483_648.0) as f32
182                }
183                (SampleKind::Float, 4) => f32::from_le_bytes([s[0], s[1], s[2], s[3]]),
184                (SampleKind::Float, 8) => {
185                    f64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]) as f32
186                }
187                _ => unreachable!("container width validated above"),
188            };
189            ch.push(v);
190        }
191    }
192    Ok(Wav {
193        sample_rate: rate,
194        channels,
195    })
196}
197
198// ───────────────────────────── resampling ─────────────────────────────
199
200fn gcd(mut a: u64, mut b: u64) -> u64 {
201    while b != 0 {
202        (a, b) = (b, a % b);
203    }
204    a
205}
206
207/// torchaudio `_get_sinc_resample_kernel` with its defaults
208/// (`lowpass_filter_width = 6`, `rolloff = 0.99`, `sinc_interp_hann`,
209/// `dtype = None`): the kernel is built in f64 and stored as f32. `orig` and
210/// `new` are already gcd-reduced. Returns `(kernel [new][2·width + orig],
211/// width)`.
212///
213/// One detail is kept on purpose: the output phase `-j / new` is a float32
214/// division in torch (`torch.arange(0, -new, -1)` is int64 and true
215/// division promotes it to the default dtype) before it meets the f64 tap
216/// grid.
217pub fn sinc_resample_kernel(orig: usize, new: usize) -> (Vec<f32>, usize) {
218    const WIDTH: f64 = 6.0;
219    const ROLLOFF: f64 = 0.99;
220    let base_freq = (orig.min(new) as f64) * ROLLOFF;
221    let width = (WIDTH * orig as f64 / base_freq).ceil() as usize;
222    let taps = 2 * width + orig;
223    let scale = base_freq / orig as f64;
224    let mut kernel = vec![0f32; new * taps];
225    for j in 0..new {
226        let phase = (-(j as f32) / new as f32) as f64;
227        for i in 0..taps {
228            let idx = (i as f64 - width as f64) / orig as f64;
229            let mut t = (phase + idx) * base_freq;
230            t = t.clamp(-WIDTH, WIDTH);
231            let window = {
232                let c = (t * std::f64::consts::PI / WIDTH / 2.0).cos();
233                c * c
234            };
235            let t = t * std::f64::consts::PI;
236            let sinc = if t == 0.0 { 1.0 } else { t.sin() / t };
237            kernel[j * taps + i] = (sinc * (window * scale)) as f32;
238        }
239    }
240    (kernel, width)
241}
242
243/// torchaudio's `Resample(orig, new)` output length: the f64 quotient is
244/// rounded to float32 (`torch.as_tensor` of a Python float) before the
245/// ceiling, so a long input can come out one sample shorter than the exact
246/// `⌈new·N/orig⌉`. This returns what torchaudio returns.
247pub fn resampled_len(n: usize, orig: u32, new: u32) -> usize {
248    if orig == new {
249        return n;
250    }
251    let g = gcd(orig as u64, new as u64);
252    let (o, nw) = ((orig as u64 / g) as f64, (new as u64 / g) as f64);
253    ((nw * n as f64 / o) as f32).ceil() as usize
254}
255
256/// Resample one channel from `orig` Hz to `new` Hz exactly as torchaudio's
257/// `Resample` does: zero padding of `width` on the left and `width + orig`
258/// on the right, a strided polyphase convolution, truncated to
259/// [`resampled_len`]. Accumulation is f64.
260pub fn resample_sinc(x: &[f32], orig: u32, new: u32) -> Vec<f32> {
261    if orig == new {
262        return x.to_vec();
263    }
264    let g = gcd(orig as u64, new as u64);
265    let o = (orig as u64 / g) as usize;
266    let nw = (new as u64 / g) as usize;
267    let (kernel, width) = sinc_resample_kernel(o, nw);
268    let taps = 2 * width + o;
269    let len = x.len();
270    let frames = len / o + 1;
271    let target = resampled_len(len, orig, new).min(frames * nw);
272    let mut out = vec![0f32; target];
273    // x_pad[p] = x[p − width] inside the signal, 0 outside.
274    for (n_out, dst) in out.iter_mut().enumerate() {
275        let (f, j) = (n_out / nw, n_out % nw);
276        let krow = &kernel[j * taps..(j + 1) * taps];
277        let base = (f * o) as isize - width as isize;
278        let mut acc = 0f64;
279        for (m, &kv) in krow.iter().enumerate() {
280            let p = base + m as isize;
281            if p >= 0 && (p as usize) < len {
282                acc += kv as f64 * x[p as usize] as f64;
283            }
284        }
285        *dst = acc as f32;
286    }
287    out
288}
289
290/// Resample every channel to 24 kHz, then average the channels (the
291/// reference resamples `[C, N]` first and takes `mean(dim=0)` after).
292pub fn wav_to_mono_24k(wav: &Wav) -> Result<Vec<f32>, String> {
293    if wav.channels.is_empty() {
294        return Err("audio: WAV has no channels".into());
295    }
296    let chans: Vec<Vec<f32>> = wav
297        .channels
298        .iter()
299        .map(|c| resample_sinc(c, wav.sample_rate, SAMPLE_RATE))
300        .collect();
301    if chans.len() == 1 {
302        return Ok(chans.into_iter().next().unwrap());
303    }
304    let n = chans[0].len();
305    let inv = chans.len() as f32;
306    Ok((0..n)
307        .map(|i| chans.iter().map(|c| c[i]).sum::<f32>() / inv)
308        .collect())
309}
310
311// ───────────────────────────── log-mel ─────────────────────────────
312
313/// A mixed-radix complex FFT in f64 for a fixed size (factors 2, 3, 5, …).
314struct Fft {
315    n: usize,
316    factors: Vec<usize>,
317    /// `e^{−2πi·k/n}` for k in 0..n.
318    tw: Vec<(f64, f64)>,
319}
320
321impl Fft {
322    fn new(n: usize) -> Self {
323        let mut factors = Vec::new();
324        let mut m = n;
325        for p in [4usize, 2, 3, 5] {
326            while m % p == 0 {
327                factors.push(p);
328                m /= p;
329            }
330        }
331        let mut p = 7;
332        while m > 1 {
333            while m % p == 0 {
334                factors.push(p);
335                m /= p;
336            }
337            p += 2;
338        }
339        let tw = (0..n)
340            .map(|k| {
341                let a = -2.0 * std::f64::consts::PI * k as f64 / n as f64;
342                (a.cos(), a.sin())
343            })
344            .collect();
345        Self { n, factors, tw }
346    }
347
348    /// `out[k] = Σ_t x[t]·e^{−2πi·kt/n}` for a real input of length n.
349    fn forward_real(&self, x: &[f64], out: &mut [(f64, f64)]) {
350        debug_assert_eq!(x.len(), self.n);
351        debug_assert_eq!(out.len(), self.n);
352        let cx: Vec<(f64, f64)> = x.iter().map(|&v| (v, 0.0)).collect();
353        self.rec(&cx, 0, 1, out, self.n, 0);
354    }
355
356    /// Decimation in time: `out` (length `n`) receives the DFT of
357    /// `input[off + k·stride]`, k in 0..n.
358    fn rec(
359        &self,
360        input: &[(f64, f64)],
361        off: usize,
362        stride: usize,
363        out: &mut [(f64, f64)],
364        n: usize,
365        level: usize,
366    ) {
367        if n == 1 {
368            out[0] = input[off];
369            return;
370        }
371        let p = self.factors[level];
372        let m = n / p;
373        for r in 0..p {
374            self.rec(
375                input,
376                off + r * stride,
377                stride * p,
378                &mut out[r * m..(r + 1) * m],
379                m,
380                level + 1,
381            );
382        }
383        // X[k + m·q] = Σ_r W_n^{r(k+mq)} S_r[k]; the p inputs S_r[k] sit at
384        // out[r·m + k] and the p outputs at out[k + m·q] — the same slots,
385        // so each k is combined in place through a small buffer.
386        let big = self.n;
387        let step_n = big / n;
388        let step_p = big / p;
389        let mut t = [(0f64, 0f64); 16];
390        let mut tmp = vec![(0f64, 0f64); if p > 16 { p } else { 0 }];
391        let buf: &mut [(f64, f64)] = if p > 16 { &mut tmp } else { &mut t[..p] };
392        for k in 0..m {
393            for r in 0..p {
394                let s = out[r * m + k];
395                let w = self.tw[(r * k * step_n) % big];
396                buf[r] = (s.0 * w.0 - s.1 * w.1, s.0 * w.1 + s.1 * w.0);
397            }
398            for q in 0..p {
399                let (mut re, mut im) = (0f64, 0f64);
400                for (r, b) in buf.iter().enumerate() {
401                    let w = self.tw[(r * q * step_p) % big];
402                    re += b.0 * w.0 - b.1 * w.1;
403                    im += b.0 * w.1 + b.1 * w.0;
404                }
405                out[k + m * q] = (re, im);
406            }
407        }
408    }
409}
410
411/// torchaudio `melscale_fbanks(n_freqs, 0, sr/2, n_mels, sr, norm=None,
412/// mel_scale="htk")`, evaluated in f64. Row-major `[n_freqs][n_mels]`.
413pub fn mel_filterbank(n_freqs: usize, n_mels: usize, sample_rate: u32) -> Vec<f64> {
414    let f_max = (sample_rate / 2) as f64;
415    let hz_to_mel = |f: f64| 2595.0 * (1.0 + f / 700.0).log10();
416    let mel_to_hz = |m: f64| 700.0 * (10f64.powf(m / 2595.0) - 1.0);
417    let (m_min, m_max) = (hz_to_mel(0.0), hz_to_mel(f_max));
418    let f_pts: Vec<f64> = (0..n_mels + 2)
419        .map(|i| mel_to_hz(m_min + (m_max - m_min) * i as f64 / (n_mels + 1) as f64))
420        .collect();
421    let f_diff: Vec<f64> = f_pts.windows(2).map(|w| w[1] - w[0]).collect();
422    let mut fb = vec![0f64; n_freqs * n_mels];
423    for k in 0..n_freqs {
424        let freq = f_max * k as f64 / (n_freqs - 1) as f64;
425        for m in 0..n_mels {
426            let down = -(f_pts[m] - freq) / f_diff[m];
427            let up = (f_pts[m + 2] - freq) / f_diff[m + 1];
428            fb[k * n_mels + m] = down.min(up).max(0.0);
429        }
430    }
431    fb
432}
433
434/// Entries of `torch.hann_window(960)` (torch 2.14, x86 AVX-512 build) whose
435/// float32 bits differ from the correctly rounded evaluation of ATen's
436/// formula — its vectorized `cos` is 1 ulp off there. Quiet mel bands are
437/// ill conditioned: these 27 one-ulp differences alone move a near-empty
438/// top band of a resampled clip by 0.12 in log, so the stored window is
439/// reproduced bit for bit.
440const TORCH_HANN_960_FIXUPS: [(usize, u32); 27] = [
441    (30, 0x3C1D6820),
442    (42, 0x3C99C880),
443    (70, 0x3D533468),
444    (87, 0x3DA191D0),
445    (99, 0x3DCF8B30),
446    (109, 0x3DF9B6B0),
447    (125, 0x3E220032),
448    (166, 0x3E88CD7D),
449    (172, 0x3E91CA07),
450    (315, 0x3F3C56BC),
451    (331, 0x3F47CEDC),
452    (338, 0x3F4C95E8),
453    (384, 0x3F678DDE),
454    (466, 0x3F7F7689),
455    (468, 0x3F7F9AFC),
456    (522, 0x3F7B31BC),
457    (558, 0x3F6FADF2),
458    (582, 0x3F648543),
459    (639, 0x3F40B962),
460    (661, 0x3F30355E),
461    (695, 0x3F14D9C0),
462    (857, 0x3DE00070),
463    (859, 0x3DD7B41C),
464    (866, 0x3DBBC250),
465    (871, 0x3DA8DEB4),
466    (924, 0x3C625860),
467    (940, 0x3B8C2B00),
468];
469
470/// `torch.hann_window(960)` (periodic, float32) as ATen builds it —
471/// `arange · f32(2π/N)`, cos, `· −0.5 + 0.5`, all in float32 — plus the
472/// measured 1-ulp fixups of torch's vectorized cos.
473fn torch_hann_960() -> Vec<f64> {
474    let step = (2.0 * std::f64::consts::PI / N_FFT as f64) as f32;
475    let mut w: Vec<f32> = (0..N_FFT)
476        .map(|i| {
477            let c = ((i as f32 * step) as f64).cos() as f32;
478            c * -0.5f32 + 0.5f32
479        })
480        .collect();
481    for (i, bits) in TORCH_HANN_960_FIXUPS {
482        w[i] = f32::from_bits(bits);
483    }
484    w.into_iter().map(f64::from).collect()
485}
486
487/// Mel frames for a 24 kHz waveform of `n` samples (`center=True`).
488pub fn mel_frames(n: usize) -> usize {
489    1 + n / HOP
490}
491
492/// Log-mel features of a 24 kHz mono waveform, row-major `[M, 128]`.
493///
494/// The reflect pad of 480 needs more than 480 samples, as in torch.
495pub fn log_mel(wave: &[f32], pool: Option<&Pool>) -> Result<(Vec<f32>, usize), String> {
496    let n = wave.len();
497    let pad = N_FFT / 2;
498    if n <= pad {
499        return Err(format!(
500            "audio: {n} samples at 24 kHz is too short (the STFT reflect pad needs more than {pad})"
501        ));
502    }
503    let frames = mel_frames(n);
504    let n_freqs = N_FFT / 2 + 1;
505    let fb = mel_filterbank(n_freqs, N_MELS, SAMPLE_RATE);
506    // Each mel band touches a short run of bins; keep only those.
507    let bands: Vec<(usize, Vec<f64>)> = (0..N_MELS)
508        .map(|m| {
509            let nz: Vec<usize> = (0..n_freqs)
510                .filter(|&k| fb[k * N_MELS + m] != 0.0)
511                .collect();
512            match (nz.first(), nz.last()) {
513                (Some(&a), Some(&b)) => (a, (a..=b).map(|k| fb[k * N_MELS + m]).collect()),
514                _ => (0, Vec::new()),
515            }
516        })
517        .collect();
518    let window = torch_hann_960();
519    let reflect = |p: isize| -> f32 {
520        let last = n as isize - 1;
521        let mut i = p - pad as isize;
522        if i < 0 {
523            i = -i;
524        }
525        if i > last {
526            i = 2 * last - i;
527        }
528        wave[i as usize]
529    };
530    let fft = Fft::new(N_FFT);
531    let mut out = vec![0f32; frames * N_MELS];
532    let dst = SendMut::new(out.as_mut_ptr());
533    let run = |start: usize, end: usize| {
534        let mut buf = vec![0f64; N_FFT];
535        let mut spec = vec![(0f64, 0f64); N_FFT];
536        let mut mag = vec![0f64; n_freqs];
537        for t in start..end {
538            for (i, b) in buf.iter_mut().enumerate() {
539                *b = reflect((t * HOP + i) as isize) as f64 * window[i];
540            }
541            fft.forward_real(&buf, &mut spec);
542            for k in 0..n_freqs {
543                mag[k] = (spec[k].0 * spec[k].0 + spec[k].1 * spec[k].1).sqrt();
544            }
545            for (m, (k0, w)) in bands.iter().enumerate() {
546                let mut acc = 0f64;
547                for (i, wv) in w.iter().enumerate() {
548                    acc += wv * mag[k0 + i];
549                }
550                unsafe { *dst.at(t * N_MELS + m) = acc.max(MEL_FLOOR).ln() as f32 };
551            }
552        }
553    };
554    match pool {
555        Some(p) if frames >= 64 => p.run_rows(frames, &run),
556        _ => run(0, frames),
557    }
558    Ok((out, frames))
559}
560
561// ───────────────────────────── token counts ─────────────────────────────
562
563/// Mel-frame lengths of the independently encoded segments.
564pub fn segment_lengths(mel_frames: usize) -> Vec<usize> {
565    let mut v = vec![SEGMENT_FRAMES; mel_frames / SEGMENT_FRAMES];
566    if mel_frames % SEGMENT_FRAMES > 0 {
567        v.push(mel_frames % SEGMENT_FRAMES);
568    }
569    v
570}
571
572/// Tokenizer codes (25 Hz frames) for one segment of `m` mel frames:
573/// `⌈⌈m/2⌉/2⌉` (conv2 stride 2, then the pooler of 2).
574pub fn segment_codes(m: usize) -> usize {
575    m.div_ceil(2).div_ceil(2)
576}
577
578/// Codes for a whole clip: the segments' codes concatenated.
579pub fn codes_for_mel(mel_frames: usize) -> usize {
580    segment_lengths(mel_frames)
581        .into_iter()
582        .map(segment_codes)
583        .sum()
584}
585
586/// `<|audio_pad|>` count for a clip of `mel_frames` (the processor's
587/// `compute_audio_token_len`: `⌈⌈⌈M/2⌉/2⌉/4⌉`). Equal to the number of
588/// 4-frame groups the encoder produces, since 6000 is divisible by 4.
589pub fn audio_token_count(mel_frames: usize, group: usize) -> usize {
590    mel_frames.div_ceil(2).div_ceil(2).div_ceil(group)
591}
592
593/// The placeholder id run for one clip of `k` embedding rows.
594pub fn audio_placeholder_ids(k: usize) -> Vec<u32> {
595    let mut v = Vec::with_capacity(k + 2);
596    v.push(AUDIO_START_ID);
597    v.extend(std::iter::repeat_n(AUDIO_PAD_ID, k));
598    v.push(AUDIO_END_ID);
599    v
600}
601
602/// Expand each `<|mimo_audio_start|><|audio_pad|><|mimo_audio_end|>`
603/// triple, in order, to `counts[i]` pads. A count/triple mismatch, or a pad
604/// outside a triple, is an error.
605pub fn expand_audio_placeholders(ids: &[u32], counts: &[usize]) -> Result<Vec<u32>, String> {
606    let mut out = Vec::with_capacity(ids.len() + counts.iter().sum::<usize>());
607    let mut used = 0usize;
608    let mut i = 0usize;
609    while i < ids.len() {
610        if ids[i] == AUDIO_START_ID
611            && ids.get(i + 1) == Some(&AUDIO_PAD_ID)
612            && ids.get(i + 2) == Some(&AUDIO_END_ID)
613        {
614            let k = *counts.get(used).ok_or_else(|| {
615                format!(
616                    "audio: the prompt has more audio placeholders than the {} audio input(s)",
617                    counts.len()
618                )
619            })?;
620            out.extend(audio_placeholder_ids(k));
621            used += 1;
622            i += 3;
623            continue;
624        }
625        if ids[i] == AUDIO_PAD_ID {
626            return Err(format!(
627                "audio: <|audio_pad|> at position {i} is not inside a <|mimo_audio_start|>…<|mimo_audio_end|> triple"
628            ));
629        }
630        out.push(ids[i]);
631        i += 1;
632    }
633    if used != counts.len() {
634        return Err(format!(
635            "audio: {} audio input(s) but {used} placeholder(s) in the prompt",
636            counts.len()
637        ));
638    }
639    Ok(out)
640}
641
642// ───────────────────────────── numerics ─────────────────────────────
643
644/// erfc by the Numerical Recipes rational form (1.2e-7 relative
645/// everywhere, including the far tail where `1 + erf` would cancel).
646fn erfc(x: f64) -> f64 {
647    let z = x.abs();
648    let t = 1.0 / (1.0 + 0.5 * z);
649    let ans = t
650        * (-z * z - 1.265_512_23
651            + t * (1.000_023_68
652                + t * (0.374_091_96
653                    + t * (0.096_784_18
654                        + t * (-0.186_288_06
655                            + t * (0.278_868_07
656                                + t * (-1.135_203_98
657                                    + t * (1.488_515_87
658                                        + t * (-0.822_152_23 + t * 0.170_872_77)))))))))
659            .exp();
660    if x >= 0.0 { ans } else { 2.0 - ans }
661}
662
663/// `nn.GELU()` (erf form) written as `x/2 · erfc(−x/√2)`, which keeps
664/// relative accuracy for negative inputs.
665#[inline]
666fn gelu(v: f32) -> f32 {
667    (0.5 * v as f64 * erfc(-(v as f64) * std::f64::consts::FRAC_1_SQRT_2)) as f32
668}
669
670fn gelu_inplace(x: &mut [f32]) {
671    for v in x {
672        *v = gelu(*v);
673    }
674}
675
676fn layer_norm_rows(x: &[f32], w: &[f32], b: &[f32], eps: f64, dst: &mut [f32]) {
677    let d = w.len();
678    for (xr, dr) in x.chunks_exact(d).zip(dst.chunks_exact_mut(d)) {
679        let mean = xr.iter().map(|&v| v as f64).sum::<f64>() / d as f64;
680        let var = xr.iter().map(|&v| (v as f64 - mean).powi(2)).sum::<f64>() / d as f64;
681        let inv = 1.0 / (var + eps).sqrt();
682        for i in 0..d {
683            dr[i] = ((xr[i] as f64 - mean) * inv * w[i] as f64 + b[i] as f64) as f32;
684        }
685    }
686}
687
688/// Qwen2 RMSNorm: `w · (x · rsqrt(mean(x²) + eps))`.
689fn rms_norm_rows(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
690    let d = w.len();
691    for (xr, dr) in x.chunks_exact(d).zip(dst.chunks_exact_mut(d)) {
692        let ms = xr.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / d as f64;
693        let inv = 1.0 / (ms + eps).sqrt();
694        for i in 0..d {
695            dr[i] = w[i] * ((xr[i] as f64 * inv) as f32);
696        }
697    }
698}
699
700fn add_bias(x: &mut [f32], b: &[f32]) {
701    for row in x.chunks_exact_mut(b.len()) {
702        for (v, bb) in row.iter_mut().zip(b) {
703            *v += bb;
704        }
705    }
706}
707
708fn add_into(x: &mut [f32], y: &[f32]) {
709    for (a, b) in x.iter_mut().zip(y) {
710        *a += b;
711    }
712}
713
714/// RoPE tables as the references build them: `inv_freq` in float32
715/// (`1 / base^(arange(0, d, 2)/d)`), `freqs = pos · inv_freq` as one f32
716/// product, cos/sin of that f32 angle. Returns `(cos, sin)`, each
717/// `[positions][head_dim/2]` (the full table is `[f | f]`).
718fn rope_tables(base: f64, head_dim: usize, positions: usize) -> (Vec<f32>, Vec<f32>) {
719    let half = head_dim / 2;
720    let inv: Vec<f32> = (0..half)
721        .map(|i| {
722            let e = (2 * i) as f32 / head_dim as f32;
723            1.0f32 / ((base as f32 as f64).powf(e as f64) as f32)
724        })
725        .collect();
726    let mut cos = vec![0f32; positions * half];
727    let mut sin = vec![0f32; positions * half];
728    for p in 0..positions {
729        for i in 0..half {
730            let a = (p as f32 * inv[i]) as f64;
731            cos[p * half + i] = a.cos() as f32;
732            sin[p * half + i] = a.sin() as f32;
733        }
734    }
735    (cos, sin)
736}
737
738/// In-place rotate_half RoPE on `x [rows][heads·hd]`, row r at position
739/// `pos(r)`.
740fn apply_rope(
741    x: &mut [f32],
742    heads: usize,
743    hd: usize,
744    cos: &[f32],
745    sin: &[f32],
746    pos: impl Fn(usize) -> usize,
747) {
748    let half = hd / 2;
749    let width = heads * hd;
750    for (r, row) in x.chunks_exact_mut(width).enumerate() {
751        let p = pos(r);
752        let (c, s) = (
753            &cos[p * half..(p + 1) * half],
754            &sin[p * half..(p + 1) * half],
755        );
756        for h in 0..heads {
757            let v = &mut row[h * hd..(h + 1) * hd];
758            for i in 0..half {
759                let (a, b) = (v[i], v[i + half]);
760                v[i] = a * c[i] - b * s[i];
761                v[i + half] = b * c[i] + a * s[i];
762            }
763        }
764    }
765}
766
767/// f32 dot product in eight independent lanes (vectorizes; the lane sums
768/// are added pairwise at the end).
769#[inline]
770fn dot8(a: &[f32], b: &[f32]) -> f32 {
771    let mut acc = [0f32; 8];
772    let (ca, cb) = (a.chunks_exact(8), b.chunks_exact(8));
773    let (ra, rb) = (ca.remainder(), cb.remainder());
774    for (x, y) in ca.zip(cb) {
775        for l in 0..8 {
776            acc[l] += x[l] * y[l];
777        }
778    }
779    let mut s = ((acc[0] + acc[4]) + (acc[1] + acc[5])) + ((acc[2] + acc[6]) + (acc[3] + acc[7]));
780    for (x, y) in ra.iter().zip(rb) {
781        s += x * y;
782    }
783    s
784}
785
786/// f64 dot product of f32 vectors in four lanes.
787#[inline]
788fn dot_f64(a: &[f32], b: &[f32]) -> f64 {
789    let mut acc = [0f64; 4];
790    let (ca, cb) = (a.chunks_exact(4), b.chunks_exact(4));
791    let (ra, rb) = (ca.remainder(), cb.remainder());
792    for (x, y) in ca.zip(cb) {
793        for l in 0..4 {
794            acc[l] += x[l] as f64 * y[l] as f64;
795        }
796    }
797    let mut s = (acc[0] + acc[2]) + (acc[1] + acc[3]);
798    for (x, y) in ra.iter().zip(rb) {
799        s += *x as f64 * *y as f64;
800    }
801    s
802}
803
804/// Scaled-dot-product attention over `l` rows of `[heads·hd]` q/k/v, with
805/// row `i` attending keys `range(i) = [lo, hi)`. Output `[l][heads·hd]`.
806#[allow(clippy::too_many_arguments)]
807fn attention(
808    q: &[f32],
809    k: &[f32],
810    v: &[f32],
811    l: usize,
812    heads: usize,
813    hd: usize,
814    range: &(dyn Fn(usize) -> (usize, usize) + Sync),
815    out: &mut [f32],
816    pool: Option<&Pool>,
817) {
818    let width = heads * hd;
819    let scale = 1.0 / (hd as f32).sqrt();
820    let dst = SendMut::new(out.as_mut_ptr());
821    let run = |start: usize, end: usize| {
822        let mut sc: Vec<f32> = Vec::new();
823        let mut acc = vec![0f32; hd];
824        for unit in start..end {
825            let (i, h) = (unit / heads, unit % heads);
826            let (lo, hi) = range(i);
827            let qi = &q[i * width + h * hd..i * width + (h + 1) * hd];
828            sc.clear();
829            let mut mx = f32::NEG_INFINITY;
830            for j in lo..hi {
831                let kj = &k[j * width + h * hd..j * width + (h + 1) * hd];
832                let s = dot8(qi, kj) * scale;
833                mx = mx.max(s);
834                sc.push(s);
835            }
836            let mut sum = 0f64;
837            for s in sc.iter_mut() {
838                *s = (*s - mx).exp();
839                sum += *s as f64;
840            }
841            acc.iter_mut().for_each(|a| *a = 0.0);
842            for (jj, &p) in sc.iter().enumerate() {
843                let j = lo + jj;
844                let vj = &v[j * width + h * hd..j * width + (h + 1) * hd];
845                for t in 0..hd {
846                    acc[t] += p * vj[t];
847                }
848            }
849            let inv = (1.0 / sum) as f32;
850            for t in 0..hd {
851                unsafe { *dst.at(i * width + h * hd + t) = acc[t] * inv };
852            }
853        }
854    };
855    let units = l * heads;
856    match pool {
857        Some(p) if units >= 64 => p.run_rows(units, &run),
858        _ => run(0, units),
859    }
860}
861
862/// A projection with its source name (the GPTQ Hessian hook keys on it).
863struct Mat {
864    p: Proj,
865    name: String,
866}
867
868/// `y [b][rows] = x [b][cols] · Wᵀ (+ bias)`.
869///
870/// Under `gptq_capture`, exact (f32) weights report their inputs here;
871/// mapped quantized ones report from `QTensor::matmat` itself.
872fn linear(m: &Mat, bias: Option<&[f32]>, x: &[f32], b: usize, pool: Option<&Pool>) -> Vec<f32> {
873    let p = &m.p;
874    if b > 0 && crate::gptq_capture::capturing() && matches!(p, Proj::F32 { .. }) {
875        crate::gptq_capture::accumulate(&m.name, x, b, p.cols());
876    }
877    let mut y = vec![0f32; b * p.rows()];
878    if b > 0 {
879        p.matmat(x, b, &mut y, pool);
880    }
881    if let Some(bias) = bias {
882        add_bias(&mut y, bias);
883    }
884    y
885}
886
887// ───────────────────────────── weight sources ─────────────────────────────
888
889/// Where tower tensors come from. Names are the canonical source names
890/// (`audio_tokenizer.encoder.*`, `audio_encoder.*`, `speech_embeddings.*`).
891trait WeightSource {
892    fn has(&self, name: &str) -> bool;
893    /// Any tensor as f32 plus its shape.
894    fn dense(&mut self, name: &str) -> Result<(Vec<f32>, Vec<usize>), String>;
895    /// A matrix `[out, in]` for the batched projection path. `cols` reshapes
896    /// a rank-3 conv kernel `[out, in, k]` to `[out, in·k]`.
897    fn proj(&mut self, name: &str) -> Result<Proj, String>;
898    fn config_blob(&self, name: &str) -> Option<Vec<u8>>;
899}
900
901struct CmfSource(Arc<CmfModel>);
902
903impl WeightSource for CmfSource {
904    fn has(&self, name: &str) -> bool {
905        self.0.tensor_index(name).is_some()
906    }
907    fn dense(&mut self, name: &str) -> Result<(Vec<f32>, Vec<usize>), String> {
908        let idx = self
909            .0
910            .tensor_index(name)
911            .ok_or_else(|| format!("mimo audio: missing tensor '{name}'"))?;
912        let entry = &self.0.tensors[idx];
913        let mut dst = vec![0f32; entry.n_elems()];
914        cortiq_core::quant::dequant_tensor(entry, self.0.entry_bytes(entry), &mut dst)
915            .map_err(|e| format!("mimo audio: tensor '{name}': {e}"))?;
916        Ok((dst, entry.shape.clone()))
917    }
918    /// Quantized tower matrices are dequantized to f32 at load (about 2 GB
919    /// for the audio towers) and run through the f32 GEMM. Left mapped,
920    /// `QTensor::matmat`'s host arm quantizes the activations to int8,
921    /// which the tokenizer's codes do not survive: with q8_2f weights the
922    /// level-0 codes agreed with the exact tower on 95.2–96.3 % of frames
923    /// that way against 96.5–98.6 % with f32 activations, and the encoder
924    /// rows' mean cosine was 0.978–0.985 against 0.9994 (3 clips).
925    /// `CMF_MIMO_AUDIO_MAPPED=1` keeps them mapped (less RAM, int8 host
926    /// activations).
927    fn proj(&mut self, name: &str) -> Result<Proj, String> {
928        let entry = self
929            .0
930            .tensor(name)
931            .ok_or_else(|| format!("mimo audio: missing tensor '{name}'"))?;
932        if entry.shape.len() == 2 {
933            let p = Proj::from_model(&self.0, name)?;
934            let keep_mapped = std::env::var("CMF_MIMO_AUDIO_MAPPED").is_ok_and(|v| v == "1");
935            if matches!(p, Proj::F32 { .. }) || keep_mapped {
936                return Ok(p);
937            }
938        }
939        let (w, shape) = self.dense(name)?;
940        let cols = shape[1..].iter().product::<usize>();
941        Ok(Proj::f32(w, cols))
942    }
943    fn config_blob(&self, name: &str) -> Option<Vec<u8>> {
944        let e = self.0.tensor(name)?;
945        Some(self.0.entry_bytes(e).to_vec())
946    }
947}
948
949/// The development loader: tensors read straight from the HF checkpoint
950/// (BF16/F16/F32 → f32, exact), only those the audio towers need.
951struct HfSource {
952    tensors: HashMap<String, (Vec<f32>, Vec<usize>)>,
953    blobs: HashMap<String, Vec<u8>>,
954}
955
956/// Read the named tensors of one safetensors file without touching the
957/// rest of it. `rename(src_name) -> Some(canonical)` selects and renames.
958fn read_safetensors_selected(
959    path: &Path,
960    rename: &dyn Fn(&str) -> Option<String>,
961    out: &mut Vec<RawTensor>,
962) -> Result<(), String> {
963    use std::io::{Read, Seek, SeekFrom};
964    let mut f = std::fs::File::open(path).map_err(|e| format!("{}: {e}", path.display()))?;
965    let mut len8 = [0u8; 8];
966    f.read_exact(&mut len8)
967        .map_err(|e| format!("{}: {e}", path.display()))?;
968    let hlen = u64::from_le_bytes(len8) as usize;
969    let mut hbytes = vec![0u8; hlen];
970    f.read_exact(&mut hbytes)
971        .map_err(|e| format!("{}: {e}", path.display()))?;
972    let header: Value = serde_json::from_slice(&hbytes)
973        .map_err(|e| format!("{}: safetensors header: {e}", path.display()))?;
974    let obj = header
975        .as_object()
976        .ok_or_else(|| format!("{}: safetensors header is not an object", path.display()))?;
977    let base = 8 + hlen as u64;
978    let mut picks: Vec<(u64, u64, String, String, Vec<usize>)> = Vec::new();
979    for (name, meta) in obj {
980        if name == "__metadata__" {
981            continue;
982        }
983        let Some(canon) = rename(name) else { continue };
984        let dtype = meta["dtype"].as_str().unwrap_or("").to_string();
985        let shape: Vec<usize> = meta["shape"]
986            .as_array()
987            .map(|a| a.iter().map(|v| v.as_u64().unwrap_or(0) as usize).collect())
988            .unwrap_or_default();
989        let offs = meta["data_offsets"]
990            .as_array()
991            .ok_or_else(|| format!("{name}: no data_offsets"))?;
992        let (s, e) = (offs[0].as_u64().unwrap_or(0), offs[1].as_u64().unwrap_or(0));
993        picks.push((s, e, canon, dtype, shape));
994    }
995    picks.sort_by_key(|p| p.0);
996    for (s, e, name, dtype, shape) in picks {
997        let mut bytes = vec![0u8; (e - s) as usize];
998        f.seek(SeekFrom::Start(base + s))
999            .and_then(|_| f.read_exact(&mut bytes))
1000            .map_err(|err| format!("{}: {name}: {err}", path.display()))?;
1001        out.push(RawTensor {
1002            name,
1003            dtype,
1004            shape,
1005            bytes,
1006        });
1007    }
1008    Ok(())
1009}
1010
1011/// One tensor as stored in a safetensors file, under its canonical name.
1012#[derive(Clone, Debug)]
1013pub struct RawTensor {
1014    pub name: String,
1015    /// safetensors dtype string: "BF16", "F16" or "F32".
1016    pub dtype: String,
1017    pub shape: Vec<usize>,
1018    pub bytes: Vec<u8>,
1019}
1020
1021impl RawTensor {
1022    pub fn to_f32(&self) -> Result<Vec<f32>, String> {
1023        let data: Vec<f32> = match self.dtype.as_str() {
1024            "F32" => self
1025                .bytes
1026                .chunks_exact(4)
1027                .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
1028                .collect(),
1029            "BF16" => self
1030                .bytes
1031                .chunks_exact(2)
1032                .map(|c| f32::from_bits((u16::from_le_bytes([c[0], c[1]]) as u32) << 16))
1033                .collect(),
1034            "F16" => self
1035                .bytes
1036                .chunks_exact(2)
1037                .map(|c| cortiq_core::quant::f16_to_f32(u16::from_le_bytes([c[0], c[1]])))
1038                .collect(),
1039            other => {
1040                return Err(format!(
1041                    "{}: unsupported safetensors dtype {other}",
1042                    self.name
1043                ));
1044            }
1045        };
1046        if data.len() != self.shape.iter().product::<usize>().max(1) {
1047            return Err(format!(
1048                "{}: {} values for shape {:?}",
1049                self.name,
1050                data.len(),
1051                self.shape
1052            ));
1053        }
1054        Ok(data)
1055    }
1056}
1057
1058/// Tokenizer tensors the encoder path does not use (EMA state).
1059fn tokenizer_tensor_unused(name: &str) -> bool {
1060    name.ends_with("._codebook.cluster_size")
1061        || name.ends_with("._codebook.embed_avg")
1062        || name.ends_with("._codebook.inited")
1063}
1064
1065/// Development helper: every audio-tower tensor of an HF MiMo-V2.6
1066/// checkpoint directory under its canonical name (`audio_tokenizer.` is
1067/// prefixed to the tokenizer file's `encoder.*`; its decoder and EMA state
1068/// are skipped), plus the config blobs `mm.config_json` and
1069/// `audio_tokenizer.config_json` when the files exist.
1070pub fn read_hf_audio_tensors(
1071    dir: &Path,
1072) -> Result<(Vec<RawTensor>, Vec<(String, Vec<u8>)>), String> {
1073    let mut tensors = Vec::new();
1074    let at_dir = dir.join("audio_tokenizer");
1075    read_safetensors_selected(
1076        &at_dir.join("model.safetensors"),
1077        &|n| {
1078            (n.starts_with("encoder.") && !tokenizer_tensor_unused(n))
1079                .then(|| format!("audio_tokenizer.{n}"))
1080        },
1081        &mut tensors,
1082    )?;
1083    // LLM-side audio tensors: whichever shards the index names.
1084    let index_path = dir.join("model.safetensors.index.json");
1085    let index: Value = serde_json::from_slice(
1086        &std::fs::read(&index_path).map_err(|e| format!("{}: {e}", index_path.display()))?,
1087    )
1088    .map_err(|e| format!("{}: {e}", index_path.display()))?;
1089    let map = index["weight_map"]
1090        .as_object()
1091        .ok_or("model.safetensors.index.json: no weight_map")?;
1092    let keep = |k: &str| k.starts_with("audio_encoder.") || k.starts_with("speech_embeddings.");
1093    let mut files: Vec<String> = map
1094        .iter()
1095        .filter(|(k, _)| keep(k))
1096        .filter_map(|(_, v)| v.as_str().map(str::to_string))
1097        .collect();
1098    files.sort();
1099    files.dedup();
1100    for file in files {
1101        read_safetensors_selected(
1102            &dir.join(&file),
1103            &|n| keep(n).then(|| n.to_string()),
1104            &mut tensors,
1105        )?;
1106    }
1107    let mut blobs = Vec::new();
1108    for (blob, file) in [
1109        ("mm.config_json", dir.join("config.json")),
1110        ("audio_tokenizer.config_json", at_dir.join("config.json")),
1111    ] {
1112        if let Ok(b) = std::fs::read(&file) {
1113            blobs.push((blob.to_string(), b));
1114        }
1115    }
1116    Ok((tensors, blobs))
1117}
1118
1119impl HfSource {
1120    fn open(dir: &Path) -> Result<Self, String> {
1121        let (raw, blobs) = read_hf_audio_tensors(dir)?;
1122        let mut tensors = HashMap::with_capacity(raw.len());
1123        for t in raw {
1124            let data = t.to_f32()?;
1125            tensors.insert(t.name, (data, t.shape));
1126        }
1127        Ok(Self {
1128            tensors,
1129            blobs: blobs.into_iter().collect(),
1130        })
1131    }
1132}
1133
1134impl WeightSource for HfSource {
1135    fn has(&self, name: &str) -> bool {
1136        self.tensors.contains_key(name)
1137    }
1138    fn dense(&mut self, name: &str) -> Result<(Vec<f32>, Vec<usize>), String> {
1139        self.tensors
1140            .remove(name)
1141            .ok_or_else(|| format!("mimo audio: missing tensor '{name}'"))
1142    }
1143    fn proj(&mut self, name: &str) -> Result<Proj, String> {
1144        let (w, shape) = self.dense(name)?;
1145        if shape.len() < 2 {
1146            return Err(format!("mimo audio: '{name}' is not a matrix: {shape:?}"));
1147        }
1148        let cols = shape[1..].iter().product::<usize>();
1149        Ok(Proj::f32(w, cols))
1150    }
1151    fn config_blob(&self, name: &str) -> Option<Vec<u8>> {
1152        self.blobs.get(name).cloned()
1153    }
1154}
1155
1156fn take_vec(src: &mut dyn WeightSource, name: &str, len: usize) -> Result<Vec<f32>, String> {
1157    let (v, shape) = src.dense(name)?;
1158    if v.len() != len {
1159        return Err(format!(
1160            "mimo audio: '{name}' has shape {shape:?}, expected {len} values"
1161        ));
1162    }
1163    Ok(v)
1164}
1165
1166fn take_proj(
1167    src: &mut dyn WeightSource,
1168    name: &str,
1169    rows: usize,
1170    cols: usize,
1171) -> Result<Mat, String> {
1172    let p = src.proj(name)?;
1173    if p.rows() != rows || p.cols() != cols {
1174        return Err(format!(
1175            "mimo audio: '{name}' is [{}, {}], expected [{rows}, {cols}]",
1176            p.rows(),
1177            p.cols()
1178        ));
1179    }
1180    Ok(Mat {
1181        p,
1182        name: name.to_string(),
1183    })
1184}
1185
1186// ───────────────────────────── configs ─────────────────────────────
1187
1188/// The audio tokenizer encoder hyper-parameters (`audio_tokenizer/config.json`).
1189#[derive(Clone, Debug)]
1190pub struct TokenizerConfig {
1191    pub d_model: usize,
1192    pub layers: usize,
1193    pub heads: usize,
1194    pub ffn: usize,
1195    pub n_mels: usize,
1196    pub skip_layer_id: Option<usize>,
1197    pub causal: bool,
1198    /// Band half-width on windowed layers (`encoder_attn_window_size[0]`),
1199    /// 0 = none.
1200    pub window: usize,
1201    pub hybrid: bool,
1202    pub swa_per_block: usize,
1203    pub rope_theta: f64,
1204    pub codebook_sizes: Vec<usize>,
1205    pub ln_eps: f64,
1206}
1207
1208impl TokenizerConfig {
1209    /// The pinned MiMo-Audio-Tokenizer values.
1210    pub fn mimo_default() -> Self {
1211        let mut books = vec![1024, 1024, 256];
1212        books.extend(std::iter::repeat_n(128, 17));
1213        Self {
1214            d_model: 1024,
1215            layers: 24,
1216            heads: 16,
1217            ffn: 4096,
1218            n_mels: 128,
1219            skip_layer_id: Some(3),
1220            causal: true,
1221            window: 128,
1222            hybrid: true,
1223            swa_per_block: 2,
1224            rope_theta: 10_000.0,
1225            codebook_sizes: books,
1226            ln_eps: 1e-5,
1227        }
1228    }
1229
1230    pub fn from_json(bytes: &[u8]) -> Result<Self, String> {
1231        let v: Value =
1232            serde_json::from_slice(bytes).map_err(|e| format!("audio_tokenizer config: {e}"))?;
1233        let d = Self::mimo_default();
1234        let us = |k: &str, dflt: usize| {
1235            v.get(k)
1236                .and_then(Value::as_u64)
1237                .map_or(dflt, |x| x as usize)
1238        };
1239        let bl = |k: &str, dflt: bool| v.get(k).and_then(Value::as_bool).unwrap_or(dflt);
1240        // Only the geometry this implementation computes is accepted.
1241        for (k, want) in [
1242            ("kernel_size", 3u64),
1243            ("stride_size", 2),
1244            ("avg_pooler", 2),
1245            ("sampling_rate", SAMPLE_RATE as u64),
1246            ("hop_length", HOP as u64),
1247            ("nfft", N_FFT as u64),
1248            ("window_size", N_FFT as u64),
1249        ] {
1250            if let Some(got) = v.get(k).and_then(Value::as_u64) {
1251                if got != want {
1252                    return Err(format!(
1253                        "audio_tokenizer config: {k} = {got}, only {want} is supported"
1254                    ));
1255                }
1256            }
1257        }
1258        if bl("scale_embedding", false) {
1259            return Err("audio_tokenizer config: scale_embedding = true is not supported".into());
1260        }
1261        if let Some(ln) = v.get("ln_type").and_then(Value::as_str) {
1262            if ln != "LayerNorm" {
1263                return Err(format!(
1264                    "audio_tokenizer config: ln_type {ln} is not supported"
1265                ));
1266            }
1267        }
1268        let n_q = us("num_quantizers", 20);
1269        let mut books: Vec<usize> = match v.get("codebook_size") {
1270            Some(Value::Array(a)) => a
1271                .iter()
1272                .filter_map(Value::as_u64)
1273                .map(|x| x as usize)
1274                .collect(),
1275            Some(x) => vec![x.as_u64().unwrap_or(1024) as usize],
1276            None => d.codebook_sizes.clone(),
1277        };
1278        if books.is_empty() {
1279            return Err("audio_tokenizer config: empty codebook_size".into());
1280        }
1281        while books.len() < n_q {
1282            books.push(*books.last().unwrap());
1283        }
1284        books.truncate(n_q);
1285        let window = v
1286            .get("encoder_attn_window_size")
1287            .and_then(Value::as_array)
1288            .and_then(|a| a.first())
1289            .and_then(Value::as_i64)
1290            .map_or(d.window as i64, |x| x)
1291            .max(0) as usize;
1292        Ok(Self {
1293            d_model: us("d_model", d.d_model),
1294            layers: us("encoder_layers", d.layers),
1295            heads: us("encoder_attention_heads", d.heads),
1296            ffn: us("encoder_ffn_dim", d.ffn),
1297            n_mels: us("n_mels", d.n_mels),
1298            skip_layer_id: match v.get("encoder_skip_layer_id") {
1299                Some(Value::Null) => None,
1300                Some(x) => x.as_u64().map(|x| x as usize),
1301                None => d.skip_layer_id,
1302            },
1303            causal: bl("encoder_causal", d.causal),
1304            window,
1305            hybrid: bl("hybrid_attention", d.hybrid),
1306            swa_per_block: us("swa_per_block", d.swa_per_block).max(1),
1307            rope_theta: v
1308                .get("rope_theta")
1309                .and_then(Value::as_f64)
1310                .unwrap_or(d.rope_theta),
1311            codebook_sizes: books,
1312            ln_eps: d.ln_eps,
1313        })
1314    }
1315
1316    /// Whether layer `i` carries the band window (HF: hybrid → the first
1317    /// `swa_per_block − 1` of every `swa_per_block` layers; else all).
1318    pub fn windowed(&self, i: usize) -> bool {
1319        self.window > 0 && (!self.hybrid || i % self.swa_per_block < self.swa_per_block - 1)
1320    }
1321}
1322
1323/// The LLM-side audio encoder hyper-parameters (`config.json` `audio_config`).
1324#[derive(Clone, Debug)]
1325pub struct EncoderConfig {
1326    pub channels: usize,
1327    pub group: usize,
1328    pub dim: usize,
1329    pub layers: usize,
1330    pub heads: usize,
1331    pub head_dim: usize,
1332    pub ffn: usize,
1333    pub rope_theta: f64,
1334    pub out_dim: usize,
1335    pub vocab: usize,
1336    pub bidirectional: bool,
1337    pub post_norm: bool,
1338    pub projection_layers: usize,
1339    pub rms_eps: f64,
1340}
1341
1342impl EncoderConfig {
1343    pub fn mimo_default() -> Self {
1344        Self {
1345            channels: 20,
1346            group: 4,
1347            dim: 1024,
1348            layers: 6,
1349            heads: 16,
1350            head_dim: 64,
1351            ffn: 4096,
1352            rope_theta: 640_000.0,
1353            out_dim: 4096,
1354            vocab: 1280,
1355            bidirectional: true,
1356            post_norm: true,
1357            projection_layers: 2,
1358            rms_eps: 1e-6,
1359        }
1360    }
1361
1362    /// Parse the `audio_config` object of the main `config.json`.
1363    pub fn from_main_config(bytes: &[u8]) -> Result<Self, String> {
1364        let v: Value =
1365            serde_json::from_slice(bytes).map_err(|e| format!("mimo config.json: {e}"))?;
1366        let a = v
1367            .get("audio_config")
1368            .filter(|a| a.is_object())
1369            .ok_or("mimo config.json: no audio_config")?;
1370        let d = Self::mimo_default();
1371        let us = |k: &str, dflt: usize| {
1372            a.get(k)
1373                .and_then(Value::as_u64)
1374                .map_or(dflt, |x| x as usize)
1375        };
1376        let first_num = |k: &str, dflt: usize| -> usize {
1377            match a.get(k) {
1378                Some(Value::String(s)) => s
1379                    .split('-')
1380                    .next()
1381                    .and_then(|x| x.trim().parse().ok())
1382                    .unwrap_or(dflt),
1383                Some(x) => x.as_u64().map_or(dflt, |x| x as usize),
1384                None => dflt,
1385            }
1386        };
1387        if let Some(Value::String(s)) = a.get("speech_vocab_size") {
1388            if s.contains('-') {
1389                return Err(format!(
1390                    "mimo audio_config: per-channel speech_vocab_size '{s}' is not supported"
1391                ));
1392            }
1393        }
1394        let prf = a
1395            .get("partial_rotary_factor")
1396            .and_then(Value::as_f64)
1397            .unwrap_or(1.0);
1398        if (prf - 1.0).abs() > 1e-9 {
1399            return Err(format!(
1400                "mimo audio_config: partial_rotary_factor {prf} is not supported"
1401            ));
1402        }
1403        let dim = us("input_local_dim", d.dim);
1404        let heads = us("input_local_attn_heads", d.heads);
1405        Ok(Self {
1406            channels: us("audio_channels", d.channels),
1407            group: us("group_size", d.group),
1408            dim,
1409            layers: us("input_local_layers", d.layers),
1410            heads,
1411            head_dim: dim / heads.max(1),
1412            ffn: us("input_local_intermediate_size", d.ffn),
1413            rope_theta: a
1414                .get("rope_theta")
1415                .and_then(Value::as_f64)
1416                .unwrap_or(d.rope_theta),
1417            out_dim: us("out_hidden_size", d.out_dim),
1418            vocab: first_num("speech_vocab_size", d.vocab),
1419            bidirectional: a
1420                .get("input_full_attention")
1421                .and_then(Value::as_bool)
1422                .unwrap_or(true),
1423            post_norm: a
1424                .get("add_post_norm")
1425                .and_then(Value::as_bool)
1426                .unwrap_or(true),
1427            projection_layers: us("projection_layers", d.projection_layers),
1428            rms_eps: d.rms_eps,
1429        })
1430    }
1431}
1432
1433// ───────────────────────────── tokenizer ─────────────────────────────
1434
1435struct TokLayer {
1436    ln1_w: Vec<f32>,
1437    ln1_b: Vec<f32>,
1438    q: Mat,
1439    q_b: Vec<f32>,
1440    k: Mat,
1441    v: Mat,
1442    v_b: Vec<f32>,
1443    o: Mat,
1444    o_b: Vec<f32>,
1445    ln2_w: Vec<f32>,
1446    ln2_b: Vec<f32>,
1447    fc1: Mat,
1448    fc1_b: Vec<f32>,
1449    fc2: Mat,
1450    fc2_b: Vec<f32>,
1451}
1452
1453/// The MiMo audio tokenizer encoder and its residual VQ.
1454pub struct AudioTokenizer {
1455    pub cfg: TokenizerConfig,
1456    conv1: Mat, // [d, n_mels·3]
1457    conv1_b: Vec<f32>,
1458    conv2: Mat, // [d, d·3]
1459    conv2_b: Vec<f32>,
1460    layers: Vec<TokLayer>,
1461    ln_w: Vec<f32>,
1462    ln_b: Vec<f32>,
1463    pool_w: Mat, // [d, d·2], no bias
1464    pool_ln_w: Vec<f32>,
1465    pool_ln_b: Vec<f32>,
1466    /// Codebooks as stored (f32), `[size][d]` each.
1467    books: Vec<Vec<f32>>,
1468    /// The same codebooks rounded to bf16 (what a bf16 serving load holds).
1469    books_bf16: Vec<Vec<f32>>,
1470}
1471
1472fn bf16_round(x: f32) -> f32 {
1473    let b = x.to_bits();
1474    if x.is_nan() {
1475        return x;
1476    }
1477    let lsb = (b >> 16) & 1;
1478    let r = b.wrapping_add(0x7FFF + lsb) & 0xFFFF_0000;
1479    f32::from_bits(r)
1480}
1481
1482impl AudioTokenizer {
1483    fn load(src: &mut dyn WeightSource, cfg: TokenizerConfig) -> Result<Self, String> {
1484        let p = "audio_tokenizer.encoder";
1485        let d = cfg.d_model;
1486        if d % cfg.heads != 0 || (d / cfg.heads) % 2 != 0 {
1487            return Err(format!(
1488                "audio tokenizer: d_model {d} / heads {} is not an even head width",
1489                cfg.heads
1490            ));
1491        }
1492        let mut layers = Vec::with_capacity(cfg.layers);
1493        for i in 0..cfg.layers {
1494            let l = format!("{p}.layers.{i}");
1495            if src.has(&format!("{l}.self_attn.k_proj.bias")) {
1496                return Err(format!(
1497                    "audio tokenizer: unexpected {l}.self_attn.k_proj.bias (k has no bias)"
1498                ));
1499            }
1500            layers.push(TokLayer {
1501                ln1_w: take_vec(src, &format!("{l}.self_attn_layer_norm.weight"), d)?,
1502                ln1_b: take_vec(src, &format!("{l}.self_attn_layer_norm.bias"), d)?,
1503                q: take_proj(src, &format!("{l}.self_attn.q_proj.weight"), d, d)?,
1504                q_b: take_vec(src, &format!("{l}.self_attn.q_proj.bias"), d)?,
1505                k: take_proj(src, &format!("{l}.self_attn.k_proj.weight"), d, d)?,
1506                v: take_proj(src, &format!("{l}.self_attn.v_proj.weight"), d, d)?,
1507                v_b: take_vec(src, &format!("{l}.self_attn.v_proj.bias"), d)?,
1508                o: take_proj(src, &format!("{l}.self_attn.out_proj.weight"), d, d)?,
1509                o_b: take_vec(src, &format!("{l}.self_attn.out_proj.bias"), d)?,
1510                ln2_w: take_vec(src, &format!("{l}.final_layer_norm.weight"), d)?,
1511                ln2_b: take_vec(src, &format!("{l}.final_layer_norm.bias"), d)?,
1512                fc1: take_proj(src, &format!("{l}.fc1.weight"), cfg.ffn, d)?,
1513                fc1_b: take_vec(src, &format!("{l}.fc1.bias"), cfg.ffn)?,
1514                fc2: take_proj(src, &format!("{l}.fc2.weight"), d, cfg.ffn)?,
1515                fc2_b: take_vec(src, &format!("{l}.fc2.bias"), d)?,
1516            });
1517        }
1518        let mut books = Vec::with_capacity(cfg.codebook_sizes.len());
1519        for (i, &size) in cfg.codebook_sizes.iter().enumerate() {
1520            books.push(take_vec(
1521                src,
1522                &format!("{p}.quantizer.vq.layers.{i}._codebook.embed"),
1523                size * d,
1524            )?);
1525        }
1526        let books_bf16 = books
1527            .iter()
1528            .map(|b| b.iter().map(|&v| bf16_round(v)).collect())
1529            .collect();
1530        Ok(Self {
1531            conv1: take_proj(src, &format!("{p}.conv1.weight"), d, cfg.n_mels * 3)?,
1532            conv1_b: take_vec(src, &format!("{p}.conv1.bias"), d)?,
1533            conv2: take_proj(src, &format!("{p}.conv2.weight"), d, d * 3)?,
1534            conv2_b: take_vec(src, &format!("{p}.conv2.bias"), d)?,
1535            layers,
1536            ln_w: take_vec(src, &format!("{p}.layer_norm.weight"), d)?,
1537            ln_b: take_vec(src, &format!("{p}.layer_norm.bias"), d)?,
1538            pool_w: take_proj(src, &format!("{p}.down_sample_layer.0.weight"), d, d * 2)?,
1539            pool_ln_w: take_vec(src, &format!("{p}.down_sample_norm.weight"), d)?,
1540            pool_ln_b: take_vec(src, &format!("{p}.down_sample_norm.bias"), d)?,
1541            books,
1542            books_bf16,
1543            cfg,
1544        })
1545    }
1546
1547    /// Pre-RVQ features of ONE segment of `m` mel frames (`mel [m][n_mels]`):
1548    /// rows `[⌈⌈m/2⌉/2⌉][d]` after `down_sample_norm`.
1549    pub fn features(&self, mel: &[f32], m: usize, pool: Option<&Pool>) -> Result<Vec<f32>, String> {
1550        let cfg = &self.cfg;
1551        let (d, nm) = (cfg.d_model, cfg.n_mels);
1552        if m == 0 || mel.len() != m * nm {
1553            return Err(format!(
1554                "audio tokenizer: mel has {} values for {m} frames",
1555                mel.len()
1556            ));
1557        }
1558        // conv1: k3 p1 stride 1, im2col row t = [x[t+k−1][c]] in (c, k) order.
1559        let mut col = vec![0f32; m * nm * 3];
1560        for t in 0..m {
1561            for k in 0..3 {
1562                let s = t as isize + k as isize - 1;
1563                if s < 0 || s >= m as isize {
1564                    continue;
1565                }
1566                let src = &mel[s as usize * nm..(s as usize + 1) * nm];
1567                let row = &mut col[t * nm * 3..(t + 1) * nm * 3];
1568                for c in 0..nm {
1569                    row[c * 3 + k] = src[c];
1570                }
1571            }
1572        }
1573        let mut h1 = linear(&self.conv1, Some(&self.conv1_b), &col, m, pool);
1574        gelu_inplace(&mut h1);
1575        // conv2: k3 p1 stride 2.
1576        let l1 = m.div_ceil(2);
1577        let mut col = vec![0f32; l1 * d * 3];
1578        for t in 0..l1 {
1579            for k in 0..3 {
1580                let s = 2 * t as isize + k as isize - 1;
1581                if s < 0 || s >= m as isize {
1582                    continue;
1583                }
1584                let src = &h1[s as usize * d..(s as usize + 1) * d];
1585                let row = &mut col[t * d * 3..(t + 1) * d * 3];
1586                for c in 0..d {
1587                    row[c * 3 + k] = src[c];
1588                }
1589            }
1590        }
1591        drop(h1);
1592        let mut h = linear(&self.conv2, Some(&self.conv2_b), &col, l1, pool);
1593        drop(col);
1594        gelu_inplace(&mut h);
1595
1596        let heads = cfg.heads;
1597        let hd = d / heads;
1598        let (cos, sin) = rope_tables(cfg.rope_theta, hd, l1);
1599        let mut skip: Option<Vec<f32>> = None;
1600        let mut n = vec![0f32; l1 * d];
1601        let mut att = vec![0f32; l1 * d];
1602        for (li, layer) in self.layers.iter().enumerate() {
1603            layer_norm_rows(&h, &layer.ln1_w, &layer.ln1_b, cfg.ln_eps, &mut n);
1604            let mut q = linear(&layer.q, Some(&layer.q_b), &n, l1, pool);
1605            let mut k = linear(&layer.k, None, &n, l1, pool);
1606            let v = linear(&layer.v, Some(&layer.v_b), &n, l1, pool);
1607            apply_rope(&mut q, heads, hd, &cos, &sin, |r| r);
1608            apply_rope(&mut k, heads, hd, &cos, &sin, |r| r);
1609            let causal = cfg.causal;
1610            let window = if cfg.windowed(li) { cfg.window } else { 0 };
1611            let range = move |i: usize| -> (usize, usize) {
1612                let hi = if causal { i + 1 } else { l1 };
1613                let (lo, hi) = if window > 0 {
1614                    (i.saturating_sub(window), hi.min(i + window + 1))
1615                } else {
1616                    (0, hi)
1617                };
1618                (lo, hi)
1619            };
1620            attention(&q, &k, &v, l1, heads, hd, &range, &mut att, pool);
1621            let o = linear(&layer.o, Some(&layer.o_b), &att, l1, pool);
1622            add_into(&mut h, &o);
1623            layer_norm_rows(&h, &layer.ln2_w, &layer.ln2_b, cfg.ln_eps, &mut n);
1624            let mut f = linear(&layer.fc1, Some(&layer.fc1_b), &n, l1, pool);
1625            gelu_inplace(&mut f);
1626            let f2 = linear(&layer.fc2, Some(&layer.fc2_b), &f, l1, pool);
1627            add_into(&mut h, &f2);
1628            if cfg.skip_layer_id.is_some_and(|s| s >= 1 && li == s - 1) {
1629                skip = Some(h.clone());
1630            }
1631        }
1632        if let Some(s) = skip {
1633            add_into(&mut h, &s);
1634        }
1635        layer_norm_rows(&h, &self.ln_w, &self.ln_b, cfg.ln_eps, &mut n);
1636        // Pooler: zero-pad to even, conv k2 s2 (no bias), GELU, LayerNorm.
1637        let l2 = l1.div_ceil(2);
1638        let mut col = vec![0f32; l2 * d * 2];
1639        for t in 0..l2 {
1640            for k in 0..2 {
1641                let s = 2 * t + k;
1642                if s >= l1 {
1643                    continue;
1644                }
1645                let src = &n[s * d..(s + 1) * d];
1646                let row = &mut col[t * d * 2..(t + 1) * d * 2];
1647                for c in 0..d {
1648                    row[c * 2 + k] = src[c];
1649                }
1650            }
1651        }
1652        let mut p = linear(&self.pool_w, None, &col, l2, pool);
1653        gelu_inplace(&mut p);
1654        let mut out = vec![0f32; l2 * d];
1655        layer_norm_rows(&p, &self.pool_ln_w, &self.pool_ln_b, cfg.ln_eps, &mut out);
1656        Ok(out)
1657    }
1658
1659    /// Residual VQ in f32: per level, the nearest codeword by
1660    /// `‖E‖² − 2 r·E` (the row's ‖r‖² does not move the argmin), then
1661    /// `r −= E[idx]`. `bf16_books` selects the bf16-rounded codebooks a
1662    /// bf16 serving load holds. Returns `[rows][levels]`.
1663    pub fn quantize(
1664        &self,
1665        feats: &[f32],
1666        rows: usize,
1667        bf16_books: bool,
1668        pool: Option<&Pool>,
1669    ) -> Vec<u32> {
1670        let d = self.cfg.d_model;
1671        let books = if bf16_books {
1672            &self.books_bf16
1673        } else {
1674            &self.books
1675        };
1676        let nq = books.len();
1677        let norms: Vec<Vec<f64>> = books
1678            .iter()
1679            .map(|b| {
1680                b.chunks_exact(d)
1681                    .map(|e| e.iter().map(|&v| v as f64 * v as f64).sum())
1682                    .collect()
1683            })
1684            .collect();
1685        let mut codes = vec![0u32; rows * nq];
1686        let dst = crate::pool::SendMutT::new(codes.as_mut_ptr());
1687        let run = |start: usize, end: usize| {
1688            let mut r = vec![0f32; d];
1689            for row in start..end {
1690                r.copy_from_slice(&feats[row * d..(row + 1) * d]);
1691                for (lvl, book) in books.iter().enumerate() {
1692                    let mut best = (f64::INFINITY, 0usize);
1693                    for (ci, e) in book.chunks_exact(d).enumerate() {
1694                        let dist = norms[lvl][ci] - 2.0 * dot_f64(&r, e);
1695                        if dist < best.0 {
1696                            best = (dist, ci);
1697                        }
1698                    }
1699                    let e = &book[best.1 * d..(best.1 + 1) * d];
1700                    for t in 0..d {
1701                        r[t] -= e[t];
1702                    }
1703                    unsafe { *dst.at(row * nq + lvl) = best.1 as u32 };
1704                }
1705            }
1706        };
1707        match pool {
1708            Some(p) if rows >= 8 => p.run_rows(rows, &run),
1709            _ => run(0, rows),
1710        }
1711        codes
1712    }
1713}
1714
1715// ───────────────────────────── LLM-side encoder ─────────────────────────────
1716
1717struct LocalLayer {
1718    ln1: Vec<f32>,
1719    q: Mat,
1720    q_b: Vec<f32>,
1721    k: Mat,
1722    k_b: Vec<f32>,
1723    v: Mat,
1724    v_b: Vec<f32>,
1725    o: Mat,
1726    ln2: Vec<f32>,
1727    gate: Mat,
1728    up: Mat,
1729    down: Mat,
1730}
1731
1732/// Speech embeddings + the local Qwen2 transformer + the projection.
1733pub struct AudioEncoder {
1734    pub cfg: EncoderConfig,
1735    /// `[channel][vocab·dim]`.
1736    speech_emb: Vec<Vec<f32>>,
1737    layers: Vec<LocalLayer>,
1738    norm: Option<Vec<f32>>,
1739    proj0: Mat,
1740    proj2: Option<Mat>,
1741}
1742
1743impl AudioEncoder {
1744    fn load(src: &mut dyn WeightSource, cfg: EncoderConfig) -> Result<Self, String> {
1745        let (d, f) = (cfg.dim, cfg.ffn);
1746        let qd = cfg.heads * cfg.head_dim;
1747        let mut speech_emb = Vec::with_capacity(cfg.channels);
1748        for c in 0..cfg.channels {
1749            speech_emb.push(take_vec(
1750                src,
1751                &format!("speech_embeddings.{c}.weight"),
1752                cfg.vocab * d,
1753            )?);
1754        }
1755        let p = "audio_encoder.input_local_transformer";
1756        let mut layers = Vec::with_capacity(cfg.layers);
1757        for i in 0..cfg.layers {
1758            let l = format!("{p}.layers.{i}");
1759            layers.push(LocalLayer {
1760                ln1: take_vec(src, &format!("{l}.input_layernorm.weight"), d)?,
1761                q: take_proj(src, &format!("{l}.self_attn.q_proj.weight"), qd, d)?,
1762                q_b: take_vec(src, &format!("{l}.self_attn.q_proj.bias"), qd)?,
1763                k: take_proj(src, &format!("{l}.self_attn.k_proj.weight"), qd, d)?,
1764                k_b: take_vec(src, &format!("{l}.self_attn.k_proj.bias"), qd)?,
1765                v: take_proj(src, &format!("{l}.self_attn.v_proj.weight"), qd, d)?,
1766                v_b: take_vec(src, &format!("{l}.self_attn.v_proj.bias"), qd)?,
1767                o: take_proj(src, &format!("{l}.self_attn.o_proj.weight"), d, qd)?,
1768                ln2: take_vec(src, &format!("{l}.post_attention_layernorm.weight"), d)?,
1769                gate: take_proj(src, &format!("{l}.mlp.gate_proj.weight"), f, d)?,
1770                up: take_proj(src, &format!("{l}.mlp.up_proj.weight"), f, d)?,
1771                down: take_proj(src, &format!("{l}.mlp.down_proj.weight"), d, f)?,
1772            });
1773        }
1774        let norm = if cfg.post_norm {
1775            Some(take_vec(src, &format!("{p}.norm.weight"), d)?)
1776        } else {
1777            None
1778        };
1779        let flat = d * cfg.group;
1780        let (proj0, proj2) = match cfg.projection_layers {
1781            2 => (
1782                take_proj(src, "audio_encoder.projection.mlp.0.weight", flat * 4, flat)?,
1783                Some(take_proj(
1784                    src,
1785                    "audio_encoder.projection.mlp.2.weight",
1786                    cfg.out_dim,
1787                    flat * 4,
1788                )?),
1789            ),
1790            1 => (
1791                take_proj(src, "audio_encoder.projection.weight", cfg.out_dim, flat)?,
1792                None,
1793            ),
1794            n => {
1795                return Err(format!(
1796                    "mimo audio_config: projection_layers = {n} is not supported"
1797                ));
1798            }
1799        };
1800        Ok(Self {
1801            cfg,
1802            speech_emb,
1803            layers,
1804            norm,
1805            proj0,
1806            proj2,
1807        })
1808    }
1809
1810    /// Codes `[t][cols]` (cols ≥ channels; extra columns are ignored) →
1811    /// grouped `[G][group][channels]`, the tail padded by repeating the last
1812    /// row.
1813    pub fn group_codes(
1814        &self,
1815        codes: &[u32],
1816        t: usize,
1817        cols: usize,
1818    ) -> Result<(Vec<u32>, usize), String> {
1819        let (c, g) = (self.cfg.channels, self.cfg.group);
1820        if cols < c {
1821            return Err(format!("audio codes have {cols} channels, need {c}"));
1822        }
1823        if t == 0 || codes.len() != t * cols {
1824            return Err(format!(
1825                "audio codes: {} values for {t}×{cols}",
1826                codes.len()
1827            ));
1828        }
1829        let padded = t.div_ceil(g) * g;
1830        let mut out = Vec::with_capacity(padded * c);
1831        for r in 0..padded {
1832            let src = r.min(t - 1);
1833            out.extend_from_slice(&codes[src * cols..src * cols + c]);
1834        }
1835        Ok((out, padded / g))
1836    }
1837
1838    /// Codes `[t][cols]` → LLM rows `[G][out_dim]`, `G = ⌈t/group⌉`.
1839    pub fn forward(
1840        &self,
1841        codes: &[u32],
1842        t: usize,
1843        cols: usize,
1844        pool: Option<&Pool>,
1845    ) -> Result<Vec<f32>, String> {
1846        let cfg = &self.cfg;
1847        let (grouped, groups) = self.group_codes(codes, t, cols)?;
1848        let (d, c, g) = (cfg.dim, cfg.channels, cfg.group);
1849        let rows = groups * g;
1850        // Speech embeddings: Σ over channels, channel order, in f32.
1851        let mut x = vec![0f32; rows * d];
1852        for r in 0..rows {
1853            let dst = &mut x[r * d..(r + 1) * d];
1854            for ch in 0..c {
1855                let id = grouped[r * c + ch] as usize;
1856                if id >= cfg.vocab {
1857                    return Err(format!(
1858                        "audio code {id} in channel {ch} is outside the {} embedding rows",
1859                        cfg.vocab
1860                    ));
1861                }
1862                let e = &self.speech_emb[ch][id * d..(id + 1) * d];
1863                for (a, b) in dst.iter_mut().zip(e) {
1864                    *a += b;
1865                }
1866            }
1867        }
1868        let (heads, hd) = (cfg.heads, cfg.head_dim);
1869        let (cos, sin) = rope_tables(cfg.rope_theta, hd, g);
1870        let mut n = vec![0f32; rows * d];
1871        let mut att = vec![0f32; rows * heads * hd];
1872        let bidir = cfg.bidirectional;
1873        let range = move |i: usize| -> (usize, usize) {
1874            let base = i / g * g;
1875            (base, if bidir { base + g } else { i + 1 })
1876        };
1877        for layer in &self.layers {
1878            rms_norm_rows(&x, &layer.ln1, cfg.rms_eps, &mut n);
1879            let mut q = linear(&layer.q, Some(&layer.q_b), &n, rows, pool);
1880            let mut k = linear(&layer.k, Some(&layer.k_b), &n, rows, pool);
1881            let v = linear(&layer.v, Some(&layer.v_b), &n, rows, pool);
1882            apply_rope(&mut q, heads, hd, &cos, &sin, |r| r % g);
1883            apply_rope(&mut k, heads, hd, &cos, &sin, |r| r % g);
1884            attention(&q, &k, &v, rows, heads, hd, &range, &mut att, pool);
1885            let o = linear(&layer.o, None, &att, rows, pool);
1886            add_into(&mut x, &o);
1887            rms_norm_rows(&x, &layer.ln2, cfg.rms_eps, &mut n);
1888            let mut gt = linear(&layer.gate, None, &n, rows, pool);
1889            let up = linear(&layer.up, None, &n, rows, pool);
1890            for (a, b) in gt.iter_mut().zip(&up) {
1891                let s = *a / (1.0 + (-*a).exp());
1892                *a = s * b;
1893            }
1894            let dn = linear(&layer.down, None, &gt, rows, pool);
1895            add_into(&mut x, &dn);
1896        }
1897        if let Some(w) = &self.norm {
1898            rms_norm_rows(&x, w, cfg.rms_eps, &mut n);
1899            std::mem::swap(&mut x, &mut n);
1900        }
1901        // [G, group·dim] is exactly the row-major [rows, dim] buffer.
1902        let mut hdn = linear(&self.proj0, None, &x, groups, pool);
1903        match &self.proj2 {
1904            Some(p2) => {
1905                gelu_inplace(&mut hdn);
1906                Ok(linear(p2, None, &hdn, groups, pool))
1907            }
1908            None => Ok(hdn),
1909        }
1910    }
1911}
1912
1913// ───────────────────────────── the whole tower ─────────────────────────────
1914
1915/// Tokenizer codes of one clip.
1916#[derive(Clone, Debug)]
1917pub struct AudioCodes {
1918    /// 25 Hz frames.
1919    pub frames: usize,
1920    /// Codes per frame (the RVQ levels).
1921    pub levels: usize,
1922    /// Row-major `[frames][levels]`.
1923    pub codes: Vec<u32>,
1924}
1925
1926/// One clip ready for injection: `rows [n_tokens][out_dim]`.
1927#[derive(Clone, Debug)]
1928pub struct AudioEmbeds {
1929    pub n_tokens: usize,
1930    pub dim: usize,
1931    pub rows: Vec<f32>,
1932}
1933
1934/// The MiMo audio towers.
1935pub struct MimoAudio {
1936    pub tokenizer: AudioTokenizer,
1937    pub encoder: AudioEncoder,
1938    /// Quantize against bf16-rounded codebooks, as the bf16 serving stacks
1939    /// do (default on; `CMF_MIMO_AUDIO_BF16_BOOKS=0` turns it off).
1940    pub bf16_codebooks: bool,
1941    pool: Option<Arc<Pool>>,
1942}
1943
1944impl MimoAudio {
1945    /// Whether a CMF carries the audio towers (a companion or a single-file
1946    /// multimodal container).
1947    pub fn present_in(model: &CmfModel) -> bool {
1948        model
1949            .tensor_index("audio_tokenizer.encoder.conv1.weight")
1950            .is_some()
1951            && model
1952                .tensor_index("audio_encoder.projection.mlp.0.weight")
1953                .is_some()
1954    }
1955
1956    fn configs(src: &dyn WeightSource) -> Result<(TokenizerConfig, EncoderConfig), String> {
1957        let tok = match src.config_blob("audio_tokenizer.config_json") {
1958            Some(b) => TokenizerConfig::from_json(&b)?,
1959            None => TokenizerConfig::mimo_default(),
1960        };
1961        let enc = match src.config_blob("mm.config_json") {
1962            Some(b) => EncoderConfig::from_main_config(&b)?,
1963            None => EncoderConfig::mimo_default(),
1964        };
1965        if tok.codebook_sizes.len() < enc.channels {
1966            return Err(format!(
1967                "mimo audio: {} RVQ levels but {} speech embedding channels",
1968                tok.codebook_sizes.len(),
1969                enc.channels
1970            ));
1971        }
1972        if let Some(&big) = tok.codebook_sizes.iter().max() {
1973            if big > enc.vocab {
1974                return Err(format!(
1975                    "mimo audio: codebook of {big} entries exceeds the {} speech embedding rows",
1976                    enc.vocab
1977                ));
1978            }
1979        }
1980        Ok((tok, enc))
1981    }
1982
1983    fn build(mut src: Box<dyn WeightSource>) -> Result<Self, String> {
1984        let (tc, ec) = Self::configs(src.as_ref())?;
1985        let tokenizer = AudioTokenizer::load(src.as_mut(), tc)?;
1986        let encoder = AudioEncoder::load(src.as_mut(), ec)?;
1987        let bf16_codebooks = std::env::var("CMF_MIMO_AUDIO_BF16_BOOKS")
1988            .map(|v| v != "0")
1989            .unwrap_or(true);
1990        Ok(Self {
1991            tokenizer,
1992            encoder,
1993            bf16_codebooks,
1994            pool: Pool::from_env(),
1995        })
1996    }
1997
1998    /// Load from a CMF holding the source tensor names (the `<stem>.mm.cmf`
1999    /// companion or a single-file multimodal container).
2000    pub fn from_model(model: &Arc<CmfModel>) -> Result<Self, String> {
2001        Self::build(Box::new(CmfSource(model.clone())))
2002    }
2003
2004    /// Development loader: read the tensors straight from the HF checkpoint
2005    /// directory (`config.json`, `model.safetensors.index.json` and its
2006    /// shards, `audio_tokenizer/`), exact f32 from BF16.
2007    pub fn from_hf_dir(dir: &Path) -> Result<Self, String> {
2008        Self::build(Box::new(HfSource::open(dir)?))
2009    }
2010
2011    pub fn pool(&self) -> Option<&Pool> {
2012        self.pool.as_deref()
2013    }
2014
2015    /// WAV bytes → 24 kHz mono → log-mel `[M][128]`.
2016    pub fn wav_to_mel(&self, bytes: &[u8]) -> Result<(Vec<f32>, usize), String> {
2017        let wav = decode_wav(bytes)?;
2018        let mono = wav_to_mono_24k(&wav)?;
2019        log_mel(&mono, self.pool())
2020    }
2021
2022    /// Pre-RVQ features of every segment, concatenated `[codes][d]`.
2023    pub fn features(&self, mel: &[f32], m: usize) -> Result<Vec<f32>, String> {
2024        let nm = self.tokenizer.cfg.n_mels;
2025        let mut out = Vec::new();
2026        let mut start = 0usize;
2027        for seg in segment_lengths(m) {
2028            out.extend(self.tokenizer.features(
2029                &mel[start * nm..(start + seg) * nm],
2030                seg,
2031                self.pool(),
2032            )?);
2033            start += seg;
2034        }
2035        Ok(out)
2036    }
2037
2038    /// Log-mel → tokenizer codes, each 6000-frame segment on its own.
2039    pub fn encode_mel(&self, mel: &[f32], m: usize) -> Result<AudioCodes, String> {
2040        let d = self.tokenizer.cfg.d_model;
2041        let feats = self.features(mel, m)?;
2042        let frames = feats.len() / d;
2043        debug_assert_eq!(frames, codes_for_mel(m));
2044        let codes = self
2045            .tokenizer
2046            .quantize(&feats, frames, self.bf16_codebooks, self.pool());
2047        Ok(AudioCodes {
2048            frames,
2049            levels: self.tokenizer.books.len(),
2050            codes,
2051        })
2052    }
2053
2054    /// Codes → LLM embedding rows.
2055    pub fn embed_codes(&self, codes: &AudioCodes) -> Result<AudioEmbeds, String> {
2056        let rows = self
2057            .encoder
2058            .forward(&codes.codes, codes.frames, codes.levels, self.pool())?;
2059        let dim = self.encoder.cfg.out_dim;
2060        Ok(AudioEmbeds {
2061            n_tokens: rows.len() / dim,
2062            dim,
2063            rows,
2064        })
2065    }
2066
2067    /// WAV bytes → LLM embedding rows (one per `<|audio_pad|>`).
2068    pub fn embed_wav(&self, bytes: &[u8]) -> Result<AudioEmbeds, String> {
2069        let (mel, m) = self.wav_to_mel(bytes)?;
2070        let codes = self.encode_mel(&mel, m)?;
2071        let emb = self.embed_codes(&codes)?;
2072        let k = audio_token_count(m, self.encoder.cfg.group);
2073        if emb.n_tokens != k {
2074            return Err(format!(
2075                "mimo audio: encoder produced {} rows but the placeholder count is {k}",
2076                emb.n_tokens
2077            ));
2078        }
2079        Ok(emb)
2080    }
2081}
2082
2083#[cfg(test)]
2084mod tests {
2085    use super::*;
2086
2087    fn wav_bytes(
2088        tag: u16,
2089        channels: u16,
2090        rate: u32,
2091        bits: u16,
2092        extensible: bool,
2093        data: &[u8],
2094    ) -> Vec<u8> {
2095        let block = channels * bits.div_ceil(8);
2096        let mut fmt = Vec::new();
2097        fmt.extend_from_slice(&(if extensible { 0xFFFEu16 } else { tag }).to_le_bytes());
2098        fmt.extend_from_slice(&channels.to_le_bytes());
2099        fmt.extend_from_slice(&rate.to_le_bytes());
2100        fmt.extend_from_slice(&(rate * block as u32).to_le_bytes());
2101        fmt.extend_from_slice(&block.to_le_bytes());
2102        fmt.extend_from_slice(&bits.to_le_bytes());
2103        if extensible {
2104            fmt.extend_from_slice(&22u16.to_le_bytes());
2105            fmt.extend_from_slice(&bits.to_le_bytes());
2106            fmt.extend_from_slice(&0u32.to_le_bytes());
2107            fmt.extend_from_slice(&tag.to_le_bytes());
2108            fmt.extend_from_slice(&[0, 0, 0, 0, 0x10, 0, 0x80, 0, 0, 0xAA, 0, 0x38, 0x9B, 0x71]);
2109        }
2110        let mut out = Vec::new();
2111        out.extend_from_slice(b"RIFF");
2112        out.extend_from_slice(&0u32.to_le_bytes());
2113        out.extend_from_slice(b"WAVE");
2114        // An odd-sized junk chunk exercises the pad byte.
2115        out.extend_from_slice(b"junk");
2116        out.extend_from_slice(&3u32.to_le_bytes());
2117        out.extend_from_slice(&[1, 2, 3, 0]);
2118        out.extend_from_slice(b"fmt ");
2119        out.extend_from_slice(&(fmt.len() as u32).to_le_bytes());
2120        out.extend_from_slice(&fmt);
2121        out.extend_from_slice(b"data");
2122        out.extend_from_slice(&(data.len() as u32).to_le_bytes());
2123        out.extend_from_slice(data);
2124        let riff = (out.len() - 8) as u32;
2125        out[4..8].copy_from_slice(&riff.to_le_bytes());
2126        out
2127    }
2128
2129    #[test]
2130    fn wav_decodes_every_supported_layout() {
2131        // Two stereo frames: (min, max-ish) then (0, −half).
2132        let pcm16: Vec<u8> = [-32768i16, 32767, 0, -16384]
2133            .iter()
2134            .flat_map(|v| v.to_le_bytes())
2135            .collect();
2136        let w = decode_wav(&wav_bytes(1, 2, 16000, 16, false, &pcm16)).unwrap();
2137        assert_eq!(w.sample_rate, 16000);
2138        assert_eq!(
2139            w.channels,
2140            vec![vec![-1.0, 0.0], vec![32767.0 / 32768.0, -0.5]]
2141        );
2142
2143        let u8d = [0u8, 128, 255];
2144        let w = decode_wav(&wav_bytes(1, 1, 8000, 8, false, &u8d)).unwrap();
2145        assert_eq!(w.channels[0], vec![-1.0, 0.0, 127.0 / 128.0]);
2146
2147        let s24: Vec<u8> = [-8_388_608i32, 4_194_304, -1]
2148            .iter()
2149            .flat_map(|v| v.to_le_bytes()[..3].to_vec())
2150            .collect();
2151        for ext in [false, true] {
2152            let w = decode_wav(&wav_bytes(1, 1, 44100, 24, ext, &s24)).unwrap();
2153            assert_eq!(w.channels[0], vec![-1.0, 0.5, -1.0 / 8_388_608.0]);
2154        }
2155
2156        let s32: Vec<u8> = [i32::MIN, 1 << 30]
2157            .iter()
2158            .flat_map(|v| v.to_le_bytes())
2159            .collect();
2160        let w = decode_wav(&wav_bytes(1, 1, 48000, 32, false, &s32)).unwrap();
2161        assert_eq!(w.channels[0], vec![-1.0, 0.5]);
2162
2163        let f32d: Vec<u8> = [0.25f32, -0.75]
2164            .iter()
2165            .flat_map(|v| v.to_le_bytes())
2166            .collect();
2167        for ext in [false, true] {
2168            let w = decode_wav(&wav_bytes(3, 1, 22050, 32, ext, &f32d)).unwrap();
2169            assert_eq!(w.channels[0], vec![0.25, -0.75]);
2170        }
2171        let f64d: Vec<u8> = [0.125f64].iter().flat_map(|v| v.to_le_bytes()).collect();
2172        assert_eq!(
2173            decode_wav(&wav_bytes(3, 1, 24000, 64, false, &f64d))
2174                .unwrap()
2175                .channels[0],
2176            vec![0.125]
2177        );
2178
2179        // A-law is refused, not misread.
2180        assert!(decode_wav(&wav_bytes(6, 1, 8000, 8, false, &[0])).is_err());
2181        assert!(decode_wav(b"OggS....").is_err());
2182    }
2183
2184    #[test]
2185    fn wav_data_size_past_the_end_reads_to_the_end() {
2186        let pcm16: Vec<u8> = [1000i16, -1000, 5]
2187            .iter()
2188            .flat_map(|v| v.to_le_bytes())
2189            .collect();
2190        let mut b = wav_bytes(1, 1, 24000, 16, false, &pcm16);
2191        let n = b.len();
2192        // data size field sits 4 + data bytes before the end.
2193        b[n - pcm16.len() - 4..n - pcm16.len()].copy_from_slice(&u32::MAX.to_le_bytes());
2194        assert_eq!(decode_wav(&b).unwrap().channels[0].len(), 3);
2195    }
2196
2197    #[test]
2198    fn resample_kernel_widths_match_torchaudio() {
2199        // (orig, new) → width, from torchaudio's formula.
2200        for (o, n, w) in [
2201            (2usize, 3usize, 7usize),
2202            (147, 80, 12),
2203            (147, 160, 7),
2204            (2, 1, 13),
2205        ] {
2206            let (k, width) = sinc_resample_kernel(o, n);
2207            assert_eq!(width, w, "{o}->{n}");
2208            assert_eq!(k.len(), n * (2 * w + o));
2209        }
2210    }
2211
2212    #[test]
2213    fn resample_lengths_follow_torchaudio_rounding() {
2214        assert_eq!(resampled_len(16000, 16000, 24000), 24000);
2215        assert_eq!(resampled_len(16001, 16000, 24000), 24002);
2216        assert_eq!(resampled_len(44100, 44100, 24000), 24000);
2217        assert_eq!(resample_sinc(&vec![0.0; 16001], 16000, 24000).len(), 24002);
2218        // 80·1837421/147 = 999957 + 1/147: float32 (ulp 1/16 there) drops
2219        // the fraction, so torchaudio keeps one sample fewer than the exact
2220        // ceiling.
2221        let n = 1_837_421usize;
2222        assert_eq!((80 * n) % 147, 1);
2223        assert_eq!((80 * n).div_ceil(147), 999_958);
2224        assert_eq!(resampled_len(n, 44100, 24000), 999_957);
2225        // DC passes through the interior at the filter's unit gain × rolloff.
2226        let y = resample_sinc(&vec![1.0; 4000], 16000, 24000);
2227        assert!((y[3000] - 1.0).abs() < 1e-2, "{}", y[3000]);
2228    }
2229
2230    #[test]
2231    fn fft_matches_a_direct_dft() {
2232        let n = 960;
2233        let x: Vec<f64> = (0..n)
2234            .map(|i| ((i * 7919) % 97) as f64 / 97.0 - 0.5)
2235            .collect();
2236        let mut out = vec![(0.0, 0.0); n];
2237        Fft::new(n).forward_real(&x, &mut out);
2238        for k in [0usize, 1, 37, 240, 480, 959] {
2239            let (mut re, mut im) = (0.0, 0.0);
2240            for (t, v) in x.iter().enumerate() {
2241                let a = -2.0 * std::f64::consts::PI * (k * t % n) as f64 / n as f64;
2242                re += v * a.cos();
2243                im += v * a.sin();
2244            }
2245            assert!(
2246                (re - out[k].0).abs() < 1e-9 && (im - out[k].1).abs() < 1e-9,
2247                "bin {k}"
2248            );
2249        }
2250    }
2251
2252    #[test]
2253    fn hann_fixups_are_single_ulp_corrections() {
2254        let step = (2.0 * std::f64::consts::PI / N_FFT as f64) as f32;
2255        // Each fixup is torch's cos landing one ulp away from the correctly
2256        // rounded one (the window value itself can move by many ulps near
2257        // zero, where `0.5 − 0.5·c` cancels).
2258        for (i, bits) in TORCH_HANN_960_FIXUPS {
2259            let c = ((i as f32 * step) as f64).cos() as f32;
2260            let win = |c: f32| (c * -0.5f32 + 0.5f32).to_bits();
2261            assert_ne!(win(c), bits, "index {i} needs no fixup");
2262            let up = f32::from_bits(c.to_bits() + 1);
2263            let dn = f32::from_bits(c.to_bits() - 1);
2264            assert!(
2265                win(up) == bits || win(dn) == bits,
2266                "index {i}: not a one-ulp cos difference"
2267            );
2268        }
2269        let w = torch_hann_960();
2270        assert_eq!(w[0], 0.0);
2271        assert_eq!(w[480], 1.0);
2272    }
2273
2274    #[test]
2275    fn mel_shape_and_short_input() {
2276        assert!(log_mel(&vec![0.0; 480], None).is_err());
2277        let (mel, m) = log_mel(&vec![0.0; 481], None).unwrap();
2278        assert_eq!((m, mel.len()), (3, 3 * N_MELS));
2279        // Silence floors at ln(1e-7).
2280        assert!(
2281            mel.iter()
2282                .all(|&v| (v - (1e-7f64).ln() as f32).abs() < 1e-6)
2283        );
2284        assert_eq!(mel_frames(120_000), 501);
2285        // Filterbank: every band is non-empty (torchaudio warns otherwise).
2286        let fb = mel_filterbank(481, 128, 24000);
2287        for m in 0..128 {
2288            assert!((0..481).any(|k| fb[k * 128 + m] > 0.0), "band {m}");
2289        }
2290    }
2291
2292    #[test]
2293    fn token_counts_and_segments() {
2294        // Spec §2.5 worked examples.
2295        assert_eq!(audio_token_count(501, 4), 32);
2296        assert_eq!(codes_for_mel(501), 126);
2297        assert_eq!(codes_for_mel(6501), 1626);
2298        assert_eq!(audio_token_count(6501, 4), 407);
2299        assert_eq!(segment_lengths(6501), vec![6000, 501]);
2300        // G10.2: the placeholder count equals the encoder's group count for
2301        // every length from 1 s to 65 s at 24 kHz (and beyond a segment edge).
2302        for n in (24_000..=65 * 24_000).step_by(240 * 7) {
2303            let m = mel_frames(n);
2304            assert_eq!(
2305                codes_for_mel(m).div_ceil(4),
2306                audio_token_count(m, 4),
2307                "M={m}"
2308            );
2309        }
2310        for m in 5990..6020 {
2311            assert_eq!(
2312                codes_for_mel(m).div_ceil(4),
2313                audio_token_count(m, 4),
2314                "M={m}"
2315            );
2316        }
2317    }
2318
2319    #[test]
2320    fn placeholder_expansion() {
2321        let ids = [
2322            1,
2323            AUDIO_START_ID,
2324            AUDIO_PAD_ID,
2325            AUDIO_END_ID,
2326            2,
2327            AUDIO_START_ID,
2328            AUDIO_PAD_ID,
2329            AUDIO_END_ID,
2330        ];
2331        let out = expand_audio_placeholders(&ids, &[2, 3]).unwrap();
2332        assert_eq!(
2333            out,
2334            vec![
2335                1,
2336                AUDIO_START_ID,
2337                AUDIO_PAD_ID,
2338                AUDIO_PAD_ID,
2339                AUDIO_END_ID,
2340                2,
2341                AUDIO_START_ID,
2342                AUDIO_PAD_ID,
2343                AUDIO_PAD_ID,
2344                AUDIO_PAD_ID,
2345                AUDIO_END_ID
2346            ]
2347        );
2348        assert!(expand_audio_placeholders(&ids, &[2]).is_err());
2349        assert!(expand_audio_placeholders(&ids, &[2, 3, 4]).is_err());
2350        assert!(expand_audio_placeholders(&[AUDIO_PAD_ID], &[]).is_err());
2351    }
2352
2353    #[test]
2354    fn gelu_and_bf16_rounding() {
2355        // torch: F.gelu(tensor([-3., -0.5, 0.5, 3.])) (erf form).
2356        let want = [-0.004_049_71f32, -0.154_268_5, 0.345_731_5, 2.995_950_3];
2357        for (x, w) in [-3.0f32, -0.5, 0.5, 3.0].iter().zip(want) {
2358            assert!((gelu(*x) - w).abs() < 2e-6, "{x}: {} vs {w}", gelu(*x));
2359        }
2360        assert_eq!(bf16_round(1.0), 1.0);
2361        assert_eq!(bf16_round(f32::from_bits(0x3F80_8000)), 1.0); // tie → even
2362        assert_eq!(
2363            bf16_round(f32::from_bits(0x3F81_8000)),
2364            f32::from_bits(0x3F82_0000)
2365        );
2366    }
2367}