1use crate::lfm3_vl::{Dense, add_bias, load_proj, load_vec};
15use crate::pipeline::Pipeline;
16use crate::pool::Pool;
17use crate::sampler::SplitMix64;
18use cortiq_core::CmfModel;
19use std::sync::Arc;
20
21pub const N_CODEBOOKS: usize = 12;
24pub const CODEBOOK_SIZE: usize = 2017;
25pub const AUDIO_EOS: u32 = 2016;
26const MASKGIT: [usize; 3] = [4, 4, 4];
28pub const FRAME_SAMPLES: usize = 1920;
30
31fn tensor_rows(model: &CmfModel, name: &str) -> Option<usize> {
32 model.tensors.iter().find(|t| t.name == name).and_then(|t| t.shape.first().copied())
33}
34
35fn has_tensor(model: &CmfModel, name: &str) -> bool {
36 model.tensors.iter().any(|t| t.name == name)
37}
38
39enum Mixer {
42 Conv { in_proj: Dense, taps: Vec<f32>, out_proj: Dense },
43 Attn { q: Dense, k: Dense, v: Dense, o: Dense, q_norm: Vec<f32>, k_norm: Vec<f32>, gate: Option<Dense> },
44}
45
46struct Layer {
47 op_norm: Vec<f32>,
48 post_op: Option<Vec<f32>>,
49 ffn_norm: Vec<f32>,
50 post_ffn: Option<Vec<f32>>,
51 mixer: Mixer,
52 w1: Dense,
53 w3: Dense,
54 w2: Dense,
55}
56
57struct Stack {
62 layers: Vec<Layer>,
63 final_norm: Vec<f32>,
64 d: usize,
65 heads: usize,
66 kv: usize,
67 hd: usize,
68 conv_k: usize,
69 eps: f64,
70 inv: Vec<f32>,
71 window: Option<usize>,
73}
74
75fn rms(x: &[f32], w: &[f32], eps: f64, out: &mut [f32]) {
76 let d = w.len();
77 for (src, dst) in x.chunks_exact(d).zip(out.chunks_exact_mut(d)) {
78 let ss = src.iter().map(|&v| v as f64 * v as f64).sum::<f64>() / d as f64;
79 let inv = 1.0 / (ss + eps).sqrt();
80 for ((o, &v), &g) in dst.iter_mut().zip(src).zip(w) {
81 *o = (v as f64 * inv) as f32 * g;
82 }
83 }
84}
85
86impl Stack {
87 #[allow(clippy::too_many_arguments)]
88 fn load(
89 model: &Arc<CmfModel>,
90 prefix: &str,
91 d: usize,
92 heads: usize,
93 kv: usize,
94 eps: f64,
95 theta: f64,
96 window: Option<usize>,
97 ) -> Result<Self, String> {
98 let hd = d / heads;
99 let mut layers = Vec::new();
100 let mut conv_k = 1;
101 for i in 0.. {
102 let p = format!("{prefix}.layers.{i}");
103 let conv = has_tensor(model, &format!("{p}.conv.in_proj.weight"));
104 let attn = has_tensor(model, &format!("{p}.self_attn.q_proj.weight"));
105 if !conv && !attn {
106 break;
107 }
108 let v = |n: &str, len: usize| load_vec(model, &format!("{p}.{n}"), len);
109 let opt = |n: &str| {
110 has_tensor(model, &format!("{p}.{n}")).then(|| v(n, d)).transpose()
111 };
112 let mixer = if conv {
113 let taps_len = model
114 .tensors
115 .iter()
116 .find(|t| t.name == format!("{p}.conv.conv.weight"))
117 .map(|t| t.shape.iter().product::<usize>())
118 .ok_or("lfm stack: conv taps missing")?;
119 conv_k = taps_len / d;
120 Mixer::Conv {
121 in_proj: load_proj(model, &format!("{p}.conv.in_proj.weight"), 3 * d, d)?,
122 taps: v("conv.conv.weight", taps_len)?,
123 out_proj: load_proj(model, &format!("{p}.conv.out_proj.weight"), d, d)?,
124 }
125 } else {
126 let gate_name = format!("{p}.self_attn.gate_proj.weight");
127 Mixer::Attn {
128 q: load_proj(model, &format!("{p}.self_attn.q_proj.weight"), heads * hd, d)?,
129 k: load_proj(model, &format!("{p}.self_attn.k_proj.weight"), kv * hd, d)?,
130 v: load_proj(model, &format!("{p}.self_attn.v_proj.weight"), kv * hd, d)?,
131 o: load_proj(model, &format!("{p}.self_attn.o_proj.weight"), d, heads * hd)?,
132 q_norm: v("self_attn.q_norm.weight", hd)?,
133 k_norm: v("self_attn.k_norm.weight", hd)?,
134 gate: has_tensor(model, &gate_name)
135 .then(|| load_proj(model, &gate_name, heads, d))
136 .transpose()?,
137 }
138 };
139 let inter = tensor_rows(model, &format!("{p}.feed_forward.w1.weight")).ok_or("lfm stack: FFN missing")?;
140 layers.push(Layer {
141 op_norm: v("operator_norm.weight", d)?,
142 post_op: opt("post_operator_norm.weight")?,
143 ffn_norm: v("ffn_norm.weight", d)?,
144 post_ffn: opt("post_ffn_norm.weight")?,
145 mixer,
146 w1: load_proj(model, &format!("{p}.feed_forward.w1.weight"), inter, d)?,
147 w3: load_proj(model, &format!("{p}.feed_forward.w3.weight"), inter, d)?,
148 w2: load_proj(model, &format!("{p}.feed_forward.w2.weight"), d, inter)?,
149 });
150 }
151 if layers.is_empty() {
152 return Err(format!("lfm stack: no layers under {prefix}"));
153 }
154 let inv = (0..hd / 2)
155 .map(|i| (1.0 / theta.powf((2 * i) as f64 / hd as f64)) as f32)
156 .collect();
157 Ok(Self {
158 layers,
159 final_norm: load_vec(model, &format!("{prefix}.embedding_norm.weight"), d)?,
160 d,
161 heads,
162 kv,
163 hd,
164 conv_k,
165 eps,
166 inv,
167 window,
168 })
169 }
170
171 fn forward(&self, x: &mut [f32], pool: Option<&Pool>) {
173 let (d, hd) = (self.d, self.hd);
174 let n = x.len() / d;
175 let mut xn = vec![0f32; n * d];
176 let mut mix = vec![0f32; n * d];
177 let mut tmp = vec![0f32; n * d];
178 for l in &self.layers {
179 rms(x, &l.op_norm, self.eps, &mut xn);
180 match &l.mixer {
181 Mixer::Conv { in_proj, taps, out_proj } => {
182 let k = self.conv_k;
183 let mut bcx = vec![0f32; n * 3 * d];
184 in_proj.matmat(&xn, n, &mut bcx, pool);
185 let mut y = vec![0f32; n * d];
186 for t in 0..n {
187 for j in 0..d {
188 let mut acc = 0f32;
189 for tap in 0..k {
190 let back = k - 1 - tap;
191 if t >= back {
192 let r = &bcx[(t - back) * 3 * d..];
193 acc += taps[j * k + tap] * (r[j] * r[2 * d + j]);
194 }
195 }
196 y[t * d + j] = bcx[t * 3 * d + d + j] * acc;
197 }
198 }
199 out_proj.matmat(&y, n, &mut mix, pool);
200 }
201 Mixer::Attn { q, k, v, o, q_norm, k_norm, gate } => {
202 let (qd, kvd) = (self.heads * hd, self.kv * hd);
203 let mut qb = vec![0f32; n * qd];
204 let mut kb = vec![0f32; n * kvd];
205 let mut vb = vec![0f32; n * kvd];
206 q.matmat(&xn, n, &mut qb, pool);
207 k.matmat(&xn, n, &mut kb, pool);
208 v.matmat(&xn, n, &mut vb, pool);
209 let mut head = vec![0f32; hd];
210 for (buf, heads, w) in [(&mut qb, self.heads, q_norm), (&mut kb, self.kv, k_norm)] {
211 for t in 0..n {
212 for hh in 0..heads {
213 let hv = &mut buf[(t * heads + hh) * hd..][..hd];
214 rms(hv, w, self.eps, &mut head);
215 hv.copy_from_slice(&head);
216 crate::attention::rope_rotate_scaled(hv, t, &self.inv, 1.0);
217 }
218 }
219 }
220 let group = self.heads / self.kv.max(1);
221 let scale = (hd as f32).powf(-0.5);
222 let mut ao = vec![0f32; n * qd];
223 let mut sc = Vec::new();
224 for t in 0..n {
225 let (lo, hi) = match self.window {
226 Some(w) => ((t + 1).saturating_sub(w), t + 1),
227 None => (0, n),
228 };
229 for hh in 0..self.heads {
230 let g = hh / group;
231 let qv = &qb[(t * self.heads + hh) * hd..][..hd];
232 sc.clear();
233 for j in lo..hi {
234 let kv = &kb[(j * self.kv + g) * hd..][..hd];
235 sc.push(qv.iter().zip(kv).map(|(a, b)| a * b).sum::<f32>() * scale);
236 }
237 let mx = sc.iter().fold(f32::NEG_INFINITY, |m, &s| m.max(s));
238 let mut sum = 0f32;
239 for s in sc.iter_mut() {
240 *s = (*s - mx).exp();
241 sum += *s;
242 }
243 let out = &mut ao[(t * self.heads + hh) * hd..][..hd];
244 for (j, &p) in (lo..hi).zip(sc.iter()) {
245 let vv = &vb[(j * self.kv + g) * hd..][..hd];
246 for (od, &x) in out.iter_mut().zip(vv) {
247 *od += p / sum * x;
248 }
249 }
250 }
251 }
252 if let Some(g) = gate {
253 let mut raw = vec![0f32; n * self.heads];
254 g.matmat(&xn, n, &mut raw, pool);
255 for t in 0..n {
256 for hh in 0..self.heads {
257 let s = 1.0 / (1.0 + (-raw[t * self.heads + hh]).exp());
258 for a in &mut ao[(t * self.heads + hh) * hd..][..hd] {
259 *a *= s;
260 }
261 }
262 }
263 }
264 o.matmat(&ao, n, &mut mix, pool);
265 }
266 }
267 if let Some(w) = &l.post_op {
268 rms(&mix, w, self.eps, &mut tmp);
269 std::mem::swap(&mut mix, &mut tmp);
270 }
271 for (a, &b) in x.iter_mut().zip(&mix) {
272 *a += b;
273 }
274 rms(x, &l.ffn_norm, self.eps, &mut xn);
275 let inter = l.w1.rows;
276 let mut g = vec![0f32; n * inter];
277 let mut u = vec![0f32; n * inter];
278 l.w1.matmat(&xn, n, &mut g, pool);
279 l.w3.matmat(&xn, n, &mut u, pool);
280 for (a, &b) in g.iter_mut().zip(&u) {
281 *a = *a / (1.0 + (-*a).exp()) * b;
282 }
283 l.w2.matmat(&g, n, &mut mix, pool);
284 if let Some(w) = &l.post_ffn {
285 rms(&mix, w, self.eps, &mut tmp);
286 std::mem::swap(&mut mix, &mut tmp);
287 }
288 for (a, &b) in x.iter_mut().zip(&mix) {
289 *a += b;
290 }
291 }
292 rms(x, &self.final_norm, self.eps, &mut xn);
293 x.copy_from_slice(&xn);
294 }
295}
296
297#[derive(Clone, Debug)]
302pub struct AudioSampling {
303 pub temperature: f32,
304 pub top_k: usize,
305 pub repetition_penalty: f32,
306 pub repetition_window: usize,
307 pub fixed_order: bool,
310}
311
312impl Default for AudioSampling {
313 fn default() -> Self {
314 Self { temperature: 1.0, top_k: 128, repetition_penalty: 1.1, repetition_window: 4096, fixed_order: false }
315 }
316}
317
318impl AudioSampling {
319 pub fn from_model(model: &CmfModel) -> Self {
323 let mut s = Self::default();
324 let g = model
325 .header
326 .provenance
327 .as_ref()
328 .and_then(|p| p.get("lfm3_audio"))
329 .and_then(|a| a.get("generation"));
330 if let Some(g) = g {
331 if let Some(t) = g.get("audio_temperature").and_then(|v| v.as_f64()) {
332 s.temperature = t as f32;
333 }
334 match g.get("audio_top_k") {
335 Some(v) if v.is_null() => s.top_k = 0,
336 Some(v) => s.top_k = v.as_u64().map_or(s.top_k, |k| k as usize),
337 None => {}
338 }
339 if let Some(r) = g.get("audio_repetition_penalty").and_then(|v| v.as_f64()) {
340 s.repetition_penalty = r as f32;
341 }
342 if let Some(w) = g.get("audio_repetition_window").and_then(|v| v.as_u64()) {
343 s.repetition_window = w as usize;
344 }
345 }
346 s
347 }
348}
349
350pub struct DepthDecoder {
352 linear: (Dense, Vec<f32>),
353 mask: Vec<f32>,
354 emb: Vec<f32>,
356 emb_norm: Vec<f32>,
357 stack: Stack,
358 d: usize,
359 codebook: Vec<f32>,
361 text_hidden: usize,
362 pool: Option<Arc<Pool>>,
363}
364
365impl DepthDecoder {
366 pub fn from_model(model: &Arc<CmfModel>, pool: Option<Arc<Pool>>) -> Result<Self, String> {
367 let text_hidden = model
368 .tensors
369 .iter()
370 .find(|t| t.name == "codebook_embedding.weight")
371 .and_then(|t| t.shape.get(1).copied())
372 .ok_or("lfm3_speech: codebook_embedding missing")?;
373 let d = tensor_rows(model, "depth_decoder.mask_embedding").ok_or("lfm3_speech: mask_embedding missing")?;
374 let mut emb = Vec::with_capacity(N_CODEBOOKS * CODEBOOK_SIZE * d);
375 let mut emb_norm = Vec::with_capacity(N_CODEBOOKS * d);
376 for i in 0..N_CODEBOOKS {
377 let p = format!("depth_decoder.depth_embeddings.{i}");
378 emb.extend(load_vec(model, &format!("{p}.embedding.weight"), CODEBOOK_SIZE * d)?);
379 emb_norm.extend(load_vec(model, &format!("{p}.embedding_norm.weight"), d)?);
380 }
381 let q_rows = tensor_rows(model, "depth_decoder.model.layers.0.self_attn.q_proj.weight").unwrap_or(d);
383 let k_rows = tensor_rows(model, "depth_decoder.model.layers.0.self_attn.k_proj.weight").unwrap_or(d);
384 let hd = model
385 .tensors
386 .iter()
387 .find(|t| t.name == "depth_decoder.model.layers.0.self_attn.q_norm.weight")
388 .and_then(|t| t.shape.first().copied())
389 .ok_or("lfm3_speech: depth q_norm missing")?;
390 let stack = Stack::load(model, "depth_decoder.model", d, q_rows / hd, k_rows / hd, 1e-5, 1e6, None)?;
391 Ok(Self {
392 linear: (
393 load_proj(model, "depth_decoder.depth_linear.weight", N_CODEBOOKS * d, text_hidden)?,
394 load_vec(model, "depth_decoder.depth_linear.bias", N_CODEBOOKS * d)?,
395 ),
396 mask: load_vec(model, "depth_decoder.mask_embedding", d)?,
397 emb,
398 emb_norm,
399 stack,
400 d,
401 codebook: load_vec(model, "codebook_embedding.weight", N_CODEBOOKS * CODEBOOK_SIZE * text_hidden)?,
402 text_hidden,
403 pool,
404 })
405 }
406
407 fn slots(&self, h: &[f32]) -> Vec<f32> {
410 let mut x = vec![0f32; N_CODEBOOKS * self.d];
411 self.linear.0.matmat(h, 1, &mut x, self.pool.as_deref());
412 add_bias(&mut x, &self.linear.1);
413 x
414 }
415
416 fn logits(
419 &self,
420 slots: &[f32],
421 codes: &[u32; N_CODEBOOKS],
422 masked: &[bool; N_CODEBOOKS],
423 want: &[usize],
424 ) -> Vec<f32> {
425 let pool = self.pool.as_deref();
426 let d = self.d;
427 let mut x = slots.to_vec();
428 for i in 0..N_CODEBOOKS {
429 let add = if masked[i] {
430 &self.mask[..]
431 } else {
432 &self.emb[(i * CODEBOOK_SIZE + codes[i] as usize) * d..][..d]
433 };
434 for (a, &b) in x[i * d..(i + 1) * d].iter_mut().zip(add) {
435 *a += b;
436 }
437 }
438 self.stack.forward(&mut x, pool);
439 let mut out = vec![0f32; want.len() * CODEBOOK_SIZE];
440 let mut normed = vec![0f32; d];
441 for (r, &i) in want.iter().enumerate() {
442 rms(&x[i * d..(i + 1) * d], &self.emb_norm[i * d..(i + 1) * d], 1e-5, &mut normed);
443 let table = &self.emb[i * CODEBOOK_SIZE * d..(i + 1) * CODEBOOK_SIZE * d];
444 let row = &mut out[r * CODEBOOK_SIZE..(r + 1) * CODEBOOK_SIZE];
445 crate::fcd_ops::gemm_nt_host(&normed, table, row, 1, d, CODEBOOK_SIZE, pool);
446 }
447 out
448 }
449
450 pub fn sample_frame(
454 &self,
455 h: &[f32],
456 s: &AudioSampling,
457 history: &[[u32; N_CODEBOOKS]],
458 rng: &mut SplitMix64,
459 ) -> [u32; N_CODEBOOKS] {
460 let mut order: Vec<usize> = (0..N_CODEBOOKS).collect();
461 if !s.fixed_order {
462 for i in (1..N_CODEBOOKS).rev() {
463 let j = (rng.next_u64() % (i as u64 + 1)) as usize;
464 order.swap(i, j);
465 }
466 }
467 let mut codes = [0u32; N_CODEBOOKS];
468 let mut masked = [true; N_CODEBOOKS];
469 let hist = &history[history.len().saturating_sub(s.repetition_window)..];
470 let slots = self.slots(h);
471 let mut at = 0;
472 for step in MASKGIT {
473 let pick = &order[at..at + step];
474 let logits = self.logits(&slots, &codes, &masked, pick);
475 for (r, &cb) in pick.iter().enumerate() {
476 let mut row = logits[r * CODEBOOK_SIZE..(r + 1) * CODEBOOK_SIZE].to_vec();
477 if s.repetition_penalty != 1.0 {
478 let mut seen = vec![false; CODEBOOK_SIZE];
479 for f in hist {
480 seen[f[cb] as usize] = true;
481 }
482 for (v, &was) in row.iter_mut().zip(&seen) {
483 if was {
484 *v = if *v < 0.0 { *v * s.repetition_penalty } else { *v / s.repetition_penalty };
485 }
486 }
487 }
488 codes[cb] = sample(&row, s.temperature, s.top_k, rng);
489 }
490 for &cb in &order[at..at + step] {
491 masked[cb] = false;
492 }
493 at += step;
494 }
495 codes
496 }
497
498 pub fn frame_row(&self, codes: &[u32; N_CODEBOOKS]) -> Vec<f32> {
501 let th = self.text_hidden;
502 let mut row = vec![0f32; th];
503 for (i, &c) in codes.iter().enumerate() {
504 let e = &self.codebook[(i * CODEBOOK_SIZE + c as usize) * th..][..th];
505 for (a, &b) in row.iter_mut().zip(e) {
506 *a += b;
507 }
508 }
509 row
510 }
511}
512
513fn sample(logits: &[f32], temperature: f32, top_k: usize, rng: &mut SplitMix64) -> u32 {
515 if temperature <= 0.0 {
516 return logits
517 .iter()
518 .enumerate()
519 .fold((0, f32::NEG_INFINITY), |b, (i, &v)| if v > b.1 { (i, v) } else { b })
520 .0 as u32;
521 }
522 let mut idx: Vec<usize> = (0..logits.len()).collect();
523 if top_k > 0 && top_k < idx.len() {
524 idx.select_nth_unstable_by(top_k - 1, |&a, &b| logits[b].total_cmp(&logits[a]));
525 idx.truncate(top_k);
526 }
527 let mx = idx.iter().map(|&i| logits[i] / temperature).fold(f32::NEG_INFINITY, f32::max);
528 let w: Vec<f64> = idx.iter().map(|&i| ((logits[i] / temperature - mx) as f64).exp()).collect();
529 let total: f64 = w.iter().sum();
530 let mut r = rng.next_f32() as f64 * total;
531 for (&i, &wi) in idx.iter().zip(&w) {
532 r -= wi;
533 if r <= 0.0 {
534 return i as u32;
535 }
536 }
537 *idx.last().unwrap() as u32
538}
539
540pub struct Codec {
544 project_out: (Dense, Vec<f32>),
545 from_q: (Dense, Vec<f32>),
546 stack: Stack,
547 lin: (Dense, Vec<f32>),
548 window: Vec<f32>,
549 pool: Option<Arc<Pool>>,
550}
551
552const FSQ_LEVELS: [u32; 4] = [8, 7, 6, 6];
553const FRAME_STACK: usize = 6;
554const N_FFT: usize = 1280;
555const HOP: usize = 320;
556
557impl Codec {
558 pub fn from_model(model: &Arc<CmfModel>, pool: Option<Arc<Pool>>) -> Result<Self, String> {
559 let qd = tensor_rows(model, "codec.quantizer.project_out.weight").ok_or("lfm3_speech: the file has no codec")?;
560 let ed = tensor_rows(model, "codec.from_quantizer.weight").ok_or("lfm3_speech: codec from_quantizer missing")?;
561 let lat = N_CODEBOOKS * FSQ_LEVELS.len();
562 let bins2 = tensor_rows(model, "codec.decoder.lin.weight").ok_or("lfm3_speech: codec lin missing")?;
563 if bins2 != 2 * (N_FFT / 2 + 1) {
564 return Err("lfm3_speech: codec spectrum width differs".into());
565 }
566 let hd = tensor_rows(model, "codec.decoder.lfm.layers.2.self_attn.q_norm.weight").unwrap_or(64);
567 let heads = tensor_rows(model, "codec.decoder.lfm.layers.2.self_attn.q_proj.weight").unwrap_or(ed) / hd;
568 let kv = tensor_rows(model, "codec.decoder.lfm.layers.2.self_attn.k_proj.weight").unwrap_or(ed) / hd;
569 Ok(Self {
570 project_out: (
571 load_proj(model, "codec.quantizer.project_out.weight", qd, lat)?,
572 load_vec(model, "codec.quantizer.project_out.bias", qd)?,
573 ),
574 from_q: (
575 load_proj(model, "codec.from_quantizer.weight", ed, qd)?,
576 load_vec(model, "codec.from_quantizer.bias", ed)?,
577 ),
578 stack: Stack::load(model, "codec.decoder.lfm", ed, heads, kv, 1e-5, 1e6, Some(30))?,
579 lin: (
580 load_proj(model, "codec.decoder.lin.weight", bins2, ed)?,
581 load_vec(model, "codec.decoder.lin.bias", bins2)?,
582 ),
583 window: load_vec(model, "codec.decoder.istft.window", N_FFT)?,
584 pool,
585 })
586 }
587
588 pub fn decode(&self, frames: &[[u32; N_CODEBOOKS]]) -> Vec<f32> {
590 use rustfft::{FftPlanner, num_complex::Complex};
591 let pool = self.pool.as_deref();
592 let nf = frames.len();
593 if nf == 0 {
594 return Vec::new();
595 }
596 let lat_w = N_CODEBOOKS * FSQ_LEVELS.len();
597 let mut lat = vec![0f32; nf * lat_w];
598 let basis = [1u32, 8, 56, 336];
599 for (f, codes) in frames.iter().enumerate() {
600 for (i, &c) in codes.iter().enumerate() {
601 for (j, (&lv, &b)) in FSQ_LEVELS.iter().zip(&basis).enumerate() {
602 let half = (lv / 2) as f32;
603 lat[f * lat_w + i * 4 + j] = (((c / b) % lv) as f32 - half) / half;
604 }
605 }
606 }
607 let qd = self.project_out.0.rows;
608 let mut q = vec![0f32; nf * qd];
609 self.project_out.0.matmat(&lat, nf, &mut q, pool);
610 add_bias(&mut q, &self.project_out.1);
611 let ed = self.from_q.0.rows;
612 let mut e = vec![0f32; nf * ed];
613 self.from_q.0.matmat(&q, nf, &mut e, pool);
614 add_bias(&mut e, &self.from_q.1);
615 let n = nf * FRAME_STACK;
616 let mut x = vec![0f32; n * ed];
617 for t in 0..n {
618 x[t * ed..(t + 1) * ed].copy_from_slice(&e[(t / FRAME_STACK) * ed..][..ed]);
619 }
620 self.stack.forward(&mut x, pool);
621 let bins = N_FFT / 2 + 1;
622 let mut spec = vec![0f32; n * 2 * bins];
623 self.lin.0.matmat(&x, n, &mut spec, pool);
624 add_bias(&mut spec, &self.lin.1);
625 let ifft = FftPlanner::<f64>::new().plan_fft_inverse(N_FFT);
626 let out_len = (n - 1) * HOP + N_FFT;
627 let mut audio = vec![0f64; out_len];
628 let mut env = vec![0f64; out_len];
629 let mut buf = vec![Complex::new(0.0, 0.0); N_FFT];
630 for t in 0..n {
631 let row = &spec[t * 2 * bins..(t + 1) * 2 * bins];
632 for k in 0..bins {
633 let mag = (row[k] as f64).exp();
634 let ph = row[bins + k] as f64;
635 let v = Complex::new(mag * ph.cos(), mag * ph.sin());
636 buf[k] = if k == 0 || k == N_FFT / 2 { Complex::new(v.re, 0.0) } else { v };
637 if k > 0 && k < N_FFT / 2 {
638 buf[N_FFT - k] = v.conj();
639 }
640 }
641 ifft.process(&mut buf);
642 for (i, b) in buf.iter().enumerate() {
643 let w = self.window[i] as f64;
644 audio[t * HOP + i] += b.re / N_FFT as f64 * w;
645 env[t * HOP + i] += w * w;
646 }
647 }
648 let trim = (N_FFT - HOP) / 2;
649 audio[trim..out_len - trim]
650 .iter()
651 .zip(&env[trim..out_len - trim])
652 .map(|(&a, &w)| (a / w) as f32)
653 .collect()
654 }
655}
656
657pub struct SpeechOut {
661 pub frames: Vec<[u32; N_CODEBOOKS]>,
662 pub text_ids: Vec<u32>,
663}
664
665pub fn can_speak(model: &CmfModel) -> bool {
667 has_tensor(model, "depth_decoder.depth_linear.weight") && has_tensor(model, "codec.decoder.lin.weight")
668}
669
670pub fn speak(
676 pipeline: &mut Pipeline,
677 depth: &DepthDecoder,
678 ids: &[u32],
679 rows: Option<&[f32]>,
680 max_steps: usize,
681 s: &AudioSampling,
682 seed: u64,
683) -> Result<SpeechOut, String> {
684 const N_TEXT: usize = 6;
685 const N_AUDIO: usize = 12;
686 let id = |t: &str| pipeline.tokenizer.token_to_id(t);
687 let audio_start = id("<|audio_start|>").ok_or("tokenizer lacks <|audio_start|>")?;
688 let (mixed_start, text_end, im_end) = (id("<|mixed_start|>"), id("<|text_end|>"), id("<|im_end|>"));
689 #[derive(PartialEq, Clone, Copy)]
690 enum Mode {
691 Text,
692 Audio,
693 Mixed,
694 }
695 let mut mode = match ids.last() {
696 Some(&t) if t == audio_start => Mode::Audio,
697 Some(&t) if Some(t) == mixed_start => Mode::Mixed,
698 _ => Mode::Text,
699 };
700 let (mut slots_left, mut speaking, mut text_done) = (N_TEXT, false, false);
701 let mut rng = SplitMix64::new(seed);
702 let mut h = pipeline.speech_prefill(ids, rows)?;
703 let mut pos = ids.len();
704 let mut out = SpeechOut { frames: Vec::new(), text_ids: Vec::new() };
705 let prof = std::env::var_os("CMF_LFM3_SPEECH_PROF").is_some();
707 let (mut t_depth, mut t_lm, mut t_head) = (0f64, 0f64, 0f64);
708 for _ in 0..max_steps {
709 let interleaving = mode == Mode::Mixed;
710 let t0 = std::time::Instant::now();
711 let row = if mode == Mode::Audio || (interleaving && speaking) {
712 let codes = depth.sample_frame(&h, s, &out.frames, &mut rng);
713 t_depth += t0.elapsed().as_secs_f64();
714 if codes.contains(&AUDIO_EOS) {
715 mode = Mode::Text;
716 depth.frame_row(&[AUDIO_EOS; N_CODEBOOKS])
717 } else {
718 out.frames.push(codes);
719 depth.frame_row(&codes)
720 }
721 } else {
722 let logits = pipeline.speech_logits(&h);
723 t_head += t0.elapsed().as_secs_f64();
724 let t = sample(&logits, 0.0, 0, &mut rng);
725 if Some(t) == text_end {
726 if !interleaving {
727 break;
728 }
729 text_done = true;
731 speaking = true;
732 } else if Some(t) == im_end || pipeline.tokenizer.is_eos(t) {
733 break;
734 } else if !interleaving && t == audio_start {
735 mode = Mode::Audio;
736 } else if !interleaving && Some(t) == mixed_start {
737 mode = Mode::Mixed;
738 (slots_left, speaking, text_done) = (N_TEXT, false, false);
739 }
740 out.text_ids.push(t);
741 pipeline.embed_id(t)
742 };
743 if interleaving && !text_done {
744 slots_left -= 1;
745 if slots_left == 0 {
746 speaking = !speaking;
747 slots_left = if speaking { N_AUDIO } else { N_TEXT };
748 }
749 }
750 let t1 = std::time::Instant::now();
751 h = pipeline.speech_step(&row, pos);
752 t_lm += t1.elapsed().as_secs_f64();
753 pos += 1;
754 }
755 if prof {
756 eprintln!(
757 "speech: {} frames, {} text tokens | depth decoder {:.0} ms, language model {:.0} ms, text head {:.0} ms",
758 out.frames.len(),
759 out.text_ids.len(),
760 t_depth * 1e3,
761 t_lm * 1e3,
762 t_head * 1e3
763 );
764 }
765 Ok(out)
766}
767
768pub fn write_wav(path: &std::path::Path, samples: &[f32], rate: u32) -> std::io::Result<()> {
770 use std::io::Write;
771 let bytes = (samples.len() * 2) as u32;
772 let mut f = std::io::BufWriter::new(std::fs::File::create(path)?);
773 f.write_all(b"RIFF")?;
774 f.write_all(&(36 + bytes).to_le_bytes())?;
775 f.write_all(b"WAVEfmt ")?;
776 f.write_all(&16u32.to_le_bytes())?;
777 f.write_all(&1u16.to_le_bytes())?;
778 f.write_all(&1u16.to_le_bytes())?;
779 f.write_all(&rate.to_le_bytes())?;
780 f.write_all(&(rate * 2).to_le_bytes())?;
781 f.write_all(&2u16.to_le_bytes())?;
782 f.write_all(&16u16.to_le_bytes())?;
783 f.write_all(b"data")?;
784 f.write_all(&bytes.to_le_bytes())?;
785 for &v in samples {
786 f.write_all(&((v.clamp(-1.0, 1.0) * 32767.0) as i16).to_le_bytes())?;
787 }
788 f.flush()
789}