1use crate::pool::Pool;
17
18pub static AVAE_TIME: [std::sync::atomic::AtomicU64; 6] = [
23 std::sync::atomic::AtomicU64::new(0),
24 std::sync::atomic::AtomicU64::new(0),
25 std::sync::atomic::AtomicU64::new(0),
26 std::sync::atomic::AtomicU64::new(0),
27 std::sync::atomic::AtomicU64::new(0),
28 std::sync::atomic::AtomicU64::new(0),
29];
30
31fn atime_on() -> bool {
32 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
33 *ON.get_or_init(|| std::env::var("CMF_AVAE_TIME").is_ok())
34}
35
36fn atime(slot: usize, t: std::time::Instant) {
37 if atime_on() {
38 AVAE_TIME[slot].fetch_add(
39 t.elapsed().as_micros() as u64,
40 std::sync::atomic::Ordering::Relaxed,
41 );
42 }
43}
44
45pub fn avae_time_report() -> Option<String> {
47 if !atime_on() {
48 return None;
49 }
50 const NAMES: [&str; 6] = [
51 "dec_in", "conv_pre", "upsamples", "resblocks", "act_post", "conv_post",
52 ];
53 let mut v: Vec<(u64, &str)> = AVAE_TIME
54 .iter()
55 .map(|a| a.load(std::sync::atomic::Ordering::Relaxed))
56 .zip(NAMES)
57 .collect();
58 let total: u64 = v.iter().map(|(u, _)| u).sum();
59 if total == 0 {
60 return None;
61 }
62 v.sort_by(|a, b| b.0.cmp(&a.0));
63 let mut out = format!("audio vae phases (total {:.1} s):\n", total as f64 / 1e6);
64 for (us, name) in v {
65 out.push_str(&format!(
66 " {name:<12} {:>6.1} s {:>5.1}%\n",
67 us as f64 / 1e6,
68 100.0 * us as f64 / total as f64
69 ));
70 }
71 Some(out)
72}
73use cortiq_core::CmfModel;
74use std::sync::Arc;
75
76const FILTER_LEN: usize = 12;
79
80struct Conv1d {
81 w: Vec<f32>, b: Option<Vec<f32>>,
83 out_ch: usize,
84 in_ch: usize,
85 k: usize,
86 pad: usize,
87 dilation: usize,
88}
89
90impl Conv1d {
91 fn load(model: &Arc<CmfModel>, name: &str, pad: usize, dilation: usize) -> Result<Self, String> {
92 let e = model
93 .tensor(&format!("{name}.weight"))
94 .ok_or_else(|| format!("missing {name}.weight"))?;
95 let w = crate::dit::cmf_f32(model, &format!("{name}.weight"))?;
96 let b = crate::dit::cmf_f32(model, &format!("{name}.bias")).ok();
97 Ok(Self {
98 out_ch: e.shape[0],
99 in_ch: e.shape[1],
100 k: e.shape[2],
101 w,
102 b,
103 pad,
104 dilation,
105 })
106 }
107
108 fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
111 let out_n = (n + 2 * self.pad).saturating_sub(self.dilation * (self.k - 1));
112 let mut out = vec![0f32; self.out_ch * out_n];
113 let ptr = SendPtr(out.as_mut_ptr());
114 if std::env::var("CMF_AVAE_CONV_GPU").as_deref() != Ok("0")
148 && crate::gpu::enabled_here()
149 {
150 let kk = self.in_ch * self.k;
151 if let Some(col_len) = kk.checked_mul(out_n) {
152 let mut col = vec![0f32; col_len];
153 let pc = SendPtr(col.as_mut_ptr());
154 let fill = |lo: usize, hi: usize| {
155 for r in lo..hi {
156 let (i, j) = (r / self.k, r % self.k);
157 let src = &x[i * n..(i + 1) * n];
158 let dst = unsafe { pc.row(r * out_n, out_n) };
160 for (t, d) in dst.iter_mut().enumerate() {
161 let p = (t + j * self.dilation) as isize - self.pad as isize;
162 *d = if p >= 0 && (p as usize) < n {
163 src[p as usize]
164 } else {
165 0.0
166 };
167 }
168 }
169 };
170 match pool {
171 Some(p) => p.run_rows(kk, &fill),
172 None => fill(0, kk),
173 }
174 let mut colt = vec![0f32; col_len];
178 for r in 0..kk {
179 for t in 0..out_n {
180 colt[t * kk + r] = col[r * out_n + t];
181 }
182 }
183 let mut yt = vec![0f32; out_n * self.out_ch];
184 if crate::gpu::gemm_nt_f32(&colt, &self.w, &mut yt, out_n, kk, self.out_ch) {
185 for o in 0..self.out_ch {
186 let bias = self.b.as_ref().map_or(0.0, |b| b[o]);
187 let dst = unsafe { ptr.row(o * out_n, out_n) };
189 for (t, d) in dst.iter_mut().enumerate() {
190 *d = yt[t * self.out_ch + o] + bias;
191 }
192 }
193 return out;
194 }
195 }
196 }
197 let tiles = (128 / self.out_ch.max(1)).max(1);
198 let tile = out_n.div_ceil(tiles.max(1));
199 let rows = self.out_ch * tiles;
200 let work = |lo: usize, hi: usize| {
201 for r in lo..hi {
202 let o = r / tiles;
203 let t0 = (r - o * tiles) * tile;
204 if t0 >= out_n {
205 continue;
206 }
207 let len = tile.min(out_n - t0);
208 let dst = unsafe { ptr.row(o * out_n + t0, len) };
210 let bias = self.b.as_ref().map_or(0.0, |b| b[o]);
211 dst.fill(bias);
212 for i in 0..self.in_ch {
213 let ker = &self.w[(o * self.in_ch + i) * self.k..(o * self.in_ch + i + 1) * self.k];
214 let src = &x[i * n..(i + 1) * n];
215 for (tt, d) in dst.iter_mut().enumerate() {
216 let t = t0 + tt;
217 let mut acc = 0f32;
218 for (j, &kv) in ker.iter().enumerate() {
219 let p = (t + j * self.dilation) as isize - self.pad as isize;
220 if p >= 0 && (p as usize) < n {
221 acc += kv * src[p as usize];
222 }
223 }
224 *d += acc;
225 }
226 }
227 }
228 };
229 match pool {
230 Some(p) => p.run_rows(rows, &work),
231 None => work(0, rows),
232 }
233 out
234 }
235}
236
237struct ConvT1d {
238 w: Vec<f32>, b: Vec<f32>,
240 in_ch: usize,
241 out_ch: usize,
242 k: usize,
243 stride: usize,
244 pad: usize,
245}
246
247impl ConvT1d {
248 fn load(model: &Arc<CmfModel>, name: &str, stride: usize) -> Result<Self, String> {
249 let e = model
250 .tensor(&format!("{name}.weight"))
251 .ok_or_else(|| format!("missing {name}.weight"))?;
252 let (in_ch, out_ch, k) = (e.shape[0], e.shape[1], e.shape[2]);
253 Ok(Self {
254 w: crate::dit::cmf_f32(model, &format!("{name}.weight"))?,
255 b: crate::dit::cmf_f32(model, &format!("{name}.bias"))?,
256 in_ch,
257 out_ch,
258 k,
259 stride,
260 pad: (k - stride) / 2,
261 })
262 }
263
264 fn apply(&self, x: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
265 let full = (n - 1) * self.stride + self.k;
266 let out_n = full - 2 * self.pad;
267 let mut out = vec![0f32; self.out_ch * out_n];
268 let ptr = SendPtr(out.as_mut_ptr());
269 let work = |lo: usize, hi: usize| {
270 for o in lo..hi {
271 let dst = unsafe { ptr.row(o * out_n, out_n) };
273 dst.fill(self.b[o]);
274 for i in 0..self.in_ch {
275 let ker = &self.w[(i * self.out_ch + o) * self.k..(i * self.out_ch + o + 1) * self.k];
276 let src = &x[i * n..(i + 1) * n];
277 for (t, &sv) in src.iter().enumerate() {
278 if sv == 0.0 {
279 continue;
280 }
281 let base = t * self.stride;
282 for (j, &kv) in ker.iter().enumerate() {
283 let p = base + j;
284 if p >= self.pad && p - self.pad < out_n {
285 dst[p - self.pad] += sv * kv;
286 }
287 }
288 }
289 }
290 }
291 };
292 match pool {
293 Some(p) => p.run_rows(self.out_ch, &work),
294 None => work(0, self.out_ch),
295 }
296 out
297 }
298}
299
300struct SnakeBeta {
302 alpha: Vec<f32>,
303 beta: Vec<f32>,
304}
305
306impl SnakeBeta {
307 fn load(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
308 Ok(Self {
309 alpha: crate::dit::cmf_f32(model, &format!("{name}.alpha"))?
310 .iter()
311 .map(|v| v.exp())
312 .collect(),
313 beta: crate::dit::cmf_f32(model, &format!("{name}.beta"))?
314 .iter()
315 .map(|v| v.exp())
316 .collect(),
317 })
318 }
319
320 fn apply(&self, x: &mut [f32], n: usize) {
321 for (c, row) in x.chunks_exact_mut(n).enumerate() {
322 let (a, b) = (self.alpha[c], 1.0 / (self.beta[c] + 1e-9));
323 for v in row.iter_mut() {
324 let s = (a * *v).sin();
325 *v += s * s * b;
326 }
327 }
328 }
329}
330
331fn bessel_i0(x: f64) -> f64 {
332 let mut sum = 1.0;
335 let mut term = 1.0;
336 for k in 1..40 {
337 term *= (x / (2.0 * k as f64)).powi(2);
338 sum += term;
339 if term < 1e-18 * sum {
340 break;
341 }
342 }
343 sum
344}
345
346fn sinc(x: f64) -> f64 {
347 if x == 0.0 {
348 1.0
349 } else {
350 (std::f64::consts::PI * x).sin() / (std::f64::consts::PI * x)
351 }
352}
353
354fn kaiser_sinc(cutoff: f64, half_width: f64, k: usize) -> Vec<f32> {
356 let half = k / 2;
357 let delta_f = 4.0 * half_width;
358 let a = 2.285 * (half as f64 - 1.0) * std::f64::consts::PI * delta_f + 7.95;
359 let beta = if a > 50.0 {
360 0.1102 * (a - 8.7)
361 } else if a >= 21.0 {
362 0.5842 * (a - 21.0).powf(0.4) + 0.078_86 * (a - 21.0)
363 } else {
364 0.0
365 };
366 let denom = bessel_i0(beta);
367 let n = k as f64 - 1.0;
368 let mut f: Vec<f64> = (0..k)
369 .map(|i| {
370 let r = (2.0 * i as f64 / n) - 1.0;
371 let win = bessel_i0(beta * (1.0 - r * r).max(0.0).sqrt()) / denom;
372 let t = -(half as f64) + i as f64 + 0.5;
374 2.0 * cutoff * win * sinc(2.0 * cutoff * t)
375 })
376 .collect();
377 let s: f64 = f.iter().sum();
378 for v in f.iter_mut() {
379 *v /= s;
380 }
381 f.into_iter().map(|v| v as f32).collect()
382}
383
384#[allow(clippy::too_many_arguments)]
409fn fir_pad(
410 x: &[f32],
411 ch: usize,
412 n: usize,
413 f: &[f32],
414 pad_l: usize,
415 pad_r: usize,
416 stride: usize,
417 pool: Option<&Pool>,
418) -> (Vec<f32>, usize) {
419 let padded = n + pad_l + pad_r;
420 let out_n = (padded - f.len()) / stride + 1;
421 let mut out = vec![0f32; ch * out_n];
422 struct P(*mut f32);
423 unsafe impl Send for P {}
426 unsafe impl Sync for P {}
427 impl P {
428 #[allow(clippy::mut_from_ref)]
432 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
433 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
434 }
435 }
436 let po = P(out.as_mut_ptr());
437 let work = |lo: usize, hi: usize| {
438 let mut buf = vec![0f32; padded];
441 for c in lo..hi {
442 let src = &x[c * n..(c + 1) * n];
443 for (i, b) in buf.iter_mut().enumerate() {
444 let p = i as isize - pad_l as isize;
445 *b = src[p.clamp(0, n as isize - 1) as usize];
446 }
447 let dst = unsafe { po.row(c * out_n, out_n) };
448 for (t, d) in dst.iter_mut().enumerate() {
449 let mut acc = 0f32;
450 for (j, &kv) in f.iter().enumerate() {
451 acc += kv * buf[t * stride + j];
452 }
453 *d = acc;
454 }
455 }
456 };
457 match pool {
458 Some(p) => p.run_rows(ch, &work),
459 None => work(0, ch),
460 }
461 (out, out_n)
462}
463
464struct Activation1d {
466 act: SnakeBeta,
467 up: Vec<f32>,
468 down: Vec<f32>,
469}
470
471impl Activation1d {
472 fn load(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
477 let designed = || kaiser_sinc(0.25, 0.3, FILTER_LEN);
478 Ok(Self {
479 act: SnakeBeta::load(model, &format!("{name}.act"))?,
480 up: crate::dit::cmf_f32(model, &format!("{name}.upsample.filter"))
481 .unwrap_or_else(|_| designed()),
482 down: crate::dit::cmf_f32(model, &format!("{name}.downsample.lowpass.filter"))
483 .unwrap_or_else(|_| designed()),
484 })
485 }
486
487 fn apply(&self, x: &[f32], ch: usize, n: usize, pool: Option<&Pool>) -> (Vec<f32>, usize) {
488 let pad = FILTER_LEN / 2 - 1;
491 let pad_l = pad * 2 + (FILTER_LEN - 2) / 2;
492 let pad_r = pad * 2 + (FILTER_LEN - 2 + 1) / 2;
493 let pn = n + 2 * pad;
494 let full = (pn - 1) * 2 + FILTER_LEN;
495 let mut up = vec![0f32; ch * full];
496 for c in 0..ch {
497 let src = &x[c * n..(c + 1) * n];
498 let dst = &mut up[c * full..(c + 1) * full];
499 for i in 0..pn {
500 let p = i as isize - pad as isize;
501 let v = src[p.clamp(0, n as isize - 1) as usize] * 2.0;
502 if v == 0.0 {
503 continue;
504 }
505 for (j, &kv) in self.up.iter().enumerate() {
506 dst[i * 2 + j] += v * kv;
507 }
508 }
509 }
510 let keep = full - pad_l - pad_r;
511 let mut mid = vec![0f32; ch * keep];
512 for c in 0..ch {
513 mid[c * keep..(c + 1) * keep]
514 .copy_from_slice(&up[c * full + pad_l..c * full + pad_l + keep]);
515 }
516 self.act.apply(&mut mid, keep);
517 fir_pad(&mid, ch, keep, &self.down, FILTER_LEN / 2 - 1, FILTER_LEN / 2, 2, pool)
519 }
520}
521
522struct AmpBlock {
523 convs1: Vec<Conv1d>,
524 convs2: Vec<Conv1d>,
525 acts: Vec<Activation1d>,
526}
527
528pub struct AudioVae {
529 dec_in: Conv1d,
530 conv_pre: Conv1d,
531 ups: Vec<ConvT1d>,
532 resblocks: Vec<AmpBlock>,
533 act_post: Activation1d,
534 conv_post: Conv1d,
535 latents_mean: Vec<f32>,
536 latents_std: Vec<f32>,
537 pool: Option<Arc<Pool>>,
538 n_kernels: usize,
539 pub sample_rate: usize,
540}
541
542fn get_padding(k: usize, d: usize) -> usize {
543 (k * d - d) / 2
544}
545
546impl AudioVae {
547 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
548 let cfg: serde_json::Value = serde_json::from_slice(
549 model.tensor_bytes("avae.config_json").map_err(|e| e.to_string())?,
550 )
551 .map_err(|e| format!("avae.config_json: {e}"))?;
552 let rates: Vec<usize> = cfg["upsample_rates"]
553 .as_array()
554 .ok_or("upsample_rates")?
555 .iter()
556 .map(|v| v.as_u64().unwrap_or(1) as usize)
557 .collect();
558 let rk: Vec<usize> = cfg["resblock_kernel_sizes"]
559 .as_array()
560 .ok_or("resblock_kernel_sizes")?
561 .iter()
562 .map(|v| v.as_u64().unwrap_or(3) as usize)
563 .collect();
564 let rd: Vec<Vec<usize>> = cfg["resblock_dilation_sizes"]
565 .as_array()
566 .ok_or("resblock_dilation_sizes")?
567 .iter()
568 .map(|a| {
569 a.as_array()
570 .unwrap()
571 .iter()
572 .map(|v| v.as_u64().unwrap_or(1) as usize)
573 .collect()
574 })
575 .collect();
576
577 let mut ups = Vec::new();
578 for (i, &u) in rates.iter().enumerate() {
579 ups.push(ConvT1d::load(model, &format!("avae.decoder.ups.{i}.0"), u)?);
580 }
581 let mut resblocks = Vec::new();
582 for i in 0..rates.len() {
583 for (j, (&k, d)) in rk.iter().zip(&rd).enumerate() {
584 let p = format!("avae.decoder.resblocks.{}", i * rk.len() + j);
585 let convs1 = (0..d.len())
586 .map(|q| Conv1d::load(model, &format!("{p}.convs1.{q}"), get_padding(k, d[q]), d[q]))
587 .collect::<Result<Vec<_>, _>>()?;
588 let convs2 = (0..d.len())
589 .map(|q| Conv1d::load(model, &format!("{p}.convs2.{q}"), get_padding(k, 1), 1))
590 .collect::<Result<Vec<_>, _>>()?;
591 let acts = (0..convs1.len() + convs2.len())
592 .map(|q| Activation1d::load(model, &format!("{p}.activations.{q}")))
593 .collect::<Result<Vec<_>, _>>()?;
594 resblocks.push(AmpBlock { convs1, convs2, acts });
595 }
596 }
597 Ok(Self {
598 dec_in: Conv1d::load(model, "avae.dec_in_proj", 0, 1)?,
599 conv_pre: Conv1d::load(model, "avae.decoder.conv_pre", 3, 1)?,
600 ups,
601 resblocks,
602 act_post: Activation1d::load(model, "avae.decoder.activation_post")?,
603 conv_post: Conv1d::load(model, "avae.decoder.conv_post", 3, 1)?,
604 latents_mean: crate::dit::cmf_f32(model, "avae.latents_mean")?,
605 latents_std: crate::dit::cmf_f32(model, "avae.latents_std")?,
606 pool: Pool::from_env(),
607 n_kernels: rk.len(),
608 sample_rate: cfg["sample_rate"].as_u64().unwrap_or(32000) as usize,
609 })
610 }
611
612 pub fn decode(&self, z: &[f32], c: usize, t: usize) -> (Vec<f32>, usize) {
639 let pool = self.pool.as_deref();
640 let mut chans: Vec<Vec<f32>> = Vec::with_capacity(2);
641 for ch in 0..2 {
642 let mut lat = vec![0f32; c * t];
643 for ci in 0..c {
644 let (m, s) = (self.latents_mean[ci], self.latents_std[ci]);
645 for ti in 0..t {
646 lat[ci * t + ti] = z[(ci * 2 + ch) * t + ti] * s + m;
647 }
648 }
649 let prof = std::env::var_os("CMF_AVAE_PROF").is_some();
652 let rms = |x: &[f32]| (x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>()
653 / x.len() as f64)
654 .sqrt();
655 let tt = std::time::Instant::now();
656 let mut x = self.dec_in.apply(&lat, t, pool);
657 atime(0, tt);
658 let mut n = t;
659 if prof {
660 eprintln!("ch{ch} dec_in rms {:.6e} n {n}", rms(&x));
661 }
662 let tt = std::time::Instant::now();
663 x = self.conv_pre.apply(&x, n, pool);
664 atime(1, tt);
665 if prof {
666 eprintln!("ch{ch} conv_pre rms {:.6e} n {n}", rms(&x));
667 }
668 for i in 0..self.ups.len() {
669 let up = &self.ups[i];
670 let tt = std::time::Instant::now();
671 x = up.apply(&x, n, pool);
672 atime(2, tt);
673 n = (n - 1) * up.stride + up.k - 2 * up.pad;
674 let ch_n = up.out_ch;
675 let mut acc = vec![0f32; ch_n * n];
676 for j in 0..self.n_kernels {
677 let tt = std::time::Instant::now();
678 let r = self.resblocks[i * self.n_kernels + j].apply(&x, ch_n, n, pool);
679 atime(3, tt);
680 for (a, b) in acc.iter_mut().zip(&r) {
681 *a += b;
682 }
683 }
684 let inv = 1.0 / self.n_kernels as f32;
685 for v in acc.iter_mut() {
686 *v *= inv;
687 }
688 x = acc;
689 if prof {
690 eprintln!("ch{ch} up{i} rms {:.6e} ch {ch_n} n {n}", rms(&x));
691 }
692 }
693 let last_ch = self.ups[self.ups.len() - 1].out_ch;
694 let tt = std::time::Instant::now();
695 let (mut y, yn) = self.act_post.apply(&x, last_ch, n, pool);
696 atime(4, tt);
697 let tt = std::time::Instant::now();
698 y = self.conv_post.apply(&y, yn, pool);
699 atime(5, tt);
700 for v in y.iter_mut() {
701 *v = v.clamp(-1.0, 1.0);
702 }
703 chans.push(y);
704 n = yn;
705 let _ = n;
706 }
707 let len = chans[0].len().min(chans[1].len());
708 let mut out = vec![0f32; 2 * len];
709 for (ch, c) in chans.iter().enumerate() {
710 out[ch * len..(ch + 1) * len].copy_from_slice(&c[..len]);
711 }
712 (out, len)
713 }
714}
715
716impl AmpBlock {
717 fn apply(&self, x: &[f32], ch: usize, n: usize, pool: Option<&Pool>) -> Vec<f32> {
718 let mut cur = x.to_vec();
719 for i in 0..self.convs1.len() {
720 let (a1, a2) = (&self.acts[i * 2], &self.acts[i * 2 + 1]);
721 let (xt, tn) = a1.apply(&cur, ch, n, pool);
722 let xt = self.convs1[i].apply(&xt, tn, pool);
723 let (xt, tn2) = a2.apply(&xt, ch, tn, pool);
724 let xt = self.convs2[i].apply(&xt, tn2, pool);
725 for (a, b) in cur.iter_mut().zip(&xt) {
726 *a += b;
727 }
728 }
729 cur
730 }
731}
732
733#[doc(hidden)]
735pub fn kaiser_sinc_for_test() -> Vec<f32> {
736 kaiser_sinc(0.25, 0.3, FILTER_LEN)
737}
738
739struct SendPtr(*mut f32);
740unsafe impl Send for SendPtr {}
741unsafe impl Sync for SendPtr {}
742impl SendPtr {
743 #[allow(clippy::mut_from_ref)]
745 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
746 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
747 }
748}
749
750#[cfg(test)]
751mod tests {
752 use super::*;
753
754 #[test]
755 fn the_resampling_filter_is_the_references() {
756 let f = kaiser_sinc(0.25, 0.3, FILTER_LEN);
757 assert_eq!(f.len(), FILTER_LEN);
758 assert!((f.iter().sum::<f32>() - 1.0).abs() < 1e-6);
761 for i in 0..FILTER_LEN / 2 {
763 assert!((f[i] - f[FILTER_LEN - 1 - i]).abs() < 1e-6, "asymmetric at {i}");
764 }
765 let peak = f.iter().cloned().fold(f32::MIN, f32::max);
766 assert!((f[5] - peak).abs() < 1e-6);
767
768 }
769
770 #[test]
771 fn bessel_i0_matches_known_values() {
772 for (x, want) in [(0.0, 1.0), (1.0, 1.266_065_878), (4.664, 20.204_6)] {
775 let got = bessel_i0(x);
776 assert!((got - want).abs() < 1e-3 * want.max(1.0), "I0({x}) = {got}");
777 }
778 }
779}