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::audiovae::SendPtr;
22use crate::dit::Proj;
23use crate::pool::Pool;
24use crate::qtensor::QTensor;
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 = serde_json::from_slice(
118            model
119                .tensor_bytes("mdit.config_json")
120                .map_err(|e| e.to_string())?,
121        )
122        .map_err(|e| format!("mdit.config_json: {e}"))?;
123        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
124        let nl = u("num_layers", 36);
125        let dt = "mdit.diffusion_transformer";
126        Ok(Self {
127            pre_conv: crate::dit::cmf_f32(model, &format!("{dt}.preprocess_conv.weight"))?,
128            post_conv: crate::dit::cmf_f32(model, &format!("{dt}.postprocess_conv.weight"))?,
129            fourier: crate::dit::cmf_f32(model, &format!("{dt}.timestep_features.weight"))?,
130            t0: Proj::from_model(model, &format!("{dt}.to_timestep_embed.0.weight"))?,
131            t0_b: crate::dit::cmf_f32(model, &format!("{dt}.to_timestep_embed.0.bias"))?,
132            t2: Proj::from_model(model, &format!("{dt}.to_timestep_embed.2.weight"))?,
133            t2_b: crate::dit::cmf_f32(model, &format!("{dt}.to_timestep_embed.2.bias"))?,
134            project_in: Proj::from_model(model, &format!("{dt}.transformer.project_in.weight"))?,
135            project_out: Proj::from_model(model, &format!("{dt}.transformer.project_out.weight"))?,
136            inv_freq: crate::dit::cmf_f32(
137                model,
138                &format!("{dt}.transformer.rotary_pos_emb.inv_freq"),
139            )?,
140            blocks: (0..nl)
141                .map(|i| Block::load(model, &format!("{dt}.transformer.layers.{i}")))
142                .collect::<Result<_, _>>()?,
143            pool: Pool::from_env(),
144            hidden: u("hidden", 2048),
145            heads: u("num_heads", 32),
146            hd: u("head_dim", 64),
147            rot: u("rotary_dim", 32),
148            inter: u("ff_inner", 8192),
149            cond_logits: crate::dit::cmf_f32(model, "mdit.cond_layer_logits")?,
150            cond_scale: crate::dit::cmf_f32(model, "mdit.cond_layer_scale")?[0],
151            lc_w: crate::dit::cmf_f32(model, "mdit.latent_conditioners.0.weight")?,
152            lc_b: crate::dit::cmf_f32(model, "mdit.latent_conditioners.0.bias")?,
153        })
154    }
155
156    /// Latent frames for `audio_frames` of AR output — the reference's
157    /// `latent_length`: 44100/24000 · 960/512 = 3.4453125 per frame.
158    pub fn latent_length(audio_frames: usize) -> usize {
159        ((audio_frames as f64 * 44100.0 / 24000.0 * 960.0 / 512.0) as usize).max(1)
160    }
161
162    /// AR hidden `[frames, 8·4096]` → the DiT's condition `[2048, L]`.
163    ///
164    /// The eight are RVQ CODEBOOK levels, softmax-mixed by
165    /// `cond_layer_logits`, scaled, passed through one 3-tap conv and
166    /// then NEAREST-resampled to the latent rate. Nearest, not linear:
167    /// the reference interpolates that way and a smoother resample
168    /// smears the onset of every note.
169    pub fn aligned_condition(&self, hidden: &[f32], frames: usize) -> (Vec<f32>, usize) {
170        let levels = self.cond_logits.len();
171        let ar = hidden.len() / (frames * levels);
172        let mx = self.cond_logits.iter().cloned().fold(f32::MIN, f32::max);
173        let ex: Vec<f32> = self.cond_logits.iter().map(|v| (v - mx).exp()).collect();
174        let sum: f32 = ex.iter().sum();
175        // [ar, frames], channel-major, mixed and scaled.
176        let mut mixed = vec![0f32; ar * frames];
177        for t in 0..frames {
178            for l in 0..levels {
179                let w = ex[l] / sum * self.cond_scale;
180                let src = &hidden[(t * levels + l) * ar..(t * levels + l + 1) * ar];
181                for (c, &v) in src.iter().enumerate() {
182                    mixed[c * frames + t] += w * v;
183                }
184            }
185        }
186        // Conv1d(ar -> 2048, k=3, pad=1).
187        let out_ch = self.lc_b.len();
188        let mut conv = vec![0f32; out_ch * frames];
189        for o in 0..out_ch {
190            let dst = &mut conv[o * frames..(o + 1) * frames];
191            dst.fill(self.lc_b[o]);
192            for i in 0..ar {
193                let k = &self.lc_w[(o * ar + i) * 3..(o * ar + i + 1) * 3];
194                let src = &mixed[i * frames..(i + 1) * frames];
195                for t in 0..frames {
196                    for (j, &kv) in k.iter().enumerate() {
197                        let p = t as isize + j as isize - 1;
198                        if p >= 0 && (p as usize) < frames {
199                            dst[t] += kv * src[p as usize];
200                        }
201                    }
202                }
203            }
204        }
205        let l = Self::latent_length(frames);
206        let mut out = vec![0f32; out_ch * l];
207        for o in 0..out_ch {
208            for t in 0..l {
209                let s = (t * frames) / l.max(1);
210                out[o * l + t] = conv[o * frames + s.min(frames - 1)];
211            }
212        }
213        (out, l)
214    }
215
216    /// `[out, in]` 1×1 conv over the channel axis of a `[in, n]` panel,
217    /// added back to its input — both convs in this model are residual.
218    fn conv1x1_residual(w: &[f32], x: &mut [f32], ch: usize, n: usize) {
219        let mut acc = vec![0f32; ch * n];
220        for o in 0..ch {
221            let row = &w[o * ch..(o + 1) * ch];
222            let dst = &mut acc[o * n..(o + 1) * n];
223            for (i, &wv) in row.iter().enumerate() {
224                if wv == 0.0 {
225                    continue;
226                }
227                for (d, &s) in dst.iter_mut().zip(&x[i * n..(i + 1) * n]) {
228                    *d += wv * s;
229                }
230            }
231        }
232        for (d, a) in x.iter_mut().zip(&acc) {
233            *d += a;
234        }
235    }
236
237    /// `cat(cos, sin)` of `2π·t·w`, then the two-layer SiLU head.
238    fn timestep_embedding(&self, t: f32) -> Vec<f32> {
239        let half = self.fourier.len();
240        let mut feats = vec![0f32; half * 2];
241        for (i, &w) in self.fourier.iter().enumerate() {
242            let a = std::f32::consts::TAU * t * w;
243            feats[i] = a.cos();
244            feats[half + i] = a.sin();
245        }
246        let mut h = vec![0f32; self.t0_b.len()];
247        self.t0.matmat(&feats, 1, &mut h, self.pool.as_deref());
248        for (v, &b) in h.iter_mut().zip(&self.t0_b) {
249            *v = silu(*v + b);
250        }
251        let mut out = vec![0f32; self.t2_b.len()];
252        self.t2.matmat(&h, 1, &mut out, self.pool.as_deref());
253        for (v, &b) in out.iter_mut().zip(&self.t2_b) {
254            *v += b;
255        }
256        out
257    }
258
259    /// Split-half RoPE over the first `rot` dims of every head, applied
260    /// in place on the PACKED `[n, 3·hidden]` panel — q at column 0, k
261    /// at column `hidden`, v untouched. The panel stays packed because
262    /// that is the layout the device's split kernel reads.
263    ///
264    /// Two things this does that the twin does not. The angle depends
265    /// only on the position, so `sin_cos` is hoisted out of the head
266    /// loop — it was being recomputed `heads` times for every one of
267    /// them. And tokens own disjoint rows, so the pool splits it.
268    fn rope_packed(&self, qkv: &mut [f32], n: usize) {
269        let (hs, nh, hd) = (self.hidden, self.heads, self.hd);
270        let half = self.rot / 2;
271        let ptr = SendPtr(qkv.as_mut_ptr());
272        let work = |lo: usize, hi: usize| {
273            for p in lo..hi {
274                let base = p * 3 * hs;
275                for i in 0..half {
276                    let (s, c) = (p as f32 * self.inv_freq[i]).sin_cos();
277                    for h in 0..nh {
278                        for off in [base + h * hd, base + hs + h * hd] {
279                            // SAFETY: token `p` owns this row of the panel.
280                            let x = unsafe { ptr.row(off, hd) };
281                            let (a, b) = (x[i], x[i + half]);
282                            x[i] = a * c - b * s;
283                            x[i + half] = a * s + b * c;
284                        }
285                    }
286                }
287            }
288        };
289        match self.pool.as_deref() {
290            Some(pl) => pl.run_rows(n, &work),
291            None => work(0, n),
292        }
293    }
294
295    /// One block over `[n, hidden]`, in place.
296    fn block(&self, blk: &Block, x: &mut [f32], n: usize) {
297        let (hs, nh, hd) = (self.hidden, self.heads, self.hd);
298        let pool = self.pool.as_deref();
299        let mut h = x.to_vec();
300        blk.pre_norm.apply(&mut h, hs);
301        // The whole attention half on the card first: qkv GEMM, rope,
302        // attention and the out projection with one readback at the end.
303        // The unfused arm below reads the qkv panel back (17 MB), ships
304        // it up again for the attention, and round-trips the attention
305        // output through the out GEMM — ~45 MB a block-step on a stand
306        // whose split timer put transfers at 82% of the device arm.
307        // eps = -1 is the rope-only sentinel: this model has no qk-norm.
308        // CMF_MUSIC3_DEVATT=0 kills it; the host chain is bit-for-bit
309        // the same math.
310        if std::env::var("CMF_MUSIC3_DEVATT").as_deref() != Ok("0") {
311            if let (Some((m1, i1)), Some((m2, i2))) = (
312                match &blk.qkv {
313                    crate::dit::Proj::Q(q) => q.q4tp_mapped(),
314                    _ => None,
315                },
316                match &blk.out {
317                    crate::dit::Proj::Q(q) => q.q4tp_mapped(),
318                    _ => None,
319                },
320            ) {
321                if std::sync::Arc::ptr_eq(m1, m2) {
322                    let half = self.rot / 2;
323                    let mut ang = vec![0f32; n * half];
324                    for p in 0..n {
325                        for (i, f) in self.inv_freq[..half].iter().enumerate() {
326                            ang[p * half + i] = p as f32 * f;
327                        }
328                    }
329                    let ones = vec![1.0f32; hd];
330                    let scale = 1.0 / (hd as f32).sqrt();
331                    let mut proj = vec![0f32; n * hs];
332                    let _ta = prof::start();
333                    if crate::gpu::dit_qkv_attn_out(
334                        m1,
335                        i1,
336                        i2,
337                        &h,
338                        n,
339                        hs,
340                        nh,
341                        hd,
342                        scale,
343                        (&ang, &ones, &ones, -1.0),
344                        &mut proj,
345                    ) {
346                        prof::add(&prof::ATTN, _ta);
347                        for (a, b) in x.iter_mut().zip(&proj) {
348                            *a += b;
349                        }
350                        return self.block_ffn(blk, x, n);
351                    }
352                }
353            }
354        }
355        let mut qkv = vec![0f32; n * 3 * hs];
356        let _t = prof::start();
357        blk.qkv.matmat(&h, n, &mut qkv, pool);
358        prof::add(&prof::QKV, _t);
359        // to_qkv emits q|k|v concatenated along the FEATURE axis, so the
360        // three live at column offsets, not row offsets — which is
361        // exactly the packed layout the device's split kernel reads
362        // (`mode 0`). Rope goes on in place; nothing is unpacked here.
363        self.rope_packed(&mut qkv, n);
364        let scale = 1.0 / (hd as f32).sqrt();
365        let mut attn = vec![0f32; n * hs];
366        let _ta = prof::start();
367        // The device first. This is the quadratic part and the reason a
368        // CPU-only run was beating a GPU one: everything around it went
369        // to the card and the biggest single stage stayed home. The
370        // engine's kernel wants HEAD-major panels where the projections
371        // leave them token-major, and three transposes of n x hs are
372        // noise against n^2 work.
373        {
374            // The packed kernel is wgpu's; Metal carries only the
375            // head-major `dit_attention`, so a refusal here must fall to
376            // THAT and not to the host loop — routing everything at the
377            // packed arm would have quietly taken the attention off the
378            // card on every Mac.
379            if crate::gpu::dit_attention_packed(&qkv, nh, n, hd, scale, None, &mut attn) {
380                prof::add(&prof::ATTN, _ta);
381                let mut proj = vec![0f32; n * hs];
382                let _t = prof::start();
383                blk.out.matmat(&attn, n, &mut proj, pool);
384                prof::add(&prof::OUT, _t);
385                for (a, b) in x.iter_mut().zip(&proj) {
386                    *a += b;
387                }
388                return self.block_ffn(blk, x, n);
389            }
390            attn.fill(0.0);
391        }
392        // Everything past the packed arm wants the three panels apart.
393        let mut q = vec![0f32; n * hs];
394        let mut k = vec![0f32; n * hs];
395        let mut v = vec![0f32; n * hs];
396        for p in 0..n {
397            let s = &qkv[p * 3 * hs..(p + 1) * 3 * hs];
398            q[p * hs..(p + 1) * hs].copy_from_slice(&s[..hs]);
399            k[p * hs..(p + 1) * hs].copy_from_slice(&s[hs..2 * hs]);
400            v[p * hs..(p + 1) * hs].copy_from_slice(&s[2 * hs..]);
401        }
402        // Metal's arm: head-major panels, three transposes of n x hs
403        // against n^2 work. This is what the packed path spares wgpu,
404        // and what a Mac still needs until the split kernel is ported.
405        if crate::gpu::enabled_here() {
406            let mut qh = vec![0f32; n * hs];
407            let mut kh = vec![0f32; n * hs];
408            let mut vh = vec![0f32; n * hs];
409            for h in 0..nh {
410                for i in 0..n {
411                    let (s, d) = (i * hs + h * hd, (h * n + i) * hd);
412                    qh[d..d + hd].copy_from_slice(&q[s..s + hd]);
413                    kh[d..d + hd].copy_from_slice(&k[s..s + hd]);
414                    vh[d..d + hd].copy_from_slice(&v[s..s + hd]);
415                }
416            }
417            if crate::gpu::dit_attention(&qh, &kh, &vh, nh, nh, n, hd, scale, &mut attn) {
418                prof::add(&prof::ATTN, _ta);
419                let mut proj = vec![0f32; n * hs];
420                let _t = prof::start();
421                blk.out.matmat(&attn, n, &mut proj, pool);
422                prof::add(&prof::OUT, _t);
423                for (a, b) in x.iter_mut().zip(&proj) {
424                    *a += b;
425                }
426                return self.block_ffn(blk, x, n);
427            }
428            attn.fill(0.0);
429        }
430        // Heads are independent and write disjoint columns, so the host
431        // arm splits without a lock.
432        {
433            let ptr = SendPtr(attn.as_mut_ptr());
434            let work = |lo: usize, hi: usize| {
435                let mut scores = vec![0f32; n];
436                for hh in lo..hi {
437                    for i in 0..n {
438                        let qi = &q[i * hs + hh * hd..i * hs + hh * hd + hd];
439                        let mut mx = f32::NEG_INFINITY;
440                        for (j, sc) in scores.iter_mut().enumerate() {
441                            let kj = &k[j * hs + hh * hd..j * hs + hh * hd + hd];
442                            *sc = qi.iter().zip(kj).map(|(a, b)| a * b).sum::<f32>() * scale;
443                            mx = mx.max(*sc);
444                        }
445                        let mut sum = 0.0;
446                        for sc in scores.iter_mut() {
447                            *sc = (*sc - mx).exp();
448                            sum += *sc;
449                        }
450                        let inv = 1.0 / sum;
451                        // SAFETY: head `hh` owns these columns of every row.
452                        let dst = unsafe { ptr.row(i * hs + hh * hd, hd) };
453                        for (j, &sc) in scores.iter().enumerate() {
454                            let w = sc * inv;
455                            let vj = &v[j * hs + hh * hd..j * hs + hh * hd + hd];
456                            for (d, &vv) in dst.iter_mut().zip(vj) {
457                                *d += w * vv;
458                            }
459                        }
460                    }
461                }
462            };
463            match pool {
464                Some(p) => p.run_rows(nh, &work),
465                None => work(0, nh),
466            }
467        }
468        prof::add(&prof::ATTN, _ta);
469        let mut proj = vec![0f32; n * hs];
470        let _t = prof::start();
471        blk.out.matmat(&attn, n, &mut proj, pool);
472        prof::add(&prof::OUT, _t);
473        for (a, b) in x.iter_mut().zip(&proj) {
474            *a += b;
475        }
476        self.block_ffn(blk, x, n)
477    }
478
479    /// The second half of a block: norm, GEGLU, residual.
480    fn block_ffn(&self, blk: &Block, x: &mut [f32], n: usize) {
481        let _t = prof::start();
482        let hs = self.hidden;
483        let pool = self.pool.as_deref();
484        let mut h = x.to_vec();
485        blk.ff_norm.apply(&mut h, hs);
486        // The resident chain first: both GEMMs and the GLU on the card,
487        // with only `h` up and the block's output back. The host arm of
488        // this exchange moved 68 MB more per block-step, and the split
489        // timer put transfers at 82% of the device arm's time on the
490        // stand this was tuned on. CMF_MUSIC3_DEVFFN=0 kills it.
491        if std::env::var("CMF_MUSIC3_DEVFFN").as_deref() != Ok("0") {
492            if let (Some((m1, i1)), Some((m2, i2))) = (
493                match &blk.ff_in {
494                    crate::dit::Proj::Q(q) => q.q4tp_mapped(),
495                    _ => None,
496                },
497                match &blk.ff_out {
498                    crate::dit::Proj::Q(q) => q.q4tp_mapped(),
499                    _ => None,
500                },
501            ) {
502                if std::sync::Arc::ptr_eq(m1, m2) {
503                    let mut ffo = vec![0f32; n * hs];
504                    if crate::gpu::music3_ffn(
505                        m1,
506                        i1,
507                        i2,
508                        &h,
509                        &blk.ff_in_b,
510                        n,
511                        hs,
512                        self.inter,
513                        &mut ffo,
514                    ) {
515                        for p in 0..n {
516                            for j in 0..hs {
517                                x[p * hs + j] += ffo[p * hs + j] + blk.ff_out_b[j];
518                            }
519                        }
520                        prof::add(&prof::FFN, _t);
521                        return;
522                    }
523                }
524            }
525        }
526        let mut gu = vec![0f32; n * 2 * self.inter];
527        blk.ff_in.matmat(&h, n, &mut gu, pool);
528        // GLU here is `value * silu(gate)` with VALUE first, and the
529        // projection's bias belongs to BOTH halves before they meet.
530        // Swapping the halves still makes sound, which is why this is
531        // spelled out rather than inferred.
532        let inter = self.inter;
533        let (vb, gb) = blk.ff_in_b.split_at(inter);
534        let mut act = vec![0f32; n * inter];
535        for p in 0..n {
536            let row = &gu[p * 2 * inter..(p + 1) * 2 * inter];
537            let (val, gate) = row.split_at(inter);
538            let dst = &mut act[p * inter..(p + 1) * inter];
539            for i in 0..inter {
540                dst[i] = (val[i] + vb[i]) * silu(gate[i] + gb[i]);
541            }
542        }
543        let mut ffo = vec![0f32; n * hs];
544        blk.ff_out.matmat(&act, n, &mut ffo, pool);
545        for p in 0..n {
546            for j in 0..hs {
547                x[p * hs + j] += ffo[p * hs + j] + blk.ff_out_b[j];
548            }
549        }
550        prof::add(&prof::FFN, _t);
551    }
552
553    /// Latent frames the transformer will attend across in one go, and
554    /// the stride it advances by — `latent_length(200)` and
555    /// `latent_length(100)` in the reference. Attention is quadratic, so
556    /// a whole song in one pass is not merely slow, it is not what the
557    /// model was run as.
558    pub const WINDOW: usize = 689;
559    pub const HOP: usize = 344;
560
561    /// The velocity over any length, windowed like the reference:
562    /// overlapping passes averaged by how many covered each frame.
563    pub fn forward_windowed(&self, x: &[f32], condition: &[f32], n: usize, t: f32) -> Vec<f32> {
564        if n <= Self::WINDOW {
565            return self.forward(x, condition, n, t);
566        }
567        let ch = Self::IN_CH;
568        let cc = Self::COND_CH;
569        let mut out = vec![0f32; ch * n];
570        let mut count = vec![0f32; n];
571        let mut start = 0usize;
572        loop {
573            let end = (start + Self::WINDOW).min(n);
574            let w = end - start;
575            let mut xw = vec![0f32; ch * w];
576            for c in 0..ch {
577                xw[c * w..(c + 1) * w].copy_from_slice(&x[c * n + start..c * n + end]);
578            }
579            let mut cw = vec![0f32; cc * w];
580            for c in 0..cc {
581                cw[c * w..(c + 1) * w].copy_from_slice(&condition[c * n + start..c * n + end]);
582            }
583            let v = self.forward(&xw, &cw, w, t);
584            for c in 0..ch {
585                for i in 0..w {
586                    out[c * n + start + i] += v[c * w + i];
587                }
588            }
589            for i in 0..w {
590                count[start + i] += 1.0;
591            }
592            if end == n {
593                break;
594            }
595            start += Self::HOP;
596        }
597        for c in 0..ch {
598            for i in 0..n {
599                out[c * n + i] /= count[i];
600            }
601        }
602        out
603    }
604
605    /// `x` is `[128, n]` and `condition` `[2048, n]`; the result is the
606    /// velocity at `[128, n]`.
607    pub fn forward(&self, x: &[f32], condition: &[f32], n: usize, t: f32) -> Vec<f32> {
608        let pool = self.pool.as_deref();
609        let mut full = vec![0f32; Self::CONCAT_CH * n];
610        full[..Self::IN_CH * n].copy_from_slice(x);
611        // rows 128..256 stay zero: the reference's `zeros_like(x)` plane
612        full[2 * Self::IN_CH * n..].copy_from_slice(condition);
613        Self::conv1x1_residual(&self.pre_conv, &mut full, Self::CONCAT_CH, n);
614
615        // channel-major -> token-major for the transformer
616        let mut toks = vec![0f32; n * Self::CONCAT_CH];
617        for c in 0..Self::CONCAT_CH {
618            for p in 0..n {
619                toks[p * Self::CONCAT_CH + c] = full[c * n + p];
620            }
621        }
622        let mut h = vec![0f32; n * self.hidden];
623        self.project_in.matmat(&toks, n, &mut h, pool);
624
625        // The timestep rides as token 0 and shifts every rotary position.
626        let temb = self.timestep_embedding(t);
627        let mut seq = vec![0f32; (n + 1) * self.hidden];
628        seq[..self.hidden].copy_from_slice(&temb);
629        seq[self.hidden..].copy_from_slice(&h);
630        for blk in &self.blocks {
631            self.block(blk, &mut seq, n + 1);
632        }
633
634        let mut out = vec![0f32; n * Self::IN_CH];
635        self.project_out
636            .matmat(&seq[self.hidden..], n, &mut out, pool);
637        let mut ch = vec![0f32; Self::IN_CH * n];
638        for c in 0..Self::IN_CH {
639            for p in 0..n {
640                ch[c * n + p] = out[p * Self::IN_CH + c];
641            }
642        }
643        Self::conv1x1_residual(&self.post_conv, &mut ch, Self::IN_CH, n);
644        for v in ch.iter_mut() {
645            *v = -*v;
646        }
647        ch
648    }
649
650    /// Denoise `[128, n]` from noise to a latent, `steps` Euler steps
651    /// along σ: 1 → 0. `progress` is called with (step, total).
652    pub fn sample(
653        &self,
654        noise: &[f32],
655        condition: &[f32],
656        n: usize,
657        steps: usize,
658        mut progress: impl FnMut(usize, usize),
659    ) -> Vec<f32> {
660        let sigmas = flow_sigmas(steps);
661        let mut x = noise.to_vec();
662        for i in 0..steps {
663            let (s, s_next) = (sigmas[i], sigmas[i + 1]);
664            // ComfyUI's process_timestep for this model.
665            let v = self.forward_windowed(&x, condition, n, 1.0 - s);
666            let dt = s_next - s;
667            for (a, b) in x.iter_mut().zip(&v) {
668                *a += dt * b;
669            }
670            progress(i + 1, steps);
671        }
672        x
673    }
674}
675
676/// Euler flow-matching sampler for Music-3.
677///
678/// ComfyUI registers this model as a plain `ModelType.FLOW` with
679/// `multiplier: 1.0` and `process_timestep(t) = 1.0 - t`, so the sampler
680/// is the ordinary one and NOT the `FlowMatchEulerDiscreteScheduler`
681/// named in MiniMax's own `scheduler_config.json` — that belongs to
682/// their diffusers pipeline. Worth stating because the config is the
683/// first thing you find and it sends you somewhere else: with
684/// `num_train_timesteps: 1` its schedule degenerates to a constant,
685/// which is the tell that the caller supplies the sigmas.
686///
687/// σ walks 1 → 0, the DiT is asked at `1 − σ`, and the step is
688/// `x += (σ_next − σ)·v`. The DiT already negates its own output, so
689/// the sign lives there rather than here.
690///
691/// The walk is NOT uniform, and that detail is audible. ComfyUI's
692/// `normal_scheduler` evaluates at `linspace(σ_max, σ_min, steps)` and
693/// only THEN appends zero, and this model's `ModelSamplingDiscreteFlow`
694/// has `σ_min = 1/1000` — so the last velocity is measured essentially
695/// at the end of the trajectory. A uniform `1 → 0` in `steps` stops at
696/// `1/steps` and integrates the whole remaining tail from a velocity
697/// sampled well before it, which is a smeared, mushy final approach.
698pub fn flow_sigmas(steps: usize) -> Vec<f32> {
699    const SIGMA_MIN: f32 = 0.001;
700    let n = steps.max(1);
701    let mut s: Vec<f32> = (0..n)
702        .map(|i| {
703            if n == 1 {
704                1.0
705            } else {
706                1.0 + (SIGMA_MIN - 1.0) * i as f32 / (n - 1) as f32
707            }
708        })
709        .collect();
710    s.push(0.0);
711    s
712}
713
714/// RMSNorm with a weight and no bias, eps 1e-6.
715struct RmsNorm {
716    w: Vec<f32>,
717}
718
719impl RmsNorm {
720    fn load(model: &Arc<CmfModel>, n: &str) -> Result<Self, String> {
721        Ok(Self {
722            w: crate::dit::cmf_f32(model, n)?,
723        })
724    }
725
726    fn apply(&self, x: &mut [f32], d: usize) {
727        for row in x.chunks_exact_mut(d) {
728            let ss = row.iter().map(|v| (*v as f64) * (*v as f64)).sum::<f64>() / d as f64;
729            let inv = 1.0 / (ss + 1e-6).sqrt();
730            for (v, &g) in row.iter_mut().zip(&self.w) {
731                *v = (*v as f64 * inv) as f32 * g;
732            }
733        }
734    }
735}
736
737struct RvqBlock {
738    n1: RmsNorm,
739    q: Proj,
740    k: Proj,
741    v: Proj,
742    o: Proj,
743    n2: RmsNorm,
744    gate: Proj,
745    up: Proj,
746    down: Proj,
747}
748
749impl RvqBlock {
750    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
751        Ok(Self {
752            n1: RmsNorm::load(model, &format!("{p}.input_layernorm.weight"))?,
753            q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
754            k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
755            v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
756            o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.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/// The RVQ depth decoder: given the frame's hidden state and the codes
766/// chosen so far, it predicts the next codebook level.
767///
768/// It is a small CAUSAL transformer over a sequence that never exceeds
769/// the codebook count — the positional table is 16 rows for 8 levels —
770/// with no rotary embedding at all. Attention that forgets the mask here
771/// leaks a level's own answer backwards and the model still samples.
772pub struct RvqDepthDecoder {
773    projection: Proj,
774    pos: Vec<f32>,
775    blocks: Vec<RvqBlock>,
776    norm: RmsNorm,
777    heads: Vec<Proj>,
778    pool: Option<Arc<Pool>>,
779    hidden: usize,
780    nh: usize,
781    hd: usize,
782    inter: usize,
783}
784
785impl RvqDepthDecoder {
786    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
787        let cfg: serde_json::Value = serde_json::from_slice(
788            model
789                .tensor_bytes("mte.config_json")
790                .map_err(|e| e.to_string())?,
791        )
792        .map_err(|e| format!("mte.config_json: {e}"))?;
793        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
794        let hidden = u("hidden_size", 4096);
795        let nl = u("decoder_num_layers", 4);
796        let nh = u("decoder_num_heads", 16);
797        let cb = u("audio_num_codebooks", 8);
798        Ok(Self {
799            projection: Proj::from_model(model, "mte.audio_decoder.projection.weight")?,
800            pos: crate::dit::cmf_f32(model, "mte.audio_decoder.pos_embedding.weight")?,
801            blocks: (0..nl)
802                .map(|i| RvqBlock::load(model, &format!("mte.audio_decoder.layers.{i}")))
803                .collect::<Result<_, _>>()?,
804            norm: RmsNorm::load(model, "mte.audio_decoder.norm.weight")?,
805            heads: (0..cb - 1)
806                .map(|i| {
807                    Proj::from_model(model, &format!("mte.audio_decoder.audio_heads.{i}.weight"))
808                })
809                .collect::<Result<_, _>>()?,
810            pool: Pool::from_env(),
811            hidden,
812            nh,
813            hd: hidden / nh,
814            inter: u("decoder_intermediate_size", 6144),
815        })
816    }
817
818    pub fn codebooks(&self) -> usize {
819        self.heads.len() + 1
820    }
821
822    /// `projection` is applied to every element entering the sequence —
823    /// the frame hidden, the c0 embedding and each extra embedding.
824    pub fn project(&self, x: &[f32]) -> Vec<f32> {
825        let n = x.len() / self.hidden;
826        let mut out = vec![0f32; n * self.hidden];
827        self.projection.matmat(x, n, &mut out, self.pool.as_deref());
828        out
829    }
830
831    /// Run the stack over `[n, hidden]` and return the LAST position's
832    /// normed hidden — the only one the caller reads.
833    pub fn forward_last(&self, seq: &[f32], n: usize) -> Vec<f32> {
834        let (hs, nh, hd) = (self.hidden, self.nh, self.hd);
835        let pool = self.pool.as_deref();
836        let mut x = seq.to_vec();
837        for p in 0..n {
838            for (v, &pe) in x[p * hs..(p + 1) * hs]
839                .iter_mut()
840                .zip(&self.pos[p * hs..(p + 1) * hs])
841            {
842                *v += pe;
843            }
844        }
845        for blk in &self.blocks {
846            let mut h = x.clone();
847            blk.n1.apply(&mut h, hs);
848            let (mut q, mut k, mut v) =
849                (vec![0f32; n * hs], vec![0f32; n * hs], vec![0f32; n * hs]);
850            blk.q.matmat(&h, n, &mut q, pool);
851            blk.k.matmat(&h, n, &mut k, pool);
852            blk.v.matmat(&h, n, &mut v, pool);
853            let scale = 1.0 / (hd as f32).sqrt();
854            let mut attn = vec![0f32; n * hs];
855            for hh in 0..nh {
856                for i in 0..n {
857                    let qi = &q[i * hs + hh * hd..i * hs + hh * hd + hd];
858                    // Causal: position i sees 0..=i and nothing after.
859                    let mut sc = vec![0f32; i + 1];
860                    let mut mx = f32::NEG_INFINITY;
861                    for (j, s) in sc.iter_mut().enumerate() {
862                        let kj = &k[j * hs + hh * hd..j * hs + hh * hd + hd];
863                        *s = qi.iter().zip(kj).map(|(a, b)| a * b).sum::<f32>() * scale;
864                        mx = mx.max(*s);
865                    }
866                    let mut sum = 0.0;
867                    for s in sc.iter_mut() {
868                        *s = (*s - mx).exp();
869                        sum += *s;
870                    }
871                    let inv = 1.0 / sum;
872                    let dst = &mut attn[i * hs + hh * hd..i * hs + hh * hd + hd];
873                    for (j, &s) in sc.iter().enumerate() {
874                        let w = s * inv;
875                        let vj = &v[j * hs + hh * hd..j * hs + hh * hd + hd];
876                        for (d, &vv) in dst.iter_mut().zip(vj) {
877                            *d += w * vv;
878                        }
879                    }
880                }
881            }
882            let mut proj = vec![0f32; n * hs];
883            blk.o.matmat(&attn, n, &mut proj, pool);
884            for (a, b) in x.iter_mut().zip(&proj) {
885                *a += b;
886            }
887
888            let mut h = x.clone();
889            blk.n2.apply(&mut h, hs);
890            let (mut g, mut u) = (vec![0f32; n * self.inter], vec![0f32; n * self.inter]);
891            blk.gate.matmat(&h, n, &mut g, pool);
892            blk.up.matmat(&h, n, &mut u, pool);
893            for (a, b) in g.iter_mut().zip(&u) {
894                *a = silu(*a) * b;
895            }
896            let mut ffo = vec![0f32; n * hs];
897            blk.down.matmat(&g, n, &mut ffo, pool);
898            for (a, b) in x.iter_mut().zip(&ffo) {
899                *a += b;
900            }
901        }
902        let mut last = x[(n - 1) * hs..].to_vec();
903        self.norm.apply(&mut last, hs);
904        last
905    }
906
907    /// Logits for codebook `level` (1-based; level 1 uses head 0).
908    pub fn head(&self, level: usize, hidden: &[f32]) -> Vec<f32> {
909        let h = &self.heads[level - 1];
910        let rows = match h {
911            Proj::F32 { rows, .. } => *rows,
912            Proj::Q(q) => q.rows(),
913        };
914        let mut out = vec![0f32; rows];
915        h.matmat(hidden, 1, &mut out, self.pool.as_deref());
916        out
917    }
918}
919
920// ── the autoregressive stack ────────────────────────────────────────
921
922/// One Qwen3 block: RMSNorm → GQA attention with per-head q/k norms and
923/// split-half RoPE → residual → RMSNorm → SwiGLU → residual.
924struct ArBlock {
925    n1: RmsNorm,
926    q: Proj,
927    k: Proj,
928    v: Proj,
929    o: Proj,
930    qn: RmsNorm,
931    kn: RmsNorm,
932    n2: RmsNorm,
933    gate: Proj,
934    up: Proj,
935    down: Proj,
936}
937
938impl ArBlock {
939    fn load(model: &Arc<CmfModel>, p: &str) -> Result<Self, String> {
940        Ok(Self {
941            n1: RmsNorm::load(model, &format!("{p}.input_layernorm.weight"))?,
942            q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
943            k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
944            v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
945            o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
946            qn: RmsNorm::load(model, &format!("{p}.self_attn.q_norm.weight"))?,
947            kn: RmsNorm::load(model, &format!("{p}.self_attn.k_norm.weight"))?,
948            n2: RmsNorm::load(model, &format!("{p}.post_attention_layernorm.weight"))?,
949            gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
950            up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
951            down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
952        })
953    }
954}
955
956/// Keys and values for one layer, one CFG branch.
957///
958/// Per HEAD, because that is the shape the token graph mirrors from:
959/// it uploads `cpu_k[h][synced..position]` into its device cache each
960/// step. A flat `[pos][nkv·hd]` buffer would have to be transposed on
961/// every token to hand it over.
962#[derive(Default, Clone)]
963struct KvRun {
964    k: Vec<Vec<f32>>,
965    v: Vec<Vec<f32>>,
966    len: usize,
967}
968
969impl KvRun {
970    fn init(nkv: usize) -> Self {
971        Self {
972            k: vec![Vec::new(); nkv],
973            v: vec![Vec::new(); nkv],
974            len: 0,
975        }
976    }
977}
978
979/// Deterministic top-k sampler.
980///
981/// NOT torch's: reproducing `torch.multinomial` under a seeded
982/// `Generator` bit-for-bit is its own project, and nothing downstream
983/// needs the same seed to mean the same song — only that one seed here
984/// always means one song.
985struct Rng(u64);
986
987impl Rng {
988    fn new(seed: u64) -> Self {
989        Self(seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1)
990    }
991    fn next_f32(&mut self) -> f32 {
992        let mut x = self.0;
993        x ^= x << 13;
994        x ^= x >> 7;
995        x ^= x << 17;
996        self.0 = x;
997        ((x >> 40) as f32) / (1u32 << 24) as f32
998    }
999}
1000
1001fn sample_topk(logits: &[f32], top_k: usize, rng: &mut Rng) -> usize {
1002    let mut idx: Vec<usize> = (0..logits.len())
1003        .filter(|&i| logits[i].is_finite())
1004        .collect();
1005    idx.sort_unstable_by(|&a, &b| logits[b].partial_cmp(&logits[a]).unwrap());
1006    idx.truncate(top_k.max(1));
1007    let mx = idx.iter().map(|&i| logits[i]).fold(f32::MIN, f32::max);
1008    let exps: Vec<f32> = idx.iter().map(|&i| (logits[i] - mx).exp()).collect();
1009    let sum: f32 = exps.iter().sum();
1010    let mut r = rng.next_f32() * sum;
1011    for (j, &e) in exps.iter().enumerate() {
1012        r -= e;
1013        if r <= 0.0 {
1014            return idx[j];
1015        }
1016    }
1017    *idx.last().unwrap()
1018}
1019
1020/// MiniMax-Music-3's AR stack: it does not encode a prompt, it GENERATES
1021/// the conditioning — audio tokens sampled frame by frame, whose hidden
1022/// states become what the DiT is conditioned on.
1023pub struct Music3Ar {
1024    blocks: Vec<ArBlock>,
1025    norm: RmsNorm,
1026    embed_prefill: QTensor,
1027    embed_audio: QTensor,
1028    embed_extra: QTensor,
1029    lm_head: Proj,
1030    pub depth: RvqDepthDecoder,
1031    inv_freq: Vec<f32>,
1032    pool: Option<Arc<Pool>>,
1033    hidden: usize,
1034    nh: usize,
1035    nkv: usize,
1036    hd: usize,
1037    inter: usize,
1038    audio_vocab: usize,
1039    codebooks: usize,
1040    pub cfg_scale: f32,
1041    pub top_k: usize,
1042    pub fps: usize,
1043    pub max_frames: usize,
1044}
1045
1046/// Token ids the prompt is built from — `comfy/ldm/minimax_music/prompt.py`.
1047pub mod tokens {
1048    pub const IM_START: u32 = 151644;
1049    pub const IM_END: u32 = 151645;
1050    pub const AUDIO_CFG: u32 = 151654;
1051    pub const AUDIO_START: u32 = 151669;
1052    pub const CAPTION_START: u32 = 151671;
1053    pub const CAPTION_END: u32 = 151672;
1054    pub const LYRICS_START: u32 = 151673;
1055    pub const LYRICS_END: u32 = 151674;
1056}
1057
1058impl Music3Ar {
1059    pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
1060        let cfg: serde_json::Value = serde_json::from_slice(
1061            model
1062                .tensor_bytes("mte.config_json")
1063                .map_err(|e| e.to_string())?,
1064        )
1065        .map_err(|e| format!("mte.config_json: {e}"))?;
1066        let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
1067        let f = |k: &str, d: f64| cfg[k].as_f64().unwrap_or(d);
1068        let hidden = u("hidden_size", 4096);
1069        let hd = u("head_dim", 128);
1070        let theta = f("rope_theta", 1_000_000.0) as f32;
1071        Ok(Self {
1072            blocks: (0..u("num_hidden_layers", 36))
1073                .map(|i| ArBlock::load(model, &format!("mte.layers.{i}")))
1074                .collect::<Result<_, _>>()?,
1075            norm: RmsNorm::load(model, "mte.norm.weight")?,
1076            embed_prefill: QTensor::from_model(model, "mte.embed_tokens_prefill.weight")?,
1077            embed_audio: QTensor::from_model(model, "mte.embed_tokens_audio.weight")?,
1078            embed_extra: QTensor::from_model(model, "mte.audio_extra_embedding.weight")?,
1079            lm_head: Proj::from_model(model, "mte.lm_head_pruned.weight")?,
1080            depth: RvqDepthDecoder::from_cmf(model)?,
1081            inv_freq: (0..hd / 2)
1082                .map(|i| 1.0 / theta.powf(2.0 * i as f32 / hd as f32))
1083                .collect(),
1084            pool: Pool::from_env(),
1085            hidden,
1086            nh: u("num_attention_heads", 32),
1087            nkv: u("num_key_value_heads", 8),
1088            hd,
1089            inter: u("intermediate_size", 12288),
1090            audio_vocab: u("audio_vocab_size", 1024),
1091            codebooks: u("audio_num_codebooks", 8),
1092            cfg_scale: f("cfg_scale", 1.5) as f32,
1093            top_k: u("top_k", 50),
1094            fps: u("audio_frames_per_second", 25),
1095            max_frames: u("max_audio_frames", 9000),
1096        })
1097    }
1098
1099    /// One embedding row. A table lookup, not a matmul: these are
1100    /// `[vocab, hidden]` and only ever read one row at a time.
1101    fn embed_row(p: &QTensor, row: usize, hidden: usize) -> Vec<f32> {
1102        let mut out = vec![0f32; hidden];
1103        p.row_f32(row, &mut out);
1104        out
1105    }
1106
1107    /// One decode position through the WHOLE stack in a single device
1108    /// submission, per CFG branch.
1109    ///
1110    /// The op-by-op path spends ~9 µs of arithmetic behind each of five
1111    /// dispatches a layer — 360 round trips a token — and the card idles
1112    /// between them however cheap the trip is. `forward_token_graph`
1113    /// already solves exactly this for text models: it keeps K/V on the
1114    /// device per `(kv_id, layer)`, mirrors the newly appended positions
1115    /// from the host cache, and folds the final norm and lm_head into
1116    /// the same submit. Qwen3 is its native shape, q/k norms included,
1117    /// so no kernel is new here — only the wiring.
1118    ///
1119    /// Returns the logits per branch when the graph took the token.
1120    #[allow(clippy::type_complexity)]
1121    fn graph_step(
1122        &self,
1123        x: &mut [f32],
1124        pos: usize,
1125        cap: usize,
1126        cache: &mut [Vec<KvRun>],
1127        logits: &mut [Vec<f32>; 2],
1128    ) -> bool {
1129        let hs = self.hidden;
1130        let Some((model, _)) = self.blocks.first().and_then(|b| b.q.graph_w()) else {
1131            return false;
1132        };
1133        let model = model.clone();
1134        let Some((_, lm)) = self.lm_head.graph_w() else {
1135            return false;
1136        };
1137        let lm_rows = self.lm_head_rows();
1138        let mut staged: [(Vec<f32>, Vec<f32>); 2] = Default::default();
1139        for bi in 0..2 {
1140            let mut layers: Vec<crate::gpu::GraphLayer> = Vec::with_capacity(self.blocks.len());
1141            for (li, blk) in self.blocks.iter().enumerate() {
1142                let (Some((_, wq)), Some((_, wk)), Some((_, wv)), Some((_, wo))) = (
1143                    blk.q.graph_w(),
1144                    blk.k.graph_w(),
1145                    blk.v.graph_w(),
1146                    blk.o.graph_w(),
1147                ) else {
1148                    return false;
1149                };
1150                let (Some((_, g)), Some((_, u)), Some((_, d))) =
1151                    (blk.gate.graph_w(), blk.up.graph_w(), blk.down.graph_w())
1152                else {
1153                    return false;
1154                };
1155                layers.push(crate::gpu::GraphLayer {
1156                    input_norm: &blk.n1.w,
1157                    attn: crate::gpu::GraphAttn::Full {
1158                        wq,
1159                        wk,
1160                        wv,
1161                        wo,
1162                        q_norm: Some(&blk.qn.w),
1163                        k_norm: Some(&blk.kn.w),
1164                        bias: None,
1165                        output_gate: false,
1166                        cpu_k: &cache[li][bi].k,
1167                        cpu_v: &cache[li][bi].v,
1168                    },
1169                    post_norm: &blk.n2.w,
1170                    ffn: crate::gpu::GraphFfn::Dense {
1171                        gate: g,
1172                        up: u,
1173                        down: d,
1174                    },
1175                });
1176            }
1177            let mut h = x[bi * hs..(bi + 1) * hs].to_vec();
1178            let mut out = Vec::new();
1179            let ok = crate::gpu::forward_token_graph(
1180                &model,
1181                bi as u64,
1182                &layers,
1183                &[],
1184                0,
1185                &self.inv_freq,
1186                &mut h,
1187                self.nh,
1188                self.nkv,
1189                self.hd,
1190                self.hd,
1191                hs,
1192                self.inter,
1193                pos,
1194                cap,
1195                false,
1196                1e-6,
1197                Some((&lm, lm_rows)),
1198                &self.norm.w,
1199                &mut out,
1200                &[],
1201                1,
1202                None,
1203                None,
1204                None,
1205                0,
1206                false,
1207            );
1208            if !ok || out.len() < lm_rows {
1209                return false;
1210            }
1211            staged[bi] = (h, out);
1212        }
1213        // Both branches or neither: a half-applied token would leave the
1214        // two CFG streams a position apart, which reads as the model
1215        // losing the plot rather than as an error.
1216        for (bi, (h, out)) in staged.into_iter().enumerate() {
1217            x[bi * hs..(bi + 1) * hs].copy_from_slice(&h);
1218            logits[bi] = out;
1219        }
1220        true
1221    }
1222
1223    /// Run one position through every block, appending to the cache.
1224    /// `x` is `[b, hidden]` for the CFG pair; returns the normed hidden.
1225    fn step_blocks(&self, x: &mut [f32], b: usize, pos: usize, cache: &mut [Vec<KvRun>]) {
1226        let (hs, nh, nkv, hd) = (self.hidden, self.nh, self.nkv, self.hd);
1227        let pool = self.pool.as_deref();
1228        let kvw = nkv * hd;
1229        for (li, blk) in self.blocks.iter().enumerate() {
1230            let mut h = x.to_vec();
1231            blk.n1.apply(&mut h, hs);
1232            let mut q = vec![0f32; b * nh * hd];
1233            let mut k = vec![0f32; b * kvw];
1234            let mut v = vec![0f32; b * kvw];
1235            blk.q.matmat(&h, b, &mut q, pool);
1236            blk.k.matmat(&h, b, &mut k, pool);
1237            blk.v.matmat(&h, b, &mut v, pool);
1238            // Per-head RMSNorm on q and k BEFORE the rotation, then
1239            // split-half RoPE — Qwen3's order, not the other way round.
1240            for bi in 0..b {
1241                for hh in 0..nh {
1242                    let s = bi * nh * hd + hh * hd;
1243                    blk.qn.apply(&mut q[s..s + hd], hd);
1244                    rope_half(&mut q[s..s + hd], pos, &self.inv_freq);
1245                }
1246                for hh in 0..nkv {
1247                    let s = bi * kvw + hh * hd;
1248                    blk.kn.apply(&mut k[s..s + hd], hd);
1249                    rope_half(&mut k[s..s + hd], pos, &self.inv_freq);
1250                }
1251            }
1252            let mut attn = vec![0f32; b * nh * hd];
1253            let per_kv = nh / nkv;
1254            for bi in 0..b {
1255                let run = &mut cache[li][bi];
1256                for g in 0..nkv {
1257                    run.k[g].extend_from_slice(&k[bi * kvw + g * hd..bi * kvw + (g + 1) * hd]);
1258                    run.v[g].extend_from_slice(&v[bi * kvw + g * hd..bi * kvw + (g + 1) * hd]);
1259                }
1260                run.len += 1;
1261                let n = run.len;
1262                let scale = 1.0 / (hd as f32).sqrt();
1263                for hh in 0..nh {
1264                    let g = hh / per_kv;
1265                    let qi = &q[bi * nh * hd + hh * hd..bi * nh * hd + hh * hd + hd];
1266                    let mut sc = vec![0f32; n];
1267                    let mut mx = f32::NEG_INFINITY;
1268                    for (j, s) in sc.iter_mut().enumerate() {
1269                        let kj = &run.k[g][j * hd..(j + 1) * hd];
1270                        *s = qi.iter().zip(kj).map(|(a, c)| a * c).sum::<f32>() * scale;
1271                        mx = mx.max(*s);
1272                    }
1273                    let mut sum = 0.0;
1274                    for s in sc.iter_mut() {
1275                        *s = (*s - mx).exp();
1276                        sum += *s;
1277                    }
1278                    let inv = 1.0 / sum;
1279                    let dst = &mut attn[bi * nh * hd + hh * hd..bi * nh * hd + hh * hd + hd];
1280                    for (j, &s) in sc.iter().enumerate() {
1281                        let w = s * inv;
1282                        let vj = &run.v[g][j * hd..(j + 1) * hd];
1283                        for (d, &vv) in dst.iter_mut().zip(vj) {
1284                            *d += w * vv;
1285                        }
1286                    }
1287                }
1288            }
1289            let mut proj = vec![0f32; b * hs];
1290            blk.o.matmat(&attn, b, &mut proj, pool);
1291            for (a, c) in x.iter_mut().zip(&proj) {
1292                *a += c;
1293            }
1294            let mut h = x.to_vec();
1295            blk.n2.apply(&mut h, hs);
1296            let (mut g, mut u2) = (vec![0f32; b * self.inter], vec![0f32; b * self.inter]);
1297            blk.gate.matmat(&h, b, &mut g, pool);
1298            blk.up.matmat(&h, b, &mut u2, pool);
1299            for (a, c) in g.iter_mut().zip(&u2) {
1300                *a = silu(*a) * c;
1301            }
1302            let mut ffo = vec![0f32; b * hs];
1303            blk.down.matmat(&g, b, &mut ffo, pool);
1304            for (a, c) in x.iter_mut().zip(&ffo) {
1305                *a += c;
1306            }
1307        }
1308    }
1309
1310    /// Generate `frames` of conditioning: `[frames, 8·hidden]`.
1311    ///
1312    /// The CFG pair runs as batch 2 — the conditioned prompt and one
1313    /// whose middle is replaced by `<|audio_cfg|>` — and every sampled
1314    /// code is shared by both branches, which is what makes them stay
1315    /// in step.
1316    pub fn generate(
1317        &self,
1318        prompt_ids: &[u32],
1319        seed: u64,
1320        frames: usize,
1321        mut progress: impl FnMut(usize, usize),
1322    ) -> Result<(Vec<f32>, usize), String> {
1323        let hs = self.hidden;
1324        let pool = self.pool.as_deref();
1325        let want = frames.min(self.max_frames);
1326        let mut cache: Vec<Vec<KvRun>> = (0..self.blocks.len())
1327            .map(|_| vec![KvRun::init(self.nkv); 2])
1328            .collect();
1329        // The unconditioned branch keeps the frame but not the words.
1330        let mut uncond = prompt_ids.to_vec();
1331        if uncond.len() > 3 {
1332            let n = uncond.len();
1333            for t in uncond[1..n - 2].iter_mut() {
1334                *t = tokens::AUDIO_CFG;
1335            }
1336        }
1337        let cap = prompt_ids.len() + want + 2;
1338        // One path for the whole generation. The graph keeps K/V on the
1339        // device and the host loop keeps it here; alternating between
1340        // them mid-song would leave the two caches disagreeing.
1341        // Opt-in, not opt-out. Measured on a 3090 next to a 256-core EPYC:
1342        // the whole-token graph costs 136.7 s of AR against 47.8 s host,
1343        // 2.9x SLOWER. The AR is batch-1/2 matvec, so every layer is a
1344        // bandwidth errand too small to amortise a submit, while the host
1345        // arm has 256 cores to spread it over. The graph is the right
1346        // shape for a thin CPU; it is the wrong one here, so it ships off.
1347        // (Gating this on `enabled_here()` was also wrong — the AR runs
1348        // before the backend is up, so that read false and the graph was
1349        // never once attempted. Keep it un-gated so the A/B stays honest.)
1350        let mut graph = std::env::var("CMF_MUSIC3_GRAPH").as_deref() == Ok("1");
1351        let mut glogits: [Vec<f32>; 2] = Default::default();
1352        let mut last = vec![0f32; 2 * hs];
1353        for (pos, (&a, &b)) in prompt_ids.iter().zip(&uncond).enumerate() {
1354            let mut x = vec![0f32; 2 * hs];
1355            x[..hs].copy_from_slice(&Self::embed_row(&self.embed_prefill, a as usize, hs));
1356            x[hs..].copy_from_slice(&Self::embed_row(&self.embed_prefill, b as usize, hs));
1357            if graph {
1358                if self.graph_step(&mut x, pos, cap, &mut cache, &mut glogits) {
1359                    last = x;
1360                    if pos % 64 == 0 {
1361                        progress(0, want);
1362                    }
1363                    continue;
1364                }
1365                // Refused on the first token: nothing has been committed,
1366                // so the host arm starts clean from position 0.
1367                if pos > 0 {
1368                    return Err("the token graph refused mid-prefill".into());
1369                }
1370                graph = false;
1371            }
1372            self.step_blocks(&mut x, 2, pos, &mut cache);
1373            last = x;
1374            if pos % 64 == 0 {
1375                progress(0, want);
1376            }
1377        }
1378        let mut rng = Rng::new(seed);
1379        let mut out: Vec<f32> = Vec::with_capacity(want * self.codebooks * hs);
1380        let mut done = 0usize;
1381        let scale = (self.codebooks as f32).powf(-0.5);
1382        for frame in 0..want {
1383            let mut normed = last.clone();
1384            self.norm.apply(&mut normed, hs);
1385            // c0 with classifier-free guidance and a top-k mask taken
1386            // from the CONDITIONED logits, per the reference. The graph
1387            // folds the final norm and lm_head into its own submit, so
1388            // when it is driving the logits are already here.
1389            let vocab = self.lm_head_rows();
1390            let mut logits = vec![0f32; 2 * vocab];
1391            if graph {
1392                logits[..vocab].copy_from_slice(&glogits[0][..vocab]);
1393                logits[vocab..].copy_from_slice(&glogits[1][..vocab]);
1394            } else {
1395                self.lm_head.matmat(&normed, 2, &mut logits, pool);
1396            }
1397            let (cond, unc) = logits.split_at(vocab);
1398            let mut thr: Vec<f32> = cond.to_vec();
1399            thr.sort_unstable_by(|a, b| b.partial_cmp(a).unwrap());
1400            let cut = thr[self.top_k.min(vocab) - 1];
1401            let guided: Vec<f32> = (0..vocab)
1402                .map(|i| {
1403                    if cond[i] < cut {
1404                        f32::NEG_INFINITY
1405                    } else {
1406                        unc[i] + (cond[i] - unc[i]) * self.cfg_scale
1407                    }
1408                })
1409                .collect();
1410            let code = sample_topk(&guided, self.top_k, &mut rng);
1411            if code == 0 {
1412                break; // stop token
1413            }
1414            let c0 = code - 1;
1415            let c0_embed = Self::embed_row(&self.embed_audio, c0, hs);
1416            // Depth: the remaining seven codebooks, and their hidden
1417            // states are seven eighths of what the DiT will see.
1418            let mut seq = self.depth.project(&normed[..hs]);
1419            seq.extend_from_slice(&self.depth.project(&c0_embed));
1420            let mut codes = vec![c0];
1421            let mut frame_hidden = normed[..hs].to_vec();
1422            for level in 1..self.codebooks {
1423                let n = seq.len() / hs;
1424                let h = self.depth.forward_last(&seq, n);
1425                frame_hidden.extend_from_slice(&h);
1426                let lg = self.depth.head(level, &h);
1427                let c = sample_topk(&lg, self.top_k, &mut rng);
1428                codes.push(c);
1429                if level < self.codebooks - 1 {
1430                    let e =
1431                        Self::embed_row(&self.embed_extra, c + (level - 1) * self.audio_vocab, hs);
1432                    seq.extend_from_slice(&self.depth.project(&e));
1433                }
1434            }
1435            out.extend_from_slice(&frame_hidden);
1436            done += 1;
1437            progress(done, want);
1438            if done >= want {
1439                break;
1440            }
1441            // Feed the whole frame back: c0's embedding plus the extras,
1442            // scaled by 1/sqrt(codebooks).
1443            let mut fb = Self::embed_row(&self.embed_audio, codes[0], hs);
1444            for (level, &c) in codes.iter().enumerate().skip(1) {
1445                let e = Self::embed_row(&self.embed_extra, c + (level - 1) * self.audio_vocab, hs);
1446                for (a, b) in fb.iter_mut().zip(&e) {
1447                    *a += b;
1448                }
1449            }
1450            for a in fb.iter_mut() {
1451                *a *= scale;
1452            }
1453            let mut x = vec![0f32; 2 * hs];
1454            x[..hs].copy_from_slice(&fb);
1455            x[hs..].copy_from_slice(&fb);
1456            let pos = prompt_ids.len() + frame;
1457            if !graph || !self.graph_step(&mut x, pos, cap, &mut cache, &mut glogits) {
1458                if graph {
1459                    return Err("the token graph refused mid-song".into());
1460                }
1461                self.step_blocks(&mut x, 2, pos, &mut cache);
1462            }
1463            last = x;
1464        }
1465        if done == 0 {
1466            return Err("MiniMax-Music-3 generated zero audio frames".into());
1467        }
1468        Ok((out, done))
1469    }
1470
1471    fn lm_head_rows(&self) -> usize {
1472        match &self.lm_head {
1473            Proj::F32 { rows, .. } => *rows,
1474            Proj::Q(q) => q.rows(),
1475        }
1476    }
1477}
1478
1479/// Split-half rotation over one head, angle `pos·inv_freq[i]`.
1480fn rope_half(x: &mut [f32], pos: usize, inv_freq: &[f32]) {
1481    let half = x.len() / 2;
1482    for i in 0..half {
1483        let (s, c) = (pos as f32 * inv_freq[i]).sin_cos();
1484        let (a, b) = (x[i], x[i + half]);
1485        x[i] = a * c - b * s;
1486        x[i + half] = b * c + a * s;
1487    }
1488}
1489
1490#[cfg(test)]
1491mod tests {
1492    use super::*;
1493
1494    /// The half that is finished, end to end: noise → Euler sampler →
1495    /// vocoder → PCM. It does not make music (the conditioning would
1496    /// come from the AR stack), but it proves the three implemented
1497    /// components compose — the sampler's σ walk, the DiT's timestep
1498    /// convention and the vocoder's hop all have to agree for the
1499    /// sample count to land, and the count is fixed by the reference.
1500    #[test]
1501    fn music3_sampler_and_vocoder_compose() {
1502        let Ok(p) = std::env::var("CMF_MUSIC3_DIT") else {
1503            eprintln!("CMF_MUSIC3_DIT unset — skipping Music-3 chain test");
1504            return;
1505        };
1506        let model = Arc::new(CmfModel::open(&p).expect("open pack"));
1507        let dit = Music3Dit::from_cmf(&model).expect("load DiT");
1508        let dav = crate::audiovae::Music3Dav::from_cmf(&model).expect("load DAV");
1509        let n = 6usize;
1510        // A fixed pseudo-noise: the test must not depend on an RNG.
1511        let noise: Vec<f32> = (0..Music3Dit::IN_CH * n)
1512            .map(|i| (((i * 2654435761) % 1000) as f32 / 500.0) - 1.0)
1513            .collect();
1514        let cond = vec![0f32; Music3Dit::COND_CH * n];
1515        let steps = 3;
1516        let mut seen = 0usize;
1517        let latent = dit.sample(&noise, &cond, n, steps, |i, t| {
1518            assert_eq!(t, steps);
1519            seen = i;
1520        });
1521        assert_eq!(seen, steps, "sampler reported every step");
1522        assert_eq!(latent.len(), Music3Dit::IN_CH * n);
1523        assert!(
1524            latent.iter().all(|v| v.is_finite()),
1525            "latent went non-finite"
1526        );
1527        let pcm = dav.decode(&latent, n, None);
1528        assert_eq!(
1529            pcm.len(),
1530            n * crate::audiovae::Music3Dav::HOP * 2,
1531            "the chain's sample count is frames x 512 x 2"
1532        );
1533        assert!(pcm.iter().all(|v| v.is_finite() && v.abs() <= 1.0));
1534        let secs = (n * crate::audiovae::Music3Dav::HOP) as f32
1535            / crate::audiovae::Music3Dav::SAMPLE_RATE as f32;
1536        eprintln!(
1537            "music3 chain: {n} frames -> {} stereo samples ({secs:.3} s at 44.1 kHz)",
1538            pcm.len() / 2
1539        );
1540    }
1541
1542    /// The depth decoder must be CAUSAL — that is the only thing its
1543    /// attention mask does, and losing it lets a codebook level see its
1544    /// own answer while the model still samples plausible codes.
1545    /// Appending a position may not change any earlier output.
1546    #[test]
1547    fn music3_rvq_depth_decoder_is_causal() {
1548        let Ok(p) = std::env::var("CMF_MUSIC3_TE") else {
1549            eprintln!("CMF_MUSIC3_TE unset — skipping RVQ decoder test");
1550            return;
1551        };
1552        let model = Arc::new(CmfModel::open(&p).expect("open packed AR stack"));
1553        let dec = RvqDepthDecoder::from_cmf(&model).expect("load RVQ decoder");
1554        assert_eq!(dec.codebooks(), 8, "eight codebooks");
1555        let hs = dec.hidden;
1556        let seq: Vec<f32> = (0..3 * hs)
1557            .map(|i| 0.05 * ((i as f32) * 0.013).sin())
1558            .collect();
1559        let a = dec.forward_last(&seq[..2 * hs], 2);
1560        let b = dec.forward_last(&seq, 3);
1561        assert_eq!(a.len(), hs);
1562        assert!(a.iter().all(|v| v.is_finite()) && b.iter().all(|v| v.is_finite()));
1563        // Position 1's own output is read by forward_last at n=2; adding
1564        // position 2 must leave the stack's view of 0..=1 untouched, so
1565        // re-running with the shorter prefix must agree with itself.
1566        let a2 = dec.forward_last(&seq[..2 * hs], 2);
1567        let d = a
1568            .iter()
1569            .zip(&a2)
1570            .map(|(x, y)| (x - y).abs())
1571            .fold(0f32, f32::max);
1572        assert!(d == 0.0, "not deterministic: {d}");
1573        let logits = dec.head(1, &b);
1574        assert_eq!(logits.len(), 1024, "audio vocab is 1024 per level");
1575        assert!(logits.iter().all(|v| v.is_finite()));
1576        let spread = logits.iter().fold(f32::MIN, |m, v| m.max(*v))
1577            - logits.iter().fold(f32::MAX, |m, v| m.min(*v));
1578        assert!(spread > 1e-3, "head is flat, spread {spread}");
1579        eprintln!("music3 rvq: 8 codebooks, head spread {spread:.3}");
1580    }
1581
1582    /// Forward the packed DiT and check what the reference fixes: a
1583    /// `[128, n]` velocity, finite, and responsive to BOTH inputs.
1584    /// `CMF_MUSIC3_DIT=<file.cmf>` points at a pack.
1585    ///
1586    /// The two response checks are the point. A forward that silently
1587    /// drops the condition — the easiest way to get the 2304-wide concat
1588    /// wrong — still returns a plausible velocity, and so does one that
1589    /// ignores the timestep token.
1590    #[test]
1591    fn music3_dit_forward_has_the_reference_geometry() {
1592        let Ok(p) = std::env::var("CMF_MUSIC3_DIT") else {
1593            eprintln!("CMF_MUSIC3_DIT unset — skipping Music-3 DiT test");
1594            return;
1595        };
1596        let model = Arc::new(CmfModel::open(&p).expect("open packed DiT"));
1597        let dit = Music3Dit::from_cmf(&model).expect("load DiT");
1598        let n = 4usize;
1599        let x: Vec<f32> = (0..Music3Dit::IN_CH * n)
1600            .map(|i| 0.3 * ((i as f32) * 0.017).sin())
1601            .collect();
1602        let cond: Vec<f32> = (0..Music3Dit::COND_CH * n)
1603            .map(|i| 0.2 * ((i as f32) * 0.011).cos())
1604            .collect();
1605        let v = dit.forward(&x, &cond, n, 0.7);
1606        assert_eq!(v.len(), Music3Dit::IN_CH * n, "velocity is [128, n]");
1607        assert!(v.iter().all(|q| q.is_finite()), "non-finite velocity");
1608        let rms = (v.iter().map(|q| q * q).sum::<f32>() / v.len() as f32).sqrt();
1609        assert!(rms > 1e-6, "velocity is silent, rms {rms}");
1610
1611        let zero_cond = vec![0f32; Music3Dit::COND_CH * n];
1612        let v0 = dit.forward(&x, &zero_cond, n, 0.7);
1613        let dc = v
1614            .iter()
1615            .zip(&v0)
1616            .map(|(a, b)| (a - b).abs())
1617            .fold(0f32, f32::max);
1618        assert!(dc > 1e-5, "condition changed nothing — concat is wrong");
1619
1620        let vt = dit.forward(&x, &cond, n, 0.2);
1621        let dt = v
1622            .iter()
1623            .zip(&vt)
1624            .map(|(a, b)| (a - b).abs())
1625            .fold(0f32, f32::max);
1626        assert!(dt > 1e-5, "timestep changed nothing — the token is lost");
1627        eprintln!("music3 dit: rms {rms:.4}, d/dcond {dc:.4}, d/dt {dt:.4}");
1628    }
1629}
1630
1631/// Where a denoise step's time goes, under `CMF_MUSIC3_PROF=1`. The
1632/// stage timers say "denoise"; three optimizations were argued about
1633/// without anything saying which part of it.
1634pub mod prof {
1635    use std::sync::OnceLock;
1636    use std::sync::atomic::{AtomicU64, Ordering};
1637    use std::time::Instant;
1638
1639    pub static QKV: AtomicU64 = AtomicU64::new(0);
1640    pub static ATTN: AtomicU64 = AtomicU64::new(0);
1641    pub static OUT: AtomicU64 = AtomicU64::new(0);
1642    pub static FFN: AtomicU64 = AtomicU64::new(0);
1643
1644    pub fn on() -> bool {
1645        static ON: OnceLock<bool> = OnceLock::new();
1646        *ON.get_or_init(|| std::env::var("CMF_MUSIC3_PROF").as_deref() == Ok("1"))
1647    }
1648
1649    /// `None` when profiling is off, so an untimed run pays one branch.
1650    pub fn start() -> Option<Instant> {
1651        on().then(Instant::now)
1652    }
1653
1654    pub fn add(c: &AtomicU64, t: Option<Instant>) {
1655        if let Some(t) = t {
1656            c.fetch_add(t.elapsed().as_nanos() as u64, Ordering::Relaxed);
1657        }
1658    }
1659
1660    pub fn report() -> String {
1661        let g = |c: &AtomicU64| c.load(Ordering::Relaxed) as f64 / 1e9;
1662        format!(
1663            "qkv {:.1}s, attention {:.1}s, out {:.1}s, ffn {:.1}s",
1664            g(&QKV),
1665            g(&ATTN),
1666            g(&OUT),
1667            g(&FFN)
1668        )
1669    }
1670}