1use crate::pool::Pool;
19use crate::qtensor::QTensor;
20use crate::vae::{StTensor, read_safetensors};
21use cortiq_core::CmfModel;
22use std::collections::HashMap;
23use std::path::Path;
24use std::sync::Arc;
25
26pub(crate) enum Proj {
29 F32 {
30 w: Vec<f32>,
31 rows: usize,
32 cols: usize,
33 },
34 Q(QTensor),
35}
36
37impl Proj {
38 pub(crate) fn f32(w: Vec<f32>, cols: usize) -> Self {
40 let rows = w.len() / cols;
41 debug_assert_eq!(w.len(), rows * cols);
42 Proj::F32 { w, rows, cols }
43 }
44
45 pub(crate) fn from_model(model: &Arc<CmfModel>, name: &str) -> Result<Self, String> {
48 Ok(match QTensor::from_model(model, name)? {
49 QTensor::F32 { data, rows, cols } => Proj::F32 {
50 w: data,
51 rows,
52 cols,
53 },
54 q => Proj::Q(q),
55 })
56 }
57
58 pub(crate) fn rows(&self) -> usize {
59 match self {
60 Proj::F32 { rows, .. } => *rows,
61 Proj::Q(q) => q.rows(),
62 }
63 }
64
65 pub(crate) fn matmat(&self, xs: &[f32], b: usize, out: &mut [f32], pool: Option<&Pool>) {
67 match self {
68 Proj::F32 { w, rows, cols } => {
69 crate::fcd_ops::gemm_nt(xs, w, out, b, *cols, *rows, pool)
70 }
71 Proj::Q(q) => q.matmat(xs, b, out, pool),
72 }
73 }
74}
75
76struct Block {
77 modulation: Option<(Proj, Vec<f32>)>,
79 norm1: Vec<f32>,
80 q: Proj, k: Proj, v: Proj,
83 o: Proj, norm_q: Vec<f32>, norm_k: Vec<f32>,
86 norm2: Vec<f32>,
87 ffn_norm1: Vec<f32>,
88 w1: Proj, w3: Proj, w2: Proj, ffn_norm2: Vec<f32>,
92}
93
94pub struct NextDit {
97 x_emb: Proj, x_emb_b: Vec<f32>,
99 t_lin1_w: Vec<f32>, t_lin1_b: Vec<f32>,
101 t_lin2_w: Vec<f32>, t_lin2_b: Vec<f32>,
103 cap_norm: Vec<f32>, cap_w: Proj, cap_b: Vec<f32>,
106 context_refiner: Vec<Block>,
107 noise_refiner: Vec<Block>,
108 layers: Vec<Block>,
109 out_lin1_w: Vec<f32>, out_lin1_b: Vec<f32>,
111 out_lin2: Proj, out_lin2_b: Vec<f32>,
113 pool: Option<Arc<Pool>>,
114 pub hidden: usize,
115 pub in_channels: usize,
116 pub patch: usize,
117 nh: usize,
118 nkv: usize,
119 hd: usize,
120 axes_dim: Vec<usize>,
121 eps: f64,
122}
123
124mod prof {
129 use std::sync::OnceLock;
130 use std::sync::atomic::{AtomicU64, Ordering};
131
132 pub const MODNORM: usize = 0;
133 pub const QKV: usize = 1;
134 pub const ROPE: usize = 2;
135 pub const APACK: usize = 3;
136 pub const AQK: usize = 4;
137 pub const SOFTMAX: usize = 5;
138 pub const APV: usize = 6;
139 pub const OPROJ: usize = 7;
140 pub const FFN: usize = 8;
141 pub const FFNEL: usize = 9;
142 pub const HEADTAIL: usize = 10;
143 pub const GPUBLK: usize = 11;
144 const NAMES: [&str; 12] = [
145 "mod+norms", "qkv-proj", "qknorm+rope", "attn-pack", "attn-qk", "softmax", "attn-pv",
146 "o-proj", "ffn-mm", "ffn-silu", "head+tail", "gpu-block",
147 ];
148 static NS: [AtomicU64; 12] = [const { AtomicU64::new(0) }; 12];
149
150 pub fn on() -> bool {
151 static ON: OnceLock<bool> = OnceLock::new();
152 *ON.get_or_init(|| std::env::var("CMF_DIT_PROF").is_ok_and(|v| v != "0"))
153 }
154
155 pub struct Span(Option<(std::time::Instant, usize)>);
157 pub fn span(cat: usize) -> Span {
158 Span(on().then(|| (std::time::Instant::now(), cat)))
159 }
160 impl Drop for Span {
161 fn drop(&mut self) {
162 if let Some((t0, c)) = self.0 {
163 NS[c].fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
164 }
165 }
166 }
167
168 pub fn dump() {
169 if !on() {
170 return;
171 }
172 let total: u64 = NS.iter().map(|a| a.load(Ordering::Relaxed)).sum();
173 if total == 0 {
174 return;
175 }
176 eprintln!("dit prof ({:.1} s total in blocks):", total as f64 / 1e9);
177 for (name, a) in NAMES.iter().zip(&NS) {
178 let ns = a.load(Ordering::Relaxed);
179 eprintln!(
180 " {name:<12} {:>7.2} s {:>4.1}%",
181 ns as f64 / 1e9,
182 ns as f64 * 100.0 / total as f64
183 );
184 }
185 }
186}
187
188fn rms_norm(x: &[f32], w: &[f32], eps: f64) -> Vec<f32> {
190 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
191 let inv = 1.0 / (ss + eps).sqrt();
192 x.iter()
193 .zip(w)
194 .map(|(&v, &g)| (v as f64 * inv) as f32 * g)
195 .collect()
196}
197
198fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
200 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
201 let inv = 1.0 / (ss + eps).sqrt();
202 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
203 *d = (v as f64 * inv) as f32 * g;
204 }
205}
206
207fn rms_norm_inplace(v: &mut [f32], w: &[f32], eps: f64) {
209 let ss = v.iter().map(|&x| (x as f64) * (x as f64)).sum::<f64>() / v.len() as f64;
210 let inv = 1.0 / (ss + eps).sqrt();
211 for (x, &g) in v.iter_mut().zip(w) {
212 *x = (*x as f64 * inv) as f32 * g;
213 }
214}
215
216fn pool_rows(pool: Option<&Pool>, n: usize, f: &(dyn Fn(usize, usize) + Sync)) {
218 match pool {
219 Some(p) => p.run_rows(n, f),
220 None => f(0, n),
221 }
222}
223
224fn silu(v: f32) -> f32 {
225 v / (1.0 + (-v).exp())
226}
227
228fn linear(x: &[f32], w: &[f32], b: &[f32]) -> Vec<f32> {
230 let k = x.len();
231 b.iter()
232 .enumerate()
233 .map(|(o, &bias)| {
234 let row = &w[o * k..(o + 1) * k];
235 bias + row.iter().zip(x).map(|(&a, &c)| a * c).sum::<f32>()
236 })
237 .collect()
238}
239
240struct SendRows(*mut f32);
244unsafe impl Send for SendRows {}
245unsafe impl Sync for SendRows {}
246impl SendRows {
247 #[allow(clippy::mut_from_ref)] unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
250 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
251 }
252
253 unsafe fn set(&self, off: usize, v: f32) {
255 unsafe { *self.0.add(off) = v }
256 }
257}
258
259fn softmax_inplace(row: &mut [f32]) {
262 #[cfg(target_arch = "aarch64")]
263 {
264 crate::attention::softmax_row(row);
265 }
266 #[cfg(not(target_arch = "aarch64"))]
267 {
268 let mx = row.iter().cloned().fold(f32::MIN, f32::max);
269 let mut den = 0f32;
270 for r in row.iter_mut() {
271 *r = (*r - mx).exp();
272 den += *r;
273 }
274 if den > 0.0 {
275 let inv = 1.0 / den;
276 for r in row.iter_mut() {
277 *r *= inv;
278 }
279 }
280 }
281}
282
283pub(crate) fn cmf_f32(model: &CmfModel, name: &str) -> Result<Vec<f32>, String> {
285 let entry = model
286 .tensor(name)
287 .ok_or_else(|| format!("missing tensor {name}"))?;
288 let bytes = model.entry_bytes(entry);
289 let mut out = vec![0f32; entry.shape.iter().product()];
290 cortiq_core::quant::dequant_tensor(entry, bytes, &mut out)?;
291 Ok(out)
292}
293
294fn rope_table(ids: &[[u32; 3]], axes_dim: &[usize]) -> (Vec<f64>, Vec<f64>) {
297 let pairs: usize = axes_dim.iter().sum::<usize>() / 2;
298 let mut cos = Vec::with_capacity(ids.len() * pairs);
299 let mut sin = Vec::with_capacity(ids.len() * pairs);
300 for id in ids {
301 for (a, &d) in axes_dim.iter().enumerate() {
302 for j in 0..d / 2 {
303 let freq = 1.0 / 10000f64.powf(2.0 * j as f64 / d as f64);
304 let ang = id[a] as f64 * freq;
305 cos.push(ang.cos());
306 sin.push(ang.sin());
307 }
308 }
309 }
310 (cos, sin)
311}
312
313impl Drop for NextDit {
314 fn drop(&mut self) {
315 prof::dump();
316 }
317}
318
319impl NextDit {
320 pub fn load_dir(dir: &Path) -> Result<Self, String> {
321 let cfg: serde_json::Value = serde_json::from_slice(
322 &std::fs::read(dir.join("config.json")).map_err(|e| format!("config.json: {e}"))?,
323 )
324 .map_err(|e| format!("config.json: {e}"))?;
325 let idx: serde_json::Value = serde_json::from_slice(
326 &std::fs::read(dir.join("diffusion_pytorch_model.safetensors.index.json"))
327 .map_err(|e| format!("index: {e}"))?,
328 )
329 .map_err(|e| format!("index: {e}"))?;
330 let mut shards: Vec<String> = idx["weight_map"]
331 .as_object()
332 .ok_or("weight_map")?
333 .values()
334 .filter_map(|v| v.as_str().map(String::from))
335 .collect();
336 shards.sort();
337 shards.dedup();
338 let mut t: HashMap<String, StTensor> = HashMap::new();
339 for sh in &shards {
340 t.extend(read_safetensors(&dir.join(sh))?);
341 }
342 let mut take = |n: String| -> Result<Vec<f32>, String> {
343 t.remove(&n)
344 .map(|v| v.data)
345 .ok_or_else(|| format!("missing tensor {n}"))
346 };
347 let hidden = cfg["hidden_size"].as_u64().ok_or("hidden")? as usize;
348 let mut blocks = |pfx: &str, count: usize, modulated: bool| -> Result<Vec<Block>, String> {
349 (0..count)
350 .map(|l| {
351 let p = format!("{pfx}.{l}");
352 let w1 = take(format!("{p}.feed_forward.linear_1.weight"))?;
353 let inter = w1.len() / hidden;
354 Ok(Block {
355 modulation: if modulated {
356 let mw = take(format!("{p}.norm1.linear.weight"))?;
357 let cols = mw.len() / (4 * hidden);
358 Some((Proj::f32(mw, cols), take(format!("{p}.norm1.linear.bias"))?))
359 } else {
360 None
361 },
362 norm1: if modulated {
363 take(format!("{p}.norm1.norm.weight"))?
364 } else {
365 take(format!("{p}.norm1.weight"))?
366 },
367 q: Proj::f32(take(format!("{p}.attn.to_q.weight"))?, hidden),
368 k: Proj::f32(take(format!("{p}.attn.to_k.weight"))?, hidden),
369 v: Proj::f32(take(format!("{p}.attn.to_v.weight"))?, hidden),
370 o: {
371 let o = take(format!("{p}.attn.to_out.0.weight"))?;
372 let cols = o.len() / hidden;
373 Proj::f32(o, cols)
374 },
375 norm_q: take(format!("{p}.attn.norm_q.weight"))?,
376 norm_k: take(format!("{p}.attn.norm_k.weight"))?,
377 norm2: take(format!("{p}.norm2.weight"))?,
378 ffn_norm1: take(format!("{p}.ffn_norm1.weight"))?,
379 w1: Proj::f32(w1, hidden),
380 w3: Proj::f32(take(format!("{p}.feed_forward.linear_3.weight"))?, hidden),
381 w2: Proj::f32(take(format!("{p}.feed_forward.linear_2.weight"))?, inter),
382 ffn_norm2: take(format!("{p}.ffn_norm2.weight"))?,
383 })
384 })
385 .collect()
386 };
387 let nl = cfg["num_layers"].as_u64().ok_or("num_layers")? as usize;
388 let nr = cfg["num_refiner_layers"].as_u64().unwrap_or(2) as usize;
389 let context_refiner = blocks("context_refiner", nr, false)?;
390 let noise_refiner = blocks("noise_refiner", nr, true)?;
391 let layers = blocks("layers", nl, true)?;
392 let nh = cfg["num_attention_heads"].as_u64().ok_or("nh")? as usize;
393 let in_channels = cfg["in_channels"].as_u64().ok_or("in_channels")? as usize;
394 let patch = cfg["patch_size"].as_u64().unwrap_or(2) as usize;
395 let axes_dim: Vec<usize> = cfg["axes_dim_rope"]
396 .as_array()
397 .ok_or("axes_dim_rope")?
398 .iter()
399 .map(|v| v.as_u64().unwrap_or(0) as usize)
400 .collect();
401 let cap_norm = take("time_caption_embed.caption_embedder.0.weight".into())?;
402 let cap_feat = cap_norm.len();
403 Ok(Self {
404 x_emb: Proj::f32(
405 take("x_embedder.weight".into())?,
406 patch * patch * in_channels,
407 ),
408 x_emb_b: take("x_embedder.bias".into())?,
409 t_lin1_w: take("time_caption_embed.timestep_embedder.linear_1.weight".into())?,
410 t_lin1_b: take("time_caption_embed.timestep_embedder.linear_1.bias".into())?,
411 t_lin2_w: take("time_caption_embed.timestep_embedder.linear_2.weight".into())?,
412 t_lin2_b: take("time_caption_embed.timestep_embedder.linear_2.bias".into())?,
413 cap_norm,
414 cap_w: Proj::f32(
415 take("time_caption_embed.caption_embedder.1.weight".into())?,
416 cap_feat,
417 ),
418 cap_b: take("time_caption_embed.caption_embedder.1.bias".into())?,
419 context_refiner,
420 noise_refiner,
421 layers,
422 out_lin1_w: take("norm_out.linear_1.weight".into())?,
423 out_lin1_b: take("norm_out.linear_1.bias".into())?,
424 out_lin2: Proj::f32(take("norm_out.linear_2.weight".into())?, hidden),
425 out_lin2_b: take("norm_out.linear_2.bias".into())?,
426 pool: Pool::from_env(),
427 hidden,
428 in_channels,
429 patch,
430 nh,
431 nkv: cfg["num_kv_heads"].as_u64().unwrap_or(nh as u64) as usize,
432 hd: hidden / nh,
433 axes_dim,
434 eps: cfg["norm_eps"].as_f64().unwrap_or(1e-5),
435 })
436 }
437
438 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
441 let cfg: serde_json::Value = serde_json::from_slice(
442 model
443 .tensor_bytes("dit.config_json")
444 .map_err(|e| e.to_string())?,
445 )
446 .map_err(|e| format!("dit.config_json: {e}"))?;
447 let f32v = |n: &str| -> Result<Vec<f32>, String> { cmf_f32(model, n) };
448 let hidden = cfg["hidden_size"].as_u64().ok_or("hidden")? as usize;
449 let blocks = |pfx: &str, count: usize, modulated: bool| -> Result<Vec<Block>, String> {
450 (0..count)
451 .map(|l| {
452 let p = format!("dit.{pfx}.{l}");
453 Ok(Block {
454 modulation: if modulated {
455 Some((
456 Proj::from_model(model, &format!("{p}.norm1.linear.weight"))?,
457 f32v(&format!("{p}.norm1.linear.bias"))?,
458 ))
459 } else {
460 None
461 },
462 norm1: if modulated {
463 f32v(&format!("{p}.norm1.norm.weight"))?
464 } else {
465 f32v(&format!("{p}.norm1.weight"))?
466 },
467 q: Proj::from_model(model, &format!("{p}.attn.to_q.weight"))?,
468 k: Proj::from_model(model, &format!("{p}.attn.to_k.weight"))?,
469 v: Proj::from_model(model, &format!("{p}.attn.to_v.weight"))?,
470 o: Proj::from_model(model, &format!("{p}.attn.to_out.0.weight"))?,
471 norm_q: f32v(&format!("{p}.attn.norm_q.weight"))?,
472 norm_k: f32v(&format!("{p}.attn.norm_k.weight"))?,
473 norm2: f32v(&format!("{p}.norm2.weight"))?,
474 ffn_norm1: f32v(&format!("{p}.ffn_norm1.weight"))?,
475 w1: Proj::from_model(model, &format!("{p}.feed_forward.linear_1.weight"))?,
476 w3: Proj::from_model(model, &format!("{p}.feed_forward.linear_3.weight"))?,
477 w2: Proj::from_model(model, &format!("{p}.feed_forward.linear_2.weight"))?,
478 ffn_norm2: f32v(&format!("{p}.ffn_norm2.weight"))?,
479 })
480 })
481 .collect()
482 };
483 let nl = cfg["num_layers"].as_u64().ok_or("num_layers")? as usize;
484 let nr = cfg["num_refiner_layers"].as_u64().unwrap_or(2) as usize;
485 let nh = cfg["num_attention_heads"].as_u64().ok_or("nh")? as usize;
486 let axes_dim: Vec<usize> = cfg["axes_dim_rope"]
487 .as_array()
488 .ok_or("axes_dim_rope")?
489 .iter()
490 .map(|v| v.as_u64().unwrap_or(0) as usize)
491 .collect();
492 Ok(Self {
493 x_emb: Proj::from_model(model, "dit.x_embedder.weight")?,
494 x_emb_b: f32v("dit.x_embedder.bias")?,
495 t_lin1_w: f32v("dit.time_caption_embed.timestep_embedder.linear_1.weight")?,
496 t_lin1_b: f32v("dit.time_caption_embed.timestep_embedder.linear_1.bias")?,
497 t_lin2_w: f32v("dit.time_caption_embed.timestep_embedder.linear_2.weight")?,
498 t_lin2_b: f32v("dit.time_caption_embed.timestep_embedder.linear_2.bias")?,
499 cap_norm: f32v("dit.time_caption_embed.caption_embedder.0.weight")?,
500 cap_w: Proj::from_model(model, "dit.time_caption_embed.caption_embedder.1.weight")?,
501 cap_b: f32v("dit.time_caption_embed.caption_embedder.1.bias")?,
502 context_refiner: blocks("context_refiner", nr, false)?,
503 noise_refiner: blocks("noise_refiner", nr, true)?,
504 layers: blocks("layers", nl, true)?,
505 out_lin1_w: f32v("dit.norm_out.linear_1.weight")?,
506 out_lin1_b: f32v("dit.norm_out.linear_1.bias")?,
507 out_lin2: Proj::from_model(model, "dit.norm_out.linear_2.weight")?,
508 out_lin2_b: f32v("dit.norm_out.linear_2.bias")?,
509 pool: Pool::from_env(),
510 hidden,
511 in_channels: cfg["in_channels"].as_u64().ok_or("in_channels")? as usize,
512 patch: cfg["patch_size"].as_u64().unwrap_or(2) as usize,
513 nh,
514 nkv: cfg["num_kv_heads"].as_u64().unwrap_or(nh as u64) as usize,
515 hd: hidden / nh,
516 axes_dim,
517 eps: cfg["norm_eps"].as_f64().unwrap_or(1e-5),
518 })
519 }
520
521 fn time_embed(&self, t: f32) -> Vec<f32> {
523 const HALF: usize = 128;
524 let mut freq = [0f32; 2 * HALF];
525 for i in 0..HALF {
526 let ang = t as f64 * (-(10000f64.ln()) * i as f64 / HALF as f64).exp();
527 freq[i] = ang.cos() as f32;
528 freq[HALF + i] = ang.sin() as f32;
529 }
530 let mut h = linear(&freq, &self.t_lin1_w, &self.t_lin1_b);
531 for v in h.iter_mut() {
532 *v = silu(*v);
533 }
534 linear(&h, &self.t_lin2_w, &self.t_lin2_b)
535 }
536
537 fn gpu_ffn(&self, blk: &Block, xn: &[f32], n: usize, out: &mut [f32]) -> bool {
544 use crate::gpu;
545 if n < 128 || !gpu::enabled_here() || gpu::mm_killed() {
546 return false;
547 }
548 if gpu::probe_deciding(gpu::OpClass::MatmatWide)
549 || !matches!(
550 gpu::probe_arm(gpu::OpClass::MatmatWide),
551 gpu::ProbeArm::Gpu
552 )
553 {
554 return false;
555 }
556 let (Proj::Q(q1), Proj::Q(q3), Proj::Q(q2)) = (&blk.w1, &blk.w3, &blk.w2) else {
557 return false;
558 };
559 let (Some((m, i1)), Some((_, i3)), Some((_, i2))) =
560 (q1.mapped_q4t(), q3.mapped_q4t(), q2.mapped_q4t())
561 else {
562 return false;
563 };
564 let inter = q1.rows();
565 let t0 = std::time::Instant::now();
566 if !gpu::q4t_ffn(m, i1, i3, i2, xn, n, self.hidden, inter, out) {
567 return false;
568 }
569 let flops = 6.0 * n as f64 * self.hidden as f64 * inter as f64;
570 let budget = std::time::Duration::from_secs_f64(flops / 1.5e12 * 8.0 + 0.020);
571 let el = t0.elapsed();
572 if el > budget && !gpu::probe_was_cold() {
573 tracing::warn!(
574 "gpu ffn took {el:?} (budget {budget:?}) — device contended, \
575 CPU for the rest of the process"
576 );
577 gpu::mm_kill();
578 }
579 true
580 }
581
582 fn gpu_attention(
588 &self,
589 q_all: &[f32],
590 k_all: &[f32],
591 v_all: &[f32],
592 n: usize,
593 scale: f32,
594 attn: &mut [f32],
595 ) -> bool {
596 use crate::gpu;
597 let (nh, nkv, hd) = (self.nh, self.nkv, self.hd);
598 if n < 128 || !gpu::enabled_here() || gpu::mm_killed() {
599 return false;
600 }
601 if gpu::probe_deciding(gpu::OpClass::MatmatWide)
602 || !matches!(gpu::probe_arm(gpu::OpClass::MatmatWide), gpu::ProbeArm::Gpu)
603 {
604 return false;
605 }
606 let pool = self.pool.as_deref();
607 let mut qh = vec![0f32; nh * n * hd];
608 let mut kh = vec![0f32; nkv * n * hd];
609 let mut vh = vec![0f32; nkv * n * hd];
610 {
611 let _s = prof::span(prof::APACK);
612 let (sq, sk, sv) = (
613 SendRows(qh.as_mut_ptr()),
614 SendRows(kh.as_mut_ptr()),
615 SendRows(vh.as_mut_ptr()),
616 );
617 pool_rows(pool, n, &|start, end| {
618 for p in start..end {
619 for h in 0..nh {
620 unsafe { sq.row((h * n + p) * hd, hd) }
622 .copy_from_slice(&q_all[(p * nh + h) * hd..(p * nh + h + 1) * hd]);
623 }
624 for h in 0..nkv {
625 unsafe { sk.row((h * n + p) * hd, hd) }
626 .copy_from_slice(&k_all[(p * nkv + h) * hd..(p * nkv + h + 1) * hd]);
627 unsafe { sv.row((h * n + p) * hd, hd) }
628 .copy_from_slice(&v_all[(p * nkv + h) * hd..(p * nkv + h + 1) * hd]);
629 }
630 }
631 });
632 }
633 let _s = prof::span(prof::AQK);
634 let t0 = std::time::Instant::now();
635 if !gpu::dit_attention(&qh, &kh, &vh, nh, nkv, n, hd, scale, attn) {
636 return false;
637 }
638 let flops = 4.0 * nh as f64 * (n as f64) * (n as f64) * hd as f64;
639 let budget = std::time::Duration::from_secs_f64(flops / 1.5e12 * 8.0 + 0.020);
640 let el = t0.elapsed();
641 if el > budget && !gpu::probe_was_cold() {
642 tracing::warn!(
643 "gpu attention took {el:?} (budget {budget:?}) — device contended, \
644 CPU for the rest of the process"
645 );
646 gpu::mm_kill();
647 }
648 true
649 }
650
651 fn gpu_block(
655 &self,
656 blk: &Block,
657 x: &mut [f32],
658 n: usize,
659 rope32: &(Vec<f32>, Vec<f32>),
660 m: &[f32],
661 ) -> bool {
662 use crate::gpu;
663 let (hs, nh, nkv, hd) = (self.hidden, self.nh, self.nkv, self.hd);
664 if n < 128 || !gpu::enabled_here() || gpu::mm_killed() {
665 return false;
666 }
667 if gpu::probe_deciding(gpu::OpClass::MatmatWide)
668 || !matches!(gpu::probe_arm(gpu::OpClass::MatmatWide), gpu::ProbeArm::Gpu)
669 {
670 return false;
671 }
672 if rope32.0.len() != n * hd / 2 {
675 return false;
676 }
677 fn q(p: &Proj) -> Option<(&Arc<CmfModel>, usize)> {
678 match p {
679 Proj::Q(q) => q.mapped_q4t(),
680 Proj::F32 { .. } => None,
681 }
682 }
683 let (
684 Some((model, wq)),
685 Some((_, wk)),
686 Some((_, wv)),
687 Some((_, wo)),
688 Some((_, w1)),
689 Some((_, w3)),
690 Some((_, w2)),
691 ) = (
692 q(&blk.q),
693 q(&blk.k),
694 q(&blk.v),
695 q(&blk.o),
696 q(&blk.w1),
697 q(&blk.w3),
698 q(&blk.w2),
699 )
700 else {
701 return false;
702 };
703 let inter = blk.w1.rows();
704 let gate_msa: Vec<f32> = m[hs..2 * hs].iter().map(|&v| v.tanh()).collect();
705 let gate_mlp: Vec<f32> = m[3 * hs..].iter().map(|&v| v.tanh()).collect();
706 let args = gpu::DitBlockArgs {
707 n,
708 hidden: hs,
709 inter,
710 nh,
711 nkv,
712 hd,
713 eps: self.eps as f32,
714 rope_cos: &rope32.0,
715 rope_sin: &rope32.1,
716 norm1: &blk.norm1,
717 norm2: &blk.norm2,
718 ffn_norm1: &blk.ffn_norm1,
719 ffn_norm2: &blk.ffn_norm2,
720 norm_q: &blk.norm_q,
721 norm_k: &blk.norm_k,
722 s_msa: &m[..hs],
723 gate_msa: &gate_msa,
724 s_mlp: &m[2 * hs..3 * hs],
725 gate_mlp: &gate_mlp,
726 wq,
727 wk,
728 wv,
729 wo,
730 w1,
731 w3,
732 w2,
733 };
734 let t0 = std::time::Instant::now();
735 if !gpu::dit_block(model, &args, x) {
736 return false;
737 }
738 let flops = 2.0 * n as f64 * hs as f64 * ((nh + 2 * nkv) * hd) as f64
739 + 4.0 * nh as f64 * (n as f64) * (n as f64) * hd as f64
740 + 2.0 * n as f64 * hs as f64 * (nh * hd) as f64
741 + 6.0 * n as f64 * hs as f64 * inter as f64;
742 let budget = std::time::Duration::from_secs_f64(flops / 1.5e12 * 8.0 + 0.030);
743 let el = t0.elapsed();
744 if el > budget && !gpu::probe_was_cold() {
745 tracing::warn!(
746 "gpu dit block took {el:?} (budget {budget:?}) — device contended, \
747 CPU for the rest of the process"
748 );
749 gpu::mm_kill();
750 }
751 true
752 }
753
754 fn block_forward(
755 &self,
756 blk: &Block,
757 x: &mut [f32],
758 rope: &(Vec<f64>, Vec<f64>),
759 rope32: Option<&(Vec<f32>, Vec<f32>)>,
760 temb: Option<&[f32]>,
761 ) {
762 let (hs, nh, nkv, hd) = (self.hidden, self.nh, self.nkv, self.hd);
763 let pool = self.pool.as_deref();
764 let n = x.len() / hs;
765 let modv = {
766 let _s = prof::span(prof::MODNORM);
767 blk.modulation.as_ref().zip(temb).map(|((w, b), t)| {
768 let s: Vec<f32> = t.iter().map(|&v| silu(v)).collect();
769 let mut m = vec![0f32; w.rows()];
770 w.matmat(&s, 1, &mut m, pool);
771 for (v, &bias) in m.iter_mut().zip(b) {
772 *v += bias;
773 }
774 m
775 })
776 };
777 if let (Some(m), Some(r32)) = (&modv, rope32) {
778 let _s = prof::span(prof::GPUBLK);
779 if self.gpu_block(blk, x, n, r32, m) {
780 return;
781 }
782 }
783 let modnorm = prof::span(prof::MODNORM);
784 let (s_msa, g_msa, s_mlp, g_mlp) = match &modv {
785 Some(m) => (
786 Some(&m[..hs]),
787 Some(&m[hs..2 * hs]),
788 Some(&m[2 * hs..3 * hs]),
789 Some(&m[3 * hs..]),
790 ),
791 None => (None, None, None, None),
792 };
793 let gate_msa: Option<Vec<f32>> = g_msa.map(|g| g.iter().map(|&v| v.tanh()).collect());
797 let gate_mlp: Option<Vec<f32>> = g_mlp.map(|g| g.iter().map(|&v| v.tanh()).collect());
798 let norm_scaled = |src: &[f32], w: &[f32], s: Option<&[f32]>, dst: &mut [f32]| {
803 let sr = SendRows(dst.as_mut_ptr());
804 pool_rows(pool, n, &|start, end| {
805 for p in start..end {
806 let row = unsafe { sr.row(p * hs, hs) };
808 rms_norm_into(&src[p * hs..(p + 1) * hs], w, self.eps, row);
809 if let Some(s) = s {
810 for (r, &sc) in row.iter_mut().zip(s) {
811 *r *= 1.0 + sc;
812 }
813 }
814 }
815 });
816 };
817 let residual = |src: &[f32], w: &[f32], gate: Option<&[f32]>, x: &mut [f32]| {
818 let sr = SendRows(x.as_mut_ptr());
819 pool_rows(pool, n, &|start, end| {
820 let mut tmp = vec![0f32; hs];
821 for p in start..end {
822 rms_norm_into(&src[p * hs..(p + 1) * hs], w, self.eps, &mut tmp);
823 let dst = unsafe { sr.row(p * hs, hs) };
825 match gate {
826 Some(g) => {
827 for ((d, &v), >) in dst.iter_mut().zip(&tmp).zip(g) {
828 *d += gt * v;
829 }
830 }
831 None => {
832 for (d, &v) in dst.iter_mut().zip(&tmp) {
833 *d += v;
834 }
835 }
836 }
837 }
838 });
839 };
840 let mut xn = vec![0f32; n * hs];
842 norm_scaled(x, &blk.norm1, s_msa, &mut xn);
843 drop(modnorm);
844 let mut q_all = vec![0f32; n * nh * hd];
845 let mut k_all = vec![0f32; n * nkv * hd];
846 let mut v_all = vec![0f32; n * nkv * hd];
847 {
848 let _s = prof::span(prof::QKV);
849 blk.q.matmat(&xn, n, &mut q_all, pool);
850 blk.k.matmat(&xn, n, &mut k_all, pool);
851 blk.v.matmat(&xn, n, &mut v_all, pool);
852 }
853 let rope_span = prof::span(prof::ROPE);
854 let (cos, sin) = rope;
856 let pairs = hd / 2;
857 for (all, heads, w) in [
858 (&mut q_all, nh, &blk.norm_q),
859 (&mut k_all, nkv, &blk.norm_k),
860 ] {
861 let sr = SendRows(all.as_mut_ptr());
862 pool_rows(pool, n, &|start, end| {
863 for p in start..end {
864 for hh in 0..heads {
865 let v = unsafe { sr.row((p * heads + hh) * hd, hd) };
867 rms_norm_inplace(v, w, 1e-5);
868 for j in 0..pairs {
869 let (c, s) = (cos[p * pairs + j], sin[p * pairs + j]);
870 let (a, b) = (v[2 * j] as f64, v[2 * j + 1] as f64);
871 v[2 * j] = (a * c - b * s) as f32;
872 v[2 * j + 1] = (a * s + b * c) as f32;
873 }
874 }
875 }
876 });
877 }
878 drop(rope_span);
879 let scale = 1.0 / (hd as f32).sqrt();
885 let hpk = nh / nkv;
886 let mut attn = vec![0f32; n * nh * hd];
887 if !self.gpu_attention(&q_all, &k_all, &v_all, n, scale, &mut attn) {
888 let mut qh = vec![0f32; n * hd];
889 let mut kh = vec![0f32; n * hd];
890 let mut vt = vec![0f32; hd * n]; let mut scores = vec![0f32; n * n];
892 let mut oh = vec![0f32; n * hd];
893 for hh in 0..nh {
894 let kv = hh / hpk;
895 {
896 let _s = prof::span(prof::APACK);
897 let (sq, sk, sv) = (
898 SendRows(qh.as_mut_ptr()),
899 SendRows(kh.as_mut_ptr()),
900 SendRows(vt.as_mut_ptr()),
901 );
902 pool_rows(pool, n, &|start, end| {
903 for p in start..end {
904 let qsrc = &q_all[(p * nh + hh) * hd..(p * nh + hh + 1) * hd];
905 let qd = unsafe { sq.row(p * hd, hd) };
908 for (d, &v) in qsrc.iter().enumerate() {
909 qd[d] = v * scale;
910 }
911 unsafe { sk.row(p * hd, hd) }.copy_from_slice(
912 &k_all[(p * nkv + kv) * hd..(p * nkv + kv + 1) * hd],
913 );
914 let vv = &v_all[(p * nkv + kv) * hd..(p * nkv + kv + 1) * hd];
915 for (d, &val) in vv.iter().enumerate() {
916 unsafe { sv.set(d * n + p, val) };
917 }
918 }
919 });
920 }
921 {
922 let _s = prof::span(prof::AQK);
923 crate::fcd_ops::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
924 }
925 {
926 let _s = prof::span(prof::SOFTMAX);
927 let sp = SendRows(scores.as_mut_ptr());
928 let soft = |start: usize, end: usize| {
929 for r in start..end {
930 softmax_inplace(unsafe { sp.row(r * n, n) });
932 }
933 };
934 match pool {
935 Some(p) => p.run_rows(n, &soft),
936 None => soft(0, n),
937 }
938 }
939 {
940 let _s = prof::span(prof::APV);
941 crate::fcd_ops::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
942 }
943 let _s = prof::span(prof::APACK);
944 let sa = SendRows(attn.as_mut_ptr());
945 pool_rows(pool, n, &|start, end| {
946 for p in start..end {
947 unsafe { sa.row((p * nh + hh) * hd, hd) }
949 .copy_from_slice(&oh[p * hd..(p + 1) * hd]);
950 }
951 });
952 }
953 }
954 let mut proj = vec![0f32; n * hs];
955 {
956 let _s = prof::span(prof::OPROJ);
957 blk.o.matmat(&attn, n, &mut proj, pool);
958 }
959 let modnorm = prof::span(prof::MODNORM);
960 residual(&proj, &blk.norm2, gate_msa.as_deref(), x);
961 norm_scaled(x, &blk.ffn_norm1, s_mlp, &mut xn);
963 drop(modnorm);
964 let mut d_all = vec![0f32; n * hs];
965 let fused = {
966 let _s = prof::span(prof::FFN);
967 self.gpu_ffn(blk, &xn, n, &mut d_all)
968 };
969 if !fused {
970 let inter = blk.w1.rows();
971 let mut g_all = vec![0f32; n * inter];
972 let mut u_all = vec![0f32; n * inter];
973 {
974 let _s = prof::span(prof::FFN);
975 blk.w1.matmat(&xn, n, &mut g_all, pool);
976 blk.w3.matmat(&xn, n, &mut u_all, pool);
977 }
978 {
979 let _s = prof::span(prof::FFNEL);
980 let sg = SendRows(g_all.as_mut_ptr());
981 pool_rows(pool, n, &|start, end| {
982 for p in start..end {
983 let g = unsafe { sg.row(p * inter, inter) };
985 for (gv, &uv) in g.iter_mut().zip(&u_all[p * inter..(p + 1) * inter]) {
986 *gv = silu(*gv) * uv;
987 }
988 }
989 });
990 }
991 {
992 let _s = prof::span(prof::FFN);
993 blk.w2.matmat(&g_all, n, &mut d_all, pool);
994 }
995 }
996 let _modnorm = prof::span(prof::MODNORM);
997 residual(&d_all, &blk.ffn_norm2, gate_mlp.as_deref(), x);
998 }
999
1000 pub fn forward(
1004 &self,
1005 latent: &[f32],
1006 h: usize,
1007 w: usize,
1008 cap: &[f32],
1009 cap_n: usize,
1010 t: f32,
1011 ) -> Vec<f32> {
1012 let (c, p, hs) = (self.in_channels, self.patch, self.hidden);
1013 assert_eq!(latent.len(), c * h * w);
1014 let (hp, wp) = (h / p, w / p);
1015 let n_img = hp * wp;
1016 let head = prof::span(prof::HEADTAIL);
1017 let temb = self.time_embed(t);
1018
1019 let cap_feat = self.cap_norm.len();
1021 let mut cap_n_all = vec![0f32; cap_n * cap_feat];
1022 for i in 0..cap_n {
1023 cap_n_all[i * cap_feat..(i + 1) * cap_feat].copy_from_slice(&rms_norm(
1024 &cap[i * cap_feat..(i + 1) * cap_feat],
1025 &self.cap_norm,
1026 self.eps,
1027 ));
1028 }
1029 let mut cap_e = vec![0f32; cap_n * hs];
1030 self.cap_w
1031 .matmat(&cap_n_all, cap_n, &mut cap_e, self.pool.as_deref());
1032 for i in 0..cap_n {
1033 for (v, &b) in cap_e[i * hs..(i + 1) * hs].iter_mut().zip(&self.cap_b) {
1034 *v += b;
1035 }
1036 }
1037
1038 let pv = p * p * c;
1040 let mut tok = vec![0f32; n_img * pv];
1041 for ph in 0..hp {
1042 for pw in 0..wp {
1043 let dst = &mut tok[(ph * wp + pw) * pv..(ph * wp + pw + 1) * pv];
1044 for dy in 0..p {
1045 for dx in 0..p {
1046 for ch in 0..c {
1047 dst[(dy * p + dx) * c + ch] =
1048 latent[ch * h * w + (ph * p + dy) * w + pw * p + dx];
1049 }
1050 }
1051 }
1052 }
1053 }
1054 let mut img = vec![0f32; n_img * hs];
1055 self.x_emb
1056 .matmat(&tok, n_img, &mut img, self.pool.as_deref());
1057 for i in 0..n_img {
1058 for (v, &b) in img[i * hs..(i + 1) * hs].iter_mut().zip(&self.x_emb_b) {
1059 *v += b;
1060 }
1061 }
1062
1063 let cap_ids: Vec<[u32; 3]> = (0..cap_n).map(|i| [i as u32, 0, 0]).collect();
1065 let img_ids: Vec<[u32; 3]> = (0..n_img)
1066 .map(|i| [cap_n as u32, (i / wp) as u32, (i % wp) as u32])
1067 .collect();
1068 let cap_rope = rope_table(&cap_ids, &self.axes_dim);
1069 let img_rope = rope_table(&img_ids, &self.axes_dim);
1070 let to32 = |r: &(Vec<f64>, Vec<f64>)| {
1072 (
1073 r.0.iter().map(|&v| v as f32).collect::<Vec<f32>>(),
1074 r.1.iter().map(|&v| v as f32).collect::<Vec<f32>>(),
1075 )
1076 };
1077 let img_rope32 = to32(&img_rope);
1078 drop(head);
1079
1080 for blk in &self.context_refiner {
1081 self.block_forward(blk, &mut cap_e, &cap_rope, None, None);
1082 }
1083 for blk in &self.noise_refiner {
1084 self.block_forward(blk, &mut img, &img_rope, Some(&img_rope32), Some(&temb));
1085 }
1086
1087 let n = cap_n + n_img;
1089 let mut x = cap_e;
1090 x.extend_from_slice(&img);
1091 let joint_rope = (
1092 [cap_rope.0, img_rope.0].concat(),
1093 [cap_rope.1, img_rope.1].concat(),
1094 );
1095 let joint_rope32 = to32(&joint_rope);
1096 for blk in &self.layers {
1097 self.block_forward(blk, &mut x, &joint_rope, Some(&joint_rope32), Some(&temb));
1098 }
1099
1100 let _tail = prof::span(prof::HEADTAIL);
1102 let silu_t: Vec<f32> = temb.iter().map(|&v| silu(v)).collect();
1103 let scale = linear(&silu_t, &self.out_lin1_w, &self.out_lin1_b);
1104 for row in x.chunks_exact_mut(hs) {
1105 let mean = row.iter().map(|&v| v as f64).sum::<f64>() / hs as f64;
1106 let var = row
1107 .iter()
1108 .map(|&v| (v as f64 - mean) * (v as f64 - mean))
1109 .sum::<f64>()
1110 / hs as f64;
1111 let inv = 1.0 / (var + 1e-6).sqrt();
1112 for (v, &s) in row.iter_mut().zip(&scale) {
1113 *v = ((*v as f64 - mean) * inv) as f32 * (1.0 + s);
1114 }
1115 }
1116 let mut out = vec![0f32; n * pv];
1117 self.out_lin2.matmat(&x, n, &mut out, self.pool.as_deref());
1118 for i in 0..n {
1119 for (v, &b) in out[i * pv..(i + 1) * pv].iter_mut().zip(&self.out_lin2_b) {
1120 *v += b;
1121 }
1122 }
1123
1124 let mut pred = vec![0f32; c * h * w];
1126 for ph in 0..hp {
1127 for pw in 0..wp {
1128 let src = &out[(cap_n + ph * wp + pw) * pv..(cap_n + ph * wp + pw + 1) * pv];
1129 for dy in 0..p {
1130 for dx in 0..p {
1131 for ch in 0..c {
1132 pred[ch * h * w + (ph * p + dy) * w + pw * p + dx] =
1133 src[(dy * p + dx) * c + ch];
1134 }
1135 }
1136 }
1137 }
1138 }
1139 pred
1140 }
1141}