Skip to main content

cortiq_engine/
music3.rs

1//! MiniMax-Music-3's flow-matching DiT: latent + conditioning → velocity.
2//!
3//! Ported against ComfyUI's `comfy/ldm/minimax_music/dit.py`. Four of its
4//! conventions are worth naming here, because each is invisible in a
5//! tensor name and wrong in a way that still produces plausible output:
6//!
7//! 1. The input is `[x | zeros_like(x) | condition]` on the CHANNEL axis
8//!    — 128 + 128 + 2048 = 2304, which is what `preprocess_conv`'s width
9//!    is telling you. The zero plane is not padding, it is a slot the
10//!    reference leaves empty.
11//! 2. Both 1×1 convs are RESIDUAL: `conv(x) + x`, not `conv(x)`.
12//! 3. The timestep embedding is prepended as an extra TOKEN, carried
13//!    through all 36 blocks, and dropped before `project_out`. It also
14//!    shifts every latent frame's rotary position by one.
15//! 4. The transformer's output is NEGATED. A flow-matching sampler that
16//!    steps the wrong way still moves, and still decodes to sound.
17//!
18//! RoPE covers the first 32 of each head's 64 dims, split-half: for
19//! `i < 16` the pair is `(x[i], x[i+16])`, rotated by `pos·inv_freq[i]`.
20
21use crate::dit::Proj;
22use crate::qtensor::QTensor;
23use crate::audiovae::SendPtr;
24use crate::pool::Pool;
25use cortiq_core::CmfModel;
26use std::sync::Arc;
27
28fn silu(v: f32) -> f32 {
29    v / (1.0 + (-v).exp())
30}
31
32/// LayerNorm with learned scale AND shift — not the RMSNorm the rest of
33/// this engine's transformers use.
34struct LayerNorm {
35    gamma: Vec<f32>,
36    beta: Vec<f32>,
37}
38
39impl LayerNorm {
40    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
41        Ok(Self {
42            gamma: crate::dit::cmf_f32(model, &format!("{p}.gamma"))?,
43            beta: crate::dit::cmf_f32(model, &format!("{p}.beta"))?,
44        })
45    }
46
47    fn apply(&self, x: &mut [f32], d: usize) {
48        for row in x.chunks_exact_mut(d) {
49            let mean = row.iter().map(|v| *v as f64).sum::<f64>() / d as f64;
50            let var = row.iter().map(|v| (*v as f64 - mean).powi(2)).sum::<f64>() / d as f64;
51            let inv = 1.0 / (var + 1e-5).sqrt();
52            for ((v, &g), &b) in row.iter_mut().zip(&self.gamma).zip(&self.beta) {
53                *v = ((*v as f64 - mean) * inv) as f32 * g + b;
54            }
55        }
56    }
57}
58
59struct Block {
60    pre_norm: LayerNorm,
61    qkv: Proj,
62    out: Proj,
63    ff_norm: LayerNorm,
64    ff_in: Proj,
65    ff_in_b: Vec<f32>,
66    ff_out: Proj,
67    ff_out_b: Vec<f32>,
68}
69
70impl Block {
71    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
72        Ok(Self {
73            pre_norm: LayerNorm::load(model, &format!("{p}.pre_norm"))?,
74            qkv: Proj::from_model(model, &format!("{p}.self_attn.to_qkv.weight"))?,
75            out: Proj::from_model(model, &format!("{p}.self_attn.to_out.weight"))?,
76            ff_norm: LayerNorm::load(model, &format!("{p}.ff_norm"))?,
77            ff_in: Proj::from_model(model, &format!("{p}.ff.ff.0.proj.weight"))?,
78            ff_in_b: crate::dit::cmf_f32(model, &format!("{p}.ff.ff.0.proj.bias"))?,
79            ff_out: Proj::from_model(model, &format!("{p}.ff.ff.2.weight"))?,
80            ff_out_b: crate::dit::cmf_f32(model, &format!("{p}.ff.ff.2.bias"))?,
81        })
82    }
83}
84
85pub struct Music3Dit {
86    pre_conv: Vec<f32>,  // [2304, 2304] 1x1
87    post_conv: Vec<f32>, // [128, 128] 1x1
88    fourier: Vec<f32>,   // [128]
89    t0: Proj,
90    t0_b: Vec<f32>,
91    t2: Proj,
92    t2_b: Vec<f32>,
93    project_in: Proj,
94    project_out: Proj,
95    inv_freq: Vec<f32>,
96    blocks: Vec<Block>,
97    pool: Option<Arc<Pool>>,
98    hidden: usize,
99    heads: usize,
100    hd: usize,
101    rot: usize,
102    inter: usize,
103    /// The learned mix over the AR stack's eight codebook levels, and
104    /// the 3-tap conv that turns the mixture into the DiT's condition.
105    cond_logits: Vec<f32>,
106    cond_scale: f32,
107    lc_w: Vec<f32>,
108    lc_b: Vec<f32>,
109}
110
111impl Music3Dit {
112    pub const IN_CH: usize = 128;
113    pub const COND_CH: usize = 2048;
114    pub const CONCAT_CH: usize = 2304;
115
116    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
117        let cfg: serde_json::Value =
118            serde_json::from_slice(model.tensor_bytes("mdit.config_json").map_err(|e| e.to_string())?)
119                .map_err(|e| format!("mdit.config_json: {e}"))?;
120        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
121        let nl = u("num_layers", 36);
122        let dt = "mdit.diffusion_transformer";
123        Ok(Self {
124            pre_conv: crate::dit::cmf_f32(model, &format!("{dt}.preprocess_conv.weight"))?,
125            post_conv: crate::dit::cmf_f32(model, &format!("{dt}.postprocess_conv.weight"))?,
126            fourier: crate::dit::cmf_f32(model, &format!("{dt}.timestep_features.weight"))?,
127            t0: Proj::from_model(model, &format!("{dt}.to_timestep_embed.0.weight"))?,
128            t0_b: crate::dit::cmf_f32(model, &format!("{dt}.to_timestep_embed.0.bias"))?,
129            t2: Proj::from_model(model, &format!("{dt}.to_timestep_embed.2.weight"))?,
130            t2_b: crate::dit::cmf_f32(model, &format!("{dt}.to_timestep_embed.2.bias"))?,
131            project_in: Proj::from_model(model, &format!("{dt}.transformer.project_in.weight"))?,
132            project_out: Proj::from_model(model, &format!("{dt}.transformer.project_out.weight"))?,
133            inv_freq: crate::dit::cmf_f32(model, &format!("{dt}.transformer.rotary_pos_emb.inv_freq"))?,
134            blocks: (0..nl)
135                .map(|i| Block::load(model, &format!("{dt}.transformer.layers.{i}")))
136                .collect::<Result<_, _>>()?,
137            pool: Pool::from_env(),
138            hidden: u("hidden", 2048),
139            heads: u("num_heads", 32),
140            hd: u("head_dim", 64),
141            rot: u("rotary_dim", 32),
142            inter: u("ff_inner", 8192),
143            cond_logits: crate::dit::cmf_f32(model, "mdit.cond_layer_logits")?,
144            cond_scale: crate::dit::cmf_f32(model, "mdit.cond_layer_scale")?[0],
145            lc_w: crate::dit::cmf_f32(model, "mdit.latent_conditioners.0.weight")?,
146            lc_b: crate::dit::cmf_f32(model, "mdit.latent_conditioners.0.bias")?,
147        })
148    }
149
150    /// Latent frames for `audio_frames` of AR output — the reference's
151    /// `latent_length`: 44100/24000 · 960/512 = 3.4453125 per frame.
152    pub fn latent_length(audio_frames: usize) -> usize {
153        ((audio_frames as f64 * 44100.0 / 24000.0 * 960.0 / 512.0) as usize).max(1)
154    }
155
156    /// AR hidden `[frames, 8·4096]` → the DiT's condition `[2048, L]`.
157    ///
158    /// The eight are RVQ CODEBOOK levels, softmax-mixed by
159    /// `cond_layer_logits`, scaled, passed through one 3-tap conv and
160    /// then NEAREST-resampled to the latent rate. Nearest, not linear:
161    /// the reference interpolates that way and a smoother resample
162    /// smears the onset of every note.
163    pub fn aligned_condition(&self, hidden: &[f32], frames: usize) -> (Vec<f32>, usize) {
164        let levels = self.cond_logits.len();
165        let ar = hidden.len() / (frames * levels);
166        let mx = self.cond_logits.iter().cloned().fold(f32::MIN, f32::max);
167        let ex: Vec<f32> = self.cond_logits.iter().map(|v| (v - mx).exp()).collect();
168        let sum: f32 = ex.iter().sum();
169        // [ar, frames], channel-major, mixed and scaled.
170        let mut mixed = vec![0f32; ar * frames];
171        for t in 0..frames {
172            for l in 0..levels {
173                let w = ex[l] / sum * self.cond_scale;
174                let src = &hidden[(t * levels + l) * ar..(t * levels + l + 1) * ar];
175                for (c, &v) in src.iter().enumerate() {
176                    mixed[c * frames + t] += w * v;
177                }
178            }
179        }
180        // Conv1d(ar -> 2048, k=3, pad=1).
181        let out_ch = self.lc_b.len();
182        let mut conv = vec![0f32; out_ch * frames];
183        for o in 0..out_ch {
184            let dst = &mut conv[o * frames..(o + 1) * frames];
185            dst.fill(self.lc_b[o]);
186            for i in 0..ar {
187                let k = &self.lc_w[(o * ar + i) * 3..(o * ar + i + 1) * 3];
188                let src = &mixed[i * frames..(i + 1) * frames];
189                for t in 0..frames {
190                    for (j, &kv) in k.iter().enumerate() {
191                        let p = t as isize + j as isize - 1;
192                        if p >= 0 && (p as usize) < frames {
193                            dst[t] += kv * src[p as usize];
194                        }
195                    }
196                }
197            }
198        }
199        let l = Self::latent_length(frames);
200        let mut out = vec![0f32; out_ch * l];
201        for o in 0..out_ch {
202            for t in 0..l {
203                let s = (t * frames) / l.max(1);
204                out[o * l + t] = conv[o * frames + s.min(frames - 1)];
205            }
206        }
207        (out, l)
208    }
209
210    /// `[out, in]` 1×1 conv over the channel axis of a `[in, n]` panel,
211    /// added back to its input — both convs in this model are residual.
212    fn conv1x1_residual(w: &[f32], x: &mut [f32], ch: usize, n: usize) {
213        let mut acc = vec![0f32; ch * n];
214        for o in 0..ch {
215            let row = &w[o * ch..(o + 1) * ch];
216            let dst = &mut acc[o * n..(o + 1) * n];
217            for (i, &wv) in row.iter().enumerate() {
218                if wv == 0.0 {
219                    continue;
220                }
221                for (d, &s) in dst.iter_mut().zip(&x[i * n..(i + 1) * n]) {
222                    *d += wv * s;
223                }
224            }
225        }
226        for (d, a) in x.iter_mut().zip(&acc) {
227            *d += a;
228        }
229    }
230
231    /// `cat(cos, sin)` of `2π·t·w`, then the two-layer SiLU head.
232    fn timestep_embedding(&self, t: f32) -> Vec<f32> {
233        let half = self.fourier.len();
234        let mut feats = vec![0f32; half * 2];
235        for (i, &w) in self.fourier.iter().enumerate() {
236            let a = std::f32::consts::TAU * t * w;
237            feats[i] = a.cos();
238            feats[half + i] = a.sin();
239        }
240        let mut h = vec![0f32; self.t0_b.len()];
241        self.t0.matmat(&feats, 1, &mut h, self.pool.as_deref());
242        for (v, &b) in h.iter_mut().zip(&self.t0_b) {
243            *v = silu(*v + b);
244        }
245        let mut out = vec![0f32; self.t2_b.len()];
246        self.t2.matmat(&h, 1, &mut out, self.pool.as_deref());
247        for (v, &b) in out.iter_mut().zip(&self.t2_b) {
248            *v += b;
249        }
250        out
251    }
252
253    /// Split-half RoPE over the first `rot` dims of every head.
254    fn rope(&self, q: &mut [f32], k: &mut [f32], n: usize) {
255        let half = self.rot / 2;
256        for p in 0..n {
257            for h in 0..self.heads {
258                let off = p * self.heads * self.hd + h * self.hd;
259                for i in 0..half {
260                    let (s, c) = (p as f32 * self.inv_freq[i]).sin_cos();
261                    for x in [&mut *q, &mut *k] {
262                        let (a, b) = (x[off + i], x[off + i + half]);
263                        x[off + i] = a * c - b * s;
264                        x[off + i + half] = a * s + b * c;
265                    }
266                }
267            }
268        }
269    }
270
271    /// One block over `[n, hidden]`, in place.
272    fn block(&self, blk: &Block, x: &mut [f32], n: usize) {
273        let (hs, nh, hd) = (self.hidden, self.heads, self.hd);
274        let pool = self.pool.as_deref();
275        let mut h = x.to_vec();
276        blk.pre_norm.apply(&mut h, hs);
277        let mut qkv = vec![0f32; n * 3 * hs];
278        blk.qkv.matmat(&h, n, &mut qkv, pool);
279        // to_qkv emits q|k|v concatenated along the FEATURE axis, so the
280        // three live at column offsets, not row offsets.
281        let mut q = vec![0f32; n * hs];
282        let mut k = vec![0f32; n * hs];
283        let mut v = vec![0f32; n * hs];
284        for p in 0..n {
285            let s = &qkv[p * 3 * hs..(p + 1) * 3 * hs];
286            q[p * hs..(p + 1) * hs].copy_from_slice(&s[..hs]);
287            k[p * hs..(p + 1) * hs].copy_from_slice(&s[hs..2 * hs]);
288            v[p * hs..(p + 1) * hs].copy_from_slice(&s[2 * hs..]);
289        }
290        self.rope(&mut q, &mut k, n);
291        let scale = 1.0 / (hd as f32).sqrt();
292        let mut attn = vec![0f32; n * hs];
293        // Attention here is quadratic in the sequence and the DiT runs
294        // 36 of them per step; at 431 latent frames this loop WAS the
295        // denoise, on one core, while the GEMMs around it were already
296        // threaded. Heads are independent and write disjoint columns, so
297        // they parallelize without a lock.
298        {
299            let ptr = SendPtr(attn.as_mut_ptr());
300            let work = |lo: usize, hi: usize| {
301                let mut scores = vec![0f32; n];
302                for hh in lo..hi {
303                    for i in 0..n {
304                        let qi = &q[i * hs + hh * hd..i * hs + hh * hd + hd];
305                        let mut mx = f32::NEG_INFINITY;
306                        for (j, sc) in scores.iter_mut().enumerate() {
307                            let kj = &k[j * hs + hh * hd..j * hs + hh * hd + hd];
308                            *sc = qi.iter().zip(kj).map(|(a, b)| a * b).sum::<f32>() * scale;
309                            mx = mx.max(*sc);
310                        }
311                        let mut sum = 0.0;
312                        for sc in scores.iter_mut() {
313                            *sc = (*sc - mx).exp();
314                            sum += *sc;
315                        }
316                        let inv = 1.0 / sum;
317                        // SAFETY: head `hh` owns these columns of every row.
318                        let dst = unsafe { ptr.row(i * hs + hh * hd, hd) };
319                        for (j, &sc) in scores.iter().enumerate() {
320                            let w = sc * inv;
321                            let vj = &v[j * hs + hh * hd..j * hs + hh * hd + hd];
322                            for (d, &vv) in dst.iter_mut().zip(vj) {
323                                *d += w * vv;
324                            }
325                        }
326                    }
327                }
328            };
329            match pool {
330                Some(p) => p.run_rows(nh, &work),
331                None => work(0, nh),
332            }
333        }
334        let mut proj = vec![0f32; n * hs];
335        blk.out.matmat(&attn, n, &mut proj, pool);
336        for (a, b) in x.iter_mut().zip(&proj) {
337            *a += b;
338        }
339
340        let mut h = x.to_vec();
341        blk.ff_norm.apply(&mut h, hs);
342        let mut gu = vec![0f32; n * 2 * self.inter];
343        blk.ff_in.matmat(&h, n, &mut gu, pool);
344        // GLU here is `value * silu(gate)` with VALUE first, and the
345        // projection's bias belongs to BOTH halves before they meet.
346        // Swapping the halves still makes sound, which is why this is
347        // spelled out rather than inferred.
348        let inter = self.inter;
349        let (vb, gb) = blk.ff_in_b.split_at(inter);
350        let mut act = vec![0f32; n * inter];
351        for p in 0..n {
352            let row = &gu[p * 2 * inter..(p + 1) * 2 * inter];
353            let (val, gate) = row.split_at(inter);
354            let dst = &mut act[p * inter..(p + 1) * inter];
355            for i in 0..inter {
356                dst[i] = (val[i] + vb[i]) * silu(gate[i] + gb[i]);
357            }
358        }
359        let mut ffo = vec![0f32; n * hs];
360        blk.ff_out.matmat(&act, n, &mut ffo, pool);
361        for p in 0..n {
362            for j in 0..hs {
363                x[p * hs + j] += ffo[p * hs + j] + blk.ff_out_b[j];
364            }
365        }
366    }
367
368    /// Latent frames the transformer will attend across in one go, and
369    /// the stride it advances by — `latent_length(200)` and
370    /// `latent_length(100)` in the reference. Attention is quadratic, so
371    /// a whole song in one pass is not merely slow, it is not what the
372    /// model was run as.
373    pub const WINDOW: usize = 689;
374    pub const HOP: usize = 344;
375
376    /// The velocity over any length, windowed like the reference:
377    /// overlapping passes averaged by how many covered each frame.
378    pub fn forward_windowed(&self, x: &[f32], condition: &[f32], n: usize, t: f32) -> Vec<f32> {
379        if n <= Self::WINDOW {
380            return self.forward(x, condition, n, t);
381        }
382        let ch = Self::IN_CH;
383        let cc = Self::COND_CH;
384        let mut out = vec![0f32; ch * n];
385        let mut count = vec![0f32; n];
386        let mut start = 0usize;
387        loop {
388            let end = (start + Self::WINDOW).min(n);
389            let w = end - start;
390            let mut xw = vec![0f32; ch * w];
391            for c in 0..ch {
392                xw[c * w..(c + 1) * w].copy_from_slice(&x[c * n + start..c * n + end]);
393            }
394            let mut cw = vec![0f32; cc * w];
395            for c in 0..cc {
396                cw[c * w..(c + 1) * w].copy_from_slice(&condition[c * n + start..c * n + end]);
397            }
398            let v = self.forward(&xw, &cw, w, t);
399            for c in 0..ch {
400                for i in 0..w {
401                    out[c * n + start + i] += v[c * w + i];
402                }
403            }
404            for i in 0..w {
405                count[start + i] += 1.0;
406            }
407            if end == n {
408                break;
409            }
410            start += Self::HOP;
411        }
412        for c in 0..ch {
413            for i in 0..n {
414                out[c * n + i] /= count[i];
415            }
416        }
417        out
418    }
419
420    /// `x` is `[128, n]` and `condition` `[2048, n]`; the result is the
421    /// velocity at `[128, n]`.
422    pub fn forward(&self, x: &[f32], condition: &[f32], n: usize, t: f32) -> Vec<f32> {
423        let pool = self.pool.as_deref();
424        let mut full = vec![0f32; Self::CONCAT_CH * n];
425        full[..Self::IN_CH * n].copy_from_slice(x);
426        // rows 128..256 stay zero: the reference's `zeros_like(x)` plane
427        full[2 * Self::IN_CH * n..].copy_from_slice(condition);
428        Self::conv1x1_residual(&self.pre_conv, &mut full, Self::CONCAT_CH, n);
429
430        // channel-major -> token-major for the transformer
431        let mut toks = vec![0f32; n * Self::CONCAT_CH];
432        for c in 0..Self::CONCAT_CH {
433            for p in 0..n {
434                toks[p * Self::CONCAT_CH + c] = full[c * n + p];
435            }
436        }
437        let mut h = vec![0f32; n * self.hidden];
438        self.project_in.matmat(&toks, n, &mut h, pool);
439
440        // The timestep rides as token 0 and shifts every rotary position.
441        let temb = self.timestep_embedding(t);
442        let mut seq = vec![0f32; (n + 1) * self.hidden];
443        seq[..self.hidden].copy_from_slice(&temb);
444        seq[self.hidden..].copy_from_slice(&h);
445        for blk in &self.blocks {
446            self.block(blk, &mut seq, n + 1);
447        }
448
449        let mut out = vec![0f32; n * Self::IN_CH];
450        self.project_out
451            .matmat(&seq[self.hidden..], n, &mut out, pool);
452        let mut ch = vec![0f32; Self::IN_CH * n];
453        for c in 0..Self::IN_CH {
454            for p in 0..n {
455                ch[c * n + p] = out[p * Self::IN_CH + c];
456            }
457        }
458        Self::conv1x1_residual(&self.post_conv, &mut ch, Self::IN_CH, n);
459        for v in ch.iter_mut() {
460            *v = -*v;
461        }
462        ch
463    }
464
465    /// Denoise `[128, n]` from noise to a latent, `steps` Euler steps
466    /// along σ: 1 → 0. `progress` is called with (step, total).
467    pub fn sample(
468        &self,
469        noise: &[f32],
470        condition: &[f32],
471        n: usize,
472        steps: usize,
473        mut progress: impl FnMut(usize, usize),
474    ) -> Vec<f32> {
475        let sigmas = flow_sigmas(steps);
476        let mut x = noise.to_vec();
477        for i in 0..steps {
478            let (s, s_next) = (sigmas[i], sigmas[i + 1]);
479            // ComfyUI's process_timestep for this model.
480            let v = self.forward_windowed(&x, condition, n, 1.0 - s);
481            let dt = s_next - s;
482            for (a, b) in x.iter_mut().zip(&v) {
483                *a += dt * b;
484            }
485            progress(i + 1, steps);
486        }
487        x
488    }
489}
490
491/// Euler flow-matching sampler for Music-3.
492///
493/// ComfyUI registers this model as a plain `ModelType.FLOW` with
494/// `multiplier: 1.0` and `process_timestep(t) = 1.0 - t`, so the sampler
495/// is the ordinary one and NOT the `FlowMatchEulerDiscreteScheduler`
496/// named in MiniMax's own `scheduler_config.json` — that belongs to
497/// their diffusers pipeline. Worth stating because the config is the
498/// first thing you find and it sends you somewhere else: with
499/// `num_train_timesteps: 1` its schedule degenerates to a constant,
500/// which is the tell that the caller supplies the sigmas.
501///
502/// σ walks 1 → 0, the DiT is asked at `1 − σ`, and the step is
503/// `x += (σ_next − σ)·v`. The DiT already negates its own output, so
504/// the sign lives there rather than here.
505///
506/// The walk is NOT uniform, and that detail is audible. ComfyUI's
507/// `normal_scheduler` evaluates at `linspace(σ_max, σ_min, steps)` and
508/// only THEN appends zero, and this model's `ModelSamplingDiscreteFlow`
509/// has `σ_min = 1/1000` — so the last velocity is measured essentially
510/// at the end of the trajectory. A uniform `1 → 0` in `steps` stops at
511/// `1/steps` and integrates the whole remaining tail from a velocity
512/// sampled well before it, which is a smeared, mushy final approach.
513pub fn flow_sigmas(steps: usize) -> Vec<f32> {
514    const SIGMA_MIN: f32 = 0.001;
515    let n = steps.max(1);
516    let mut s: Vec<f32> = (0..n)
517        .map(|i| {
518            if n == 1 {
519                1.0
520            } else {
521                1.0 + (SIGMA_MIN - 1.0) * i as f32 / (n - 1) as f32
522            }
523        })
524        .collect();
525    s.push(0.0);
526    s
527}
528
529/// RMSNorm with a weight and no bias, eps 1e-6.
530struct RmsNorm {
531    w: Vec<f32>,
532}
533
534impl RmsNorm {
535    fn load(model: &Arc<CmfModel>, n: &str) -> Result<Self, String> {
536        Ok(Self {
537            w: crate::dit::cmf_f32(model, n)?,
538        })
539    }
540
541    fn apply(&self, x: &mut [f32], d: usize) {
542        for row in x.chunks_exact_mut(d) {
543            let ss = row.iter().map(|v| (*v as f64) * (*v as f64)).sum::<f64>() / d as f64;
544            let inv = 1.0 / (ss + 1e-6).sqrt();
545            for (v, &g) in row.iter_mut().zip(&self.w) {
546                *v = (*v as f64 * inv) as f32 * g;
547            }
548        }
549    }
550}
551
552struct RvqBlock {
553    n1: RmsNorm,
554    q: Proj,
555    k: Proj,
556    v: Proj,
557    o: Proj,
558    n2: RmsNorm,
559    gate: Proj,
560    up: Proj,
561    down: Proj,
562}
563
564impl RvqBlock {
565    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
566        Ok(Self {
567            n1: RmsNorm::load(model, &format!("{p}.input_layernorm.weight"))?,
568            q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
569            k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
570            v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
571            o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
572            n2: RmsNorm::load(model, &format!("{p}.post_attention_layernorm.weight"))?,
573            gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
574            up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
575            down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
576        })
577    }
578}
579
580/// The RVQ depth decoder: given the frame's hidden state and the codes
581/// chosen so far, it predicts the next codebook level.
582///
583/// It is a small CAUSAL transformer over a sequence that never exceeds
584/// the codebook count — the positional table is 16 rows for 8 levels —
585/// with no rotary embedding at all. Attention that forgets the mask here
586/// leaks a level's own answer backwards and the model still samples.
587pub struct RvqDepthDecoder {
588    projection: Proj,
589    pos: Vec<f32>,
590    blocks: Vec<RvqBlock>,
591    norm: RmsNorm,
592    heads: Vec<Proj>,
593    pool: Option<Arc<Pool>>,
594    hidden: usize,
595    nh: usize,
596    hd: usize,
597    inter: usize,
598}
599
600impl RvqDepthDecoder {
601    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
602        let cfg: serde_json::Value =
603            serde_json::from_slice(model.tensor_bytes("mte.config_json").map_err(|e| e.to_string())?)
604                .map_err(|e| format!("mte.config_json: {e}"))?;
605        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
606        let hidden = u("hidden_size", 4096);
607        let nl = u("decoder_num_layers", 4);
608        let nh = u("decoder_num_heads", 16);
609        let cb = u("audio_num_codebooks", 8);
610        Ok(Self {
611            projection: Proj::from_model(model, "mte.audio_decoder.projection.weight")?,
612            pos: crate::dit::cmf_f32(model, "mte.audio_decoder.pos_embedding.weight")?,
613            blocks: (0..nl)
614                .map(|i| RvqBlock::load(model, &format!("mte.audio_decoder.layers.{i}")))
615                .collect::<Result<_, _>>()?,
616            norm: RmsNorm::load(model, "mte.audio_decoder.norm.weight")?,
617            heads: (0..cb - 1)
618                .map(|i| {
619                    Proj::from_model(model, &format!("mte.audio_decoder.audio_heads.{i}.weight"))
620                })
621                .collect::<Result<_, _>>()?,
622            pool: Pool::from_env(),
623            hidden,
624            nh,
625            hd: hidden / nh,
626            inter: u("decoder_intermediate_size", 6144),
627        })
628    }
629
630    pub fn codebooks(&self) -> usize {
631        self.heads.len() + 1
632    }
633
634    /// `projection` is applied to every element entering the sequence —
635    /// the frame hidden, the c0 embedding and each extra embedding.
636    pub fn project(&self, x: &[f32]) -> Vec<f32> {
637        let n = x.len() / self.hidden;
638        let mut out = vec![0f32; n * self.hidden];
639        self.projection
640            .matmat(x, n, &mut out, self.pool.as_deref());
641        out
642    }
643
644    /// Run the stack over `[n, hidden]` and return the LAST position's
645    /// normed hidden — the only one the caller reads.
646    pub fn forward_last(&self, seq: &[f32], n: usize) -> Vec<f32> {
647        let (hs, nh, hd) = (self.hidden, self.nh, self.hd);
648        let pool = self.pool.as_deref();
649        let mut x = seq.to_vec();
650        for p in 0..n {
651            for (v, &pe) in x[p * hs..(p + 1) * hs].iter_mut().zip(&self.pos[p * hs..(p + 1) * hs]) {
652                *v += pe;
653            }
654        }
655        for blk in &self.blocks {
656            let mut h = x.clone();
657            blk.n1.apply(&mut h, hs);
658            let (mut q, mut k, mut v) = (vec![0f32; n * hs], vec![0f32; n * hs], vec![0f32; n * hs]);
659            blk.q.matmat(&h, n, &mut q, pool);
660            blk.k.matmat(&h, n, &mut k, pool);
661            blk.v.matmat(&h, n, &mut v, pool);
662            let scale = 1.0 / (hd as f32).sqrt();
663            let mut attn = vec![0f32; n * hs];
664            for hh in 0..nh {
665                for i in 0..n {
666                    let qi = &q[i * hs + hh * hd..i * hs + hh * hd + hd];
667                    // Causal: position i sees 0..=i and nothing after.
668                    let mut sc = vec![0f32; i + 1];
669                    let mut mx = f32::NEG_INFINITY;
670                    for (j, s) in sc.iter_mut().enumerate() {
671                        let kj = &k[j * hs + hh * hd..j * hs + hh * hd + hd];
672                        *s = qi.iter().zip(kj).map(|(a, b)| a * b).sum::<f32>() * scale;
673                        mx = mx.max(*s);
674                    }
675                    let mut sum = 0.0;
676                    for s in sc.iter_mut() {
677                        *s = (*s - mx).exp();
678                        sum += *s;
679                    }
680                    let inv = 1.0 / sum;
681                    let dst = &mut attn[i * hs + hh * hd..i * hs + hh * hd + hd];
682                    for (j, &s) in sc.iter().enumerate() {
683                        let w = s * inv;
684                        let vj = &v[j * hs + hh * hd..j * hs + hh * hd + hd];
685                        for (d, &vv) in dst.iter_mut().zip(vj) {
686                            *d += w * vv;
687                        }
688                    }
689                }
690            }
691            let mut proj = vec![0f32; n * hs];
692            blk.o.matmat(&attn, n, &mut proj, pool);
693            for (a, b) in x.iter_mut().zip(&proj) {
694                *a += b;
695            }
696
697            let mut h = x.clone();
698            blk.n2.apply(&mut h, hs);
699            let (mut g, mut u) = (vec![0f32; n * self.inter], vec![0f32; n * self.inter]);
700            blk.gate.matmat(&h, n, &mut g, pool);
701            blk.up.matmat(&h, n, &mut u, pool);
702            for (a, b) in g.iter_mut().zip(&u) {
703                *a = silu(*a) * b;
704            }
705            let mut ffo = vec![0f32; n * hs];
706            blk.down.matmat(&g, n, &mut ffo, pool);
707            for (a, b) in x.iter_mut().zip(&ffo) {
708                *a += b;
709            }
710        }
711        let mut last = x[(n - 1) * hs..].to_vec();
712        self.norm.apply(&mut last, hs);
713        last
714    }
715
716    /// Logits for codebook `level` (1-based; level 1 uses head 0).
717    pub fn head(&self, level: usize, hidden: &[f32]) -> Vec<f32> {
718        let h = &self.heads[level - 1];
719        let rows = match h {
720            Proj::F32 { rows, .. } => *rows,
721            Proj::Q(q) => q.rows(),
722        };
723        let mut out = vec![0f32; rows];
724        h.matmat(hidden, 1, &mut out, self.pool.as_deref());
725        out
726    }
727}
728
729// ── the autoregressive stack ────────────────────────────────────────
730
731/// One Qwen3 block: RMSNorm → GQA attention with per-head q/k norms and
732/// split-half RoPE → residual → RMSNorm → SwiGLU → residual.
733struct ArBlock {
734    n1: RmsNorm,
735    q: Proj,
736    k: Proj,
737    v: Proj,
738    o: Proj,
739    qn: RmsNorm,
740    kn: RmsNorm,
741    n2: RmsNorm,
742    gate: Proj,
743    up: Proj,
744    down: Proj,
745}
746
747impl ArBlock {
748    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
749        Ok(Self {
750            n1: RmsNorm::load(model, &format!("{p}.input_layernorm.weight"))?,
751            q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
752            k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
753            v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
754            o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
755            qn: RmsNorm::load(model, &format!("{p}.self_attn.q_norm.weight"))?,
756            kn: RmsNorm::load(model, &format!("{p}.self_attn.k_norm.weight"))?,
757            n2: RmsNorm::load(model, &format!("{p}.post_attention_layernorm.weight"))?,
758            gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
759            up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
760            down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
761        })
762    }
763}
764
765/// Keys and values for one layer, one CFG branch: `[pos][nkv·hd]`.
766#[derive(Default, Clone)]
767struct KvRun {
768    k: Vec<f32>,
769    v: Vec<f32>,
770    len: usize,
771}
772
773/// Deterministic top-k sampler.
774///
775/// NOT torch's: reproducing `torch.multinomial` under a seeded
776/// `Generator` bit-for-bit is its own project, and nothing downstream
777/// needs the same seed to mean the same song — only that one seed here
778/// always means one song.
779struct Rng(u64);
780
781impl Rng {
782    fn new(seed: u64) -> Self {
783        Self(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1)
784    }
785    fn next_f32(&mut self) -> f32 {
786        let mut x = self.0;
787        x ^= x << 13;
788        x ^= x >> 7;
789        x ^= x << 17;
790        self.0 = x;
791        ((x >> 40) as f32) / (1u32 << 24) as f32
792    }
793}
794
795fn sample_topk(logits: &[f32], top_k: usize, rng: &mut Rng) -> usize {
796    let mut idx: Vec<usize> = (0..logits.len()).filter(|&i| logits[i].is_finite()).collect();
797    idx.sort_unstable_by(|&a, &b| logits[b].partial_cmp(&logits[a]).unwrap());
798    idx.truncate(top_k.max(1));
799    let mx = idx.iter().map(|&i| logits[i]).fold(f32::MIN, f32::max);
800    let exps: Vec<f32> = idx.iter().map(|&i| (logits[i] - mx).exp()).collect();
801    let sum: f32 = exps.iter().sum();
802    let mut r = rng.next_f32() * sum;
803    for (j, &e) in exps.iter().enumerate() {
804        r -= e;
805        if r <= 0.0 {
806            return idx[j];
807        }
808    }
809    *idx.last().unwrap()
810}
811
812/// MiniMax-Music-3's AR stack: it does not encode a prompt, it GENERATES
813/// the conditioning — audio tokens sampled frame by frame, whose hidden
814/// states become what the DiT is conditioned on.
815pub struct Music3Ar {
816    blocks: Vec<ArBlock>,
817    norm: RmsNorm,
818    embed_prefill: QTensor,
819    embed_audio: QTensor,
820    embed_extra: QTensor,
821    lm_head: Proj,
822    pub depth: RvqDepthDecoder,
823    inv_freq: Vec<f32>,
824    pool: Option<Arc<Pool>>,
825    hidden: usize,
826    nh: usize,
827    nkv: usize,
828    hd: usize,
829    inter: usize,
830    audio_vocab: usize,
831    codebooks: usize,
832    pub cfg_scale: f32,
833    pub top_k: usize,
834    pub fps: usize,
835    pub max_frames: usize,
836}
837
838/// Token ids the prompt is built from — `comfy/ldm/minimax_music/prompt.py`.
839pub mod tokens {
840    pub const IM_START: u32 = 151644;
841    pub const IM_END: u32 = 151645;
842    pub const AUDIO_CFG: u32 = 151654;
843    pub const AUDIO_START: u32 = 151669;
844    pub const CAPTION_START: u32 = 151671;
845    pub const CAPTION_END: u32 = 151672;
846    pub const LYRICS_START: u32 = 151673;
847    pub const LYRICS_END: u32 = 151674;
848}
849
850impl Music3Ar {
851    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
852        let cfg: serde_json::Value =
853            serde_json::from_slice(model.tensor_bytes("mte.config_json").map_err(|e| e.to_string())?)
854                .map_err(|e| format!("mte.config_json: {e}"))?;
855        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
856        let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
857        let hidden = u("hidden_size", 4096);
858        let hd = u("head_dim", 128);
859        let theta = f("rope_theta", 1_000_000.0) as f32;
860        Ok(Self {
861            blocks: (0..u("num_hidden_layers", 36))
862                .map(|i| ArBlock::load(model, &format!("mte.layers.{i}")))
863                .collect::<Result<_, _>>()?,
864            norm: RmsNorm::load(model, "mte.norm.weight")?,
865            embed_prefill: QTensor::from_model(model, "mte.embed_tokens_prefill.weight")?,
866            embed_audio: QTensor::from_model(model, "mte.embed_tokens_audio.weight")?,
867            embed_extra: QTensor::from_model(model, "mte.audio_extra_embedding.weight")?,
868            lm_head: Proj::from_model(model, "mte.lm_head_pruned.weight")?,
869            depth: RvqDepthDecoder::from_cmf(model)?,
870            inv_freq: (0..hd / 2)
871                .map(|i| 1.0 / theta.powf(2.0 * i as f32 / hd as f32))
872                .collect(),
873            pool: Pool::from_env(),
874            hidden,
875            nh: u("num_attention_heads", 32),
876            nkv: u("num_key_value_heads", 8),
877            hd,
878            inter: u("intermediate_size", 12288),
879            audio_vocab: u("audio_vocab_size", 1024),
880            codebooks: u("audio_num_codebooks", 8),
881            cfg_scale: f("cfg_scale", 1.5) as f32,
882            top_k: u("top_k", 50),
883            fps: u("audio_frames_per_second", 25),
884            max_frames: u("max_audio_frames", 9000),
885        })
886    }
887
888    /// One embedding row. A table lookup, not a matmul: these are
889    /// `[vocab, hidden]` and only ever read one row at a time.
890    fn embed_row(p: &QTensor, row: usize, hidden: usize) -> Vec<f32> {
891        let mut out = vec![0f32; hidden];
892        p.row_f32(row, &mut out);
893        out
894    }
895
896    /// Run one position through every block, appending to the cache.
897    /// `x` is `[b, hidden]` for the CFG pair; returns the normed hidden.
898    fn step_blocks(&self, x: &mut [f32], b: usize, pos: usize, cache: &mut [Vec<KvRun>]) {
899        let (hs, nh, nkv, hd) = (self.hidden, self.nh, self.nkv, self.hd);
900        let pool = self.pool.as_deref();
901        let kvw = nkv * hd;
902        for (li, blk) in self.blocks.iter().enumerate() {
903            let mut h = x.to_vec();
904            blk.n1.apply(&mut h, hs);
905            let mut q = vec![0f32; b * nh * hd];
906            let mut k = vec![0f32; b * kvw];
907            let mut v = vec![0f32; b * kvw];
908            blk.q.matmat(&h, b, &mut q, pool);
909            blk.k.matmat(&h, b, &mut k, pool);
910            blk.v.matmat(&h, b, &mut v, pool);
911            // Per-head RMSNorm on q and k BEFORE the rotation, then
912            // split-half RoPE — Qwen3's order, not the other way round.
913            for bi in 0..b {
914                for hh in 0..nh {
915                    let s = bi * nh * hd + hh * hd;
916                    blk.qn.apply(&mut q[s..s + hd], hd);
917                    rope_half(&mut q[s..s + hd], pos, &self.inv_freq);
918                }
919                for hh in 0..nkv {
920                    let s = bi * kvw + hh * hd;
921                    blk.kn.apply(&mut k[s..s + hd], hd);
922                    rope_half(&mut k[s..s + hd], pos, &self.inv_freq);
923                }
924            }
925            let mut attn = vec![0f32; b * nh * hd];
926            let per_kv = nh / nkv;
927            for bi in 0..b {
928                let run = &mut cache[li][bi];
929                run.k.extend_from_slice(&k[bi * kvw..(bi + 1) * kvw]);
930                run.v.extend_from_slice(&v[bi * kvw..(bi + 1) * kvw]);
931                run.len += 1;
932                let n = run.len;
933                let scale = 1.0 / (hd as f32).sqrt();
934                for hh in 0..nh {
935                    let g = hh / per_kv;
936                    let qi = &q[bi * nh * hd + hh * hd..bi * nh * hd + hh * hd + hd];
937                    let mut sc = vec![0f32; n];
938                    let mut mx = f32::NEG_INFINITY;
939                    for (j, s) in sc.iter_mut().enumerate() {
940                        let kj = &run.k[j * kvw + g * hd..j * kvw + g * hd + hd];
941                        *s = qi.iter().zip(kj).map(|(a, c)| a * c).sum::<f32>() * scale;
942                        mx = mx.max(*s);
943                    }
944                    let mut sum = 0.0;
945                    for s in sc.iter_mut() {
946                        *s = (*s - mx).exp();
947                        sum += *s;
948                    }
949                    let inv = 1.0 / sum;
950                    let dst = &mut attn[bi * nh * hd + hh * hd..bi * nh * hd + hh * hd + hd];
951                    for (j, &s) in sc.iter().enumerate() {
952                        let w = s * inv;
953                        let vj = &run.v[j * kvw + g * hd..j * kvw + g * hd + hd];
954                        for (d, &vv) in dst.iter_mut().zip(vj) {
955                            *d += w * vv;
956                        }
957                    }
958                }
959            }
960            let mut proj = vec![0f32; b * hs];
961            blk.o.matmat(&attn, b, &mut proj, pool);
962            for (a, c) in x.iter_mut().zip(&proj) {
963                *a += c;
964            }
965            let mut h = x.to_vec();
966            blk.n2.apply(&mut h, hs);
967            let (mut g, mut u2) = (vec![0f32; b * self.inter], vec![0f32; b * self.inter]);
968            blk.gate.matmat(&h, b, &mut g, pool);
969            blk.up.matmat(&h, b, &mut u2, pool);
970            for (a, c) in g.iter_mut().zip(&u2) {
971                *a = silu(*a) * c;
972            }
973            let mut ffo = vec![0f32; b * hs];
974            blk.down.matmat(&g, b, &mut ffo, pool);
975            for (a, c) in x.iter_mut().zip(&ffo) {
976                *a += c;
977            }
978        }
979    }
980
981    /// Generate `frames` of conditioning: `[frames, 8·hidden]`.
982    ///
983    /// The CFG pair runs as batch 2 — the conditioned prompt and one
984    /// whose middle is replaced by `<|audio_cfg|>` — and every sampled
985    /// code is shared by both branches, which is what makes them stay
986    /// in step.
987    pub fn generate(
988        &self,
989        prompt_ids: &[u32],
990        seed: u64,
991        frames: usize,
992        mut progress: impl FnMut(usize, usize),
993    ) -> Result<(Vec<f32>, usize), String> {
994        let hs = self.hidden;
995        let pool = self.pool.as_deref();
996        let want = frames.min(self.max_frames);
997        let mut cache: Vec<Vec<KvRun>> = (0..self.blocks.len())
998            .map(|_| vec![KvRun::default(); 2])
999            .collect();
1000        // The unconditioned branch keeps the frame but not the words.
1001        let mut uncond = prompt_ids.to_vec();
1002        if uncond.len() > 3 {
1003            let n = uncond.len();
1004            for t in uncond[1..n - 2].iter_mut() {
1005                *t = tokens::AUDIO_CFG;
1006            }
1007        }
1008        let mut last = vec![0f32; 2 * hs];
1009        for (pos, (&a, &b)) in prompt_ids.iter().zip(&uncond).enumerate() {
1010            let mut x = vec![0f32; 2 * hs];
1011            x[..hs].copy_from_slice(&Self::embed_row(&self.embed_prefill, a as usize, hs));
1012            x[hs..].copy_from_slice(&Self::embed_row(&self.embed_prefill, b as usize, hs));
1013            self.step_blocks(&mut x, 2, pos, &mut cache);
1014            last = x;
1015            if pos % 64 == 0 {
1016                progress(0, want);
1017            }
1018        }
1019        let mut rng = Rng::new(seed);
1020        let mut out: Vec<f32> = Vec::with_capacity(want * self.codebooks * hs);
1021        let mut done = 0usize;
1022        let scale = (self.codebooks as f32).powf(-0.5);
1023        for frame in 0..want {
1024            let mut normed = last.clone();
1025            self.norm.apply(&mut normed, hs);
1026            // c0 with classifier-free guidance and a top-k mask taken
1027            // from the CONDITIONED logits, per the reference.
1028            let mut logits = vec![0f32; 2 * self.lm_head_rows()];
1029            self.lm_head.matmat(&normed, 2, &mut logits, pool);
1030            let vocab = self.lm_head_rows();
1031            let (cond, unc) = logits.split_at(vocab);
1032            let mut thr: Vec<f32> = cond.to_vec();
1033            thr.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap());
1034            let cut = thr[self.top_k.min(vocab) - 1];
1035            let guided: Vec<f32> = (0..vocab)
1036                .map(|i| {
1037                    if cond[i] < cut {
1038                        f32::NEG_INFINITY
1039                    } else {
1040                        unc[i] + (cond[i] - unc[i]) * self.cfg_scale
1041                    }
1042                })
1043                .collect();
1044            let code = sample_topk(&guided, self.top_k, &mut rng);
1045            if code == 0 {
1046                break; // stop token
1047            }
1048            let c0 = code - 1;
1049            let c0_embed = Self::embed_row(&self.embed_audio, c0, hs);
1050            // Depth: the remaining seven codebooks, and their hidden
1051            // states are seven eighths of what the DiT will see.
1052            let mut seq = self.depth.project(&normed[..hs]);
1053            seq.extend_from_slice(&self.depth.project(&c0_embed));
1054            let mut codes = vec![c0];
1055            let mut frame_hidden = normed[..hs].to_vec();
1056            for level in 1..self.codebooks {
1057                let n = seq.len() / hs;
1058                let h = self.depth.forward_last(&seq, n);
1059                frame_hidden.extend_from_slice(&h);
1060                let lg = self.depth.head(level, &h);
1061                let c = sample_topk(&lg, self.top_k, &mut rng);
1062                codes.push(c);
1063                if level < self.codebooks - 1 {
1064                    let e = Self::embed_row(&self.embed_extra, c + (level - 1) * self.audio_vocab, hs);
1065                    seq.extend_from_slice(&self.depth.project(&e));
1066                }
1067            }
1068            out.extend_from_slice(&frame_hidden);
1069            done += 1;
1070            progress(done, want);
1071            if done >= want {
1072                break;
1073            }
1074            // Feed the whole frame back: c0's embedding plus the extras,
1075            // scaled by 1/sqrt(codebooks).
1076            let mut fb = Self::embed_row(&self.embed_audio, codes[0], hs);
1077            for (level, &c) in codes.iter().enumerate().skip(1) {
1078                let e = Self::embed_row(&self.embed_extra, c + (level - 1) * self.audio_vocab, hs);
1079                for (a, b) in fb.iter_mut().zip(&e) {
1080                    *a += b;
1081                }
1082            }
1083            for a in fb.iter_mut() {
1084                *a *= scale;
1085            }
1086            let mut x = vec![0f32; 2 * hs];
1087            x[..hs].copy_from_slice(&fb);
1088            x[hs..].copy_from_slice(&fb);
1089            self.step_blocks(&mut x, 2, prompt_ids.len() + frame, &mut cache);
1090            last = x;
1091        }
1092        if done == 0 {
1093            return Err("MiniMax-Music-3 generated zero audio frames".into());
1094        }
1095        Ok((out, done))
1096    }
1097
1098    fn lm_head_rows(&self) -> usize {
1099        match &self.lm_head {
1100            Proj::F32 { rows, .. } => *rows,
1101            Proj::Q(q) => q.rows(),
1102        }
1103    }
1104}
1105
1106/// Split-half rotation over one head, angle `pos·inv_freq[i]`.
1107fn rope_half(x: &mut [f32], pos: usize, inv_freq: &[f32]) {
1108    let half = x.len() / 2;
1109    for i in 0..half {
1110        let (s, c) = (pos as f32 * inv_freq[i]).sin_cos();
1111        let (a, b) = (x[i], x[i + half]);
1112        x[i] = a * c - b * s;
1113        x[i + half] = b * c + a * s;
1114    }
1115}
1116
1117#[cfg(test)]
1118mod tests {
1119    use super::*;
1120
1121    /// The half that is finished, end to end: noise → Euler sampler →
1122    /// vocoder → PCM. It does not make music (the conditioning would
1123    /// come from the AR stack), but it proves the three implemented
1124    /// components compose — the sampler's σ walk, the DiT's timestep
1125    /// convention and the vocoder's hop all have to agree for the
1126    /// sample count to land, and the count is fixed by the reference.
1127    #[test]
1128    fn music3_sampler_and_vocoder_compose() {
1129        let Ok(p) = std::env::var("CMF_MUSIC3_DIT") else {
1130            eprintln!("CMF_MUSIC3_DIT unset — skipping Music-3 chain test");
1131            return;
1132        };
1133        let model = Arc::new(CmfModel::open(&p).expect("open pack"));
1134        let dit = Music3Dit::from_cmf(&model).expect("load DiT");
1135        let dav = crate::audiovae::Music3Dav::from_cmf(&model).expect("load DAV");
1136        let n = 6usize;
1137        // A fixed pseudo-noise: the test must not depend on an RNG.
1138        let noise: Vec<f32> = (0..Music3Dit::IN_CH * n)
1139            .map(|i| (((i * 2654435761) % 1000) as f32 / 500.0) - 1.0)
1140            .collect();
1141        let cond = vec![0f32; Music3Dit::COND_CH * n];
1142        let steps = 3;
1143        let mut seen = 0usize;
1144        let latent = dit.sample(&noise, &cond, n, steps, |i, t| {
1145            assert_eq!(t, steps);
1146            seen = i;
1147        });
1148        assert_eq!(seen, steps, "sampler reported every step");
1149        assert_eq!(latent.len(), Music3Dit::IN_CH * n);
1150        assert!(latent.iter().all(|v| v.is_finite()), "latent went non-finite");
1151        let pcm = dav.decode(&latent, n, None);
1152        assert_eq!(
1153            pcm.len(),
1154            n * crate::audiovae::Music3Dav::HOP * 2,
1155            "the chain's sample count is frames x 512 x 2"
1156        );
1157        assert!(pcm.iter().all(|v| v.is_finite() && v.abs() <= 1.0));
1158        let secs = (n * crate::audiovae::Music3Dav::HOP) as f32
1159            / crate::audiovae::Music3Dav::SAMPLE_RATE as f32;
1160        eprintln!(
1161            "music3 chain: {n} frames -> {} stereo samples ({secs:.3} s at 44.1 kHz)",
1162            pcm.len() / 2
1163        );
1164    }
1165
1166    /// The depth decoder must be CAUSAL — that is the only thing its
1167    /// attention mask does, and losing it lets a codebook level see its
1168    /// own answer while the model still samples plausible codes.
1169    /// Appending a position may not change any earlier output.
1170    #[test]
1171    fn music3_rvq_depth_decoder_is_causal() {
1172        let Ok(p) = std::env::var("CMF_MUSIC3_TE") else {
1173            eprintln!("CMF_MUSIC3_TE unset — skipping RVQ decoder test");
1174            return;
1175        };
1176        let model = Arc::new(CmfModel::open(&p).expect("open packed AR stack"));
1177        let dec = RvqDepthDecoder::from_cmf(&model).expect("load RVQ decoder");
1178        assert_eq!(dec.codebooks(), 8, "eight codebooks");
1179        let hs = dec.hidden;
1180        let seq: Vec<f32> = (0..3 * hs).map(|i| 0.05 * ((i as f32) * 0.013).sin()).collect();
1181        let a = dec.forward_last(&seq[..2 * hs], 2);
1182        let b = dec.forward_last(&seq, 3);
1183        assert_eq!(a.len(), hs);
1184        assert!(a.iter().all(|v| v.is_finite()) && b.iter().all(|v| v.is_finite()));
1185        // Position 1's own output is read by forward_last at n=2; adding
1186        // position 2 must leave the stack's view of 0..=1 untouched, so
1187        // re-running with the shorter prefix must agree with itself.
1188        let a2 = dec.forward_last(&seq[..2 * hs], 2);
1189        let d = a.iter().zip(&a2).map(|(x, y)| (x - y).abs()).fold(0f32, f32::max);
1190        assert!(d == 0.0, "not deterministic: {d}");
1191        let logits = dec.head(1, &b);
1192        assert_eq!(logits.len(), 1024, "audio vocab is 1024 per level");
1193        assert!(logits.iter().all(|v| v.is_finite()));
1194        let spread = logits.iter().fold(f32::MIN, |m, v| m.max(*v))
1195            - logits.iter().fold(f32::MAX, |m, v| m.min(*v));
1196        assert!(spread > 1e-3, "head is flat, spread {spread}");
1197        eprintln!("music3 rvq: 8 codebooks, head spread {spread:.3}");
1198    }
1199
1200    /// Forward the packed DiT and check what the reference fixes: a
1201    /// `[128, n]` velocity, finite, and responsive to BOTH inputs.
1202    /// `CMF_MUSIC3_DIT=<file.cmf>` points at a pack.
1203    ///
1204    /// The two response checks are the point. A forward that silently
1205    /// drops the condition — the easiest way to get the 2304-wide concat
1206    /// wrong — still returns a plausible velocity, and so does one that
1207    /// ignores the timestep token.
1208    #[test]
1209    fn music3_dit_forward_has_the_reference_geometry() {
1210        let Ok(p) = std::env::var("CMF_MUSIC3_DIT") else {
1211            eprintln!("CMF_MUSIC3_DIT unset — skipping Music-3 DiT test");
1212            return;
1213        };
1214        let model = Arc::new(CmfModel::open(&p).expect("open packed DiT"));
1215        let dit = Music3Dit::from_cmf(&model).expect("load DiT");
1216        let n = 4usize;
1217        let x: Vec<f32> = (0..Music3Dit::IN_CH * n)
1218            .map(|i| 0.3 * ((i as f32) * 0.017).sin())
1219            .collect();
1220        let cond: Vec<f32> = (0..Music3Dit::COND_CH * n)
1221            .map(|i| 0.2 * ((i as f32) * 0.011).cos())
1222            .collect();
1223        let v = dit.forward(&x, &cond, n, 0.7);
1224        assert_eq!(v.len(), Music3Dit::IN_CH * n, "velocity is [128, n]");
1225        assert!(v.iter().all(|q| q.is_finite()), "non-finite velocity");
1226        let rms = (v.iter().map(|q| q * q).sum::<f32>() / v.len() as f32).sqrt();
1227        assert!(rms > 1e-6, "velocity is silent, rms {rms}");
1228
1229        let zero_cond = vec![0f32; Music3Dit::COND_CH * n];
1230        let v0 = dit.forward(&x, &zero_cond, n, 0.7);
1231        let dc = v
1232            .iter()
1233            .zip(&v0)
1234            .map(|(a, b)| (a - b).abs())
1235            .fold(0f32, f32::max);
1236        assert!(dc > 1e-5, "condition changed nothing — concat is wrong");
1237
1238        let vt = dit.forward(&x, &cond, n, 0.2);
1239        let dt = v
1240            .iter()
1241            .zip(&vt)
1242            .map(|(a, b)| (a - b).abs())
1243            .fold(0f32, f32::max);
1244        assert!(dt > 1e-5, "timestep changed nothing — the token is lost");
1245        eprintln!("music3 dit: rms {rms:.4}, d/dcond {dc:.4}, d/dt {dt:.4}");
1246    }
1247}