1use cortiq_core::CmfModel;
14use std::cell::Cell;
15use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock};
17
18pub struct EmbryoGraphModel {
27 pub id: u64,
28 pub hidden: usize,
29 pub intermediate: usize,
30 pub vocab: usize,
31 pub layers: usize,
32 pub phase_heads: usize,
33 pub nphase: usize,
34 pub phase_dv: usize,
35 pub anchor_q_heads: usize,
36 pub anchor_kv_heads: usize,
37 pub anchor_head_dim: usize,
38 pub rotary_dim: usize,
39 pub max_seq: usize,
40 pub cluster_count: usize,
41 pub cluster_size: usize,
42 pub phase_state_len: usize,
43 pub state_stride: usize,
44 pub kv_stride: usize,
45 pub norm_gemma: bool,
46 pub phase_mass: f32,
47 pub weights: Vec<f32>,
48 pub meta: Vec<u32>,
49 pub lm_head: Vec<f32>,
50 pub clusters: Vec<f32>,
51 pub final_norm: Vec<f32>,
52 pub inv_freq: Vec<f32>,
53 pub bounded: bool,
58 pub kv_layers: usize,
62 pub state_layers: usize,
65 pub anchor_window: usize,
66 pub anchor_sink: usize,
67 pub phase_layers: usize,
69 pub gdn_layers: usize,
73 pub gdn_heads: usize,
74 pub gdn_k_heads: usize,
75 pub gdn_dk: usize,
76 pub gdn_dv: usize,
77 pub gdn_kk: usize,
78}
79
80impl EmbryoGraphModel {
81 pub fn gdn_c_dim(&self) -> usize {
83 2 * self.gdn_k_heads * self.gdn_dk + self.gdn_heads * self.gdn_dv
84 }
85}
86
87pub const EMBRYO_META_HEADER: usize = 32;
90
91pub const EMBRYO_CHUNK_MAX: usize = 64;
95
96pub fn forward_embryo_graph_chunk(
101 model: &Arc<EmbryoGraphModel>,
102 kv_id: u64,
103 rows: &[f32],
104 position: usize,
105 n: usize,
106 logits: &mut Vec<f32>,
107) -> bool {
108 #[cfg(feature = "gpu")]
109 if backend() == Backend::Wgpu {
110 return crate::gpu_wgpu::forward_embryo_graph_chunk(model, kv_id, rows, position, n, logits);
111 }
112 let _ = (model, kv_id, rows, position, n, logits);
113 false
114}
115
116pub fn embryo_device_state_bytes(kv_id: u64) -> Option<(u64, u64)> {
122 #[cfg(feature = "gpu")]
123 if backend() == Backend::Wgpu {
124 return crate::gpu_wgpu::embryo_device_state_bytes(kv_id);
125 }
126 let _ = kv_id;
127 None
128}
129
130pub fn counts_as_placement_weight(model: &CmfModel, name: &str) -> bool {
137 model.header.genome.is_none() || cortiq_core::knowledge::is_trunk_tensor(name)
138}
139
140pub fn placement_weight_bytes(model: &CmfModel) -> u64 {
144 if model.header.genome.is_none() {
145 return model.primary_bytes().len() as u64;
146 }
147 model
148 .tensors
149 .iter()
150 .filter(|t| counts_as_placement_weight(model, &t.name))
151 .map(|t| t.nbytes)
152 .sum()
153}
154
155pub fn embryo_device_next_position(kv_id: u64) -> Option<usize> {
159 #[cfg(feature = "gpu")]
160 if backend() == Backend::Wgpu {
161 return crate::gpu_wgpu::embryo_device_next_position(kv_id);
162 }
163 let _ = kv_id;
164 None
165}
166
167thread_local! {
168 static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
172 static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
177 static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
181}
182
183pub struct CpuScopeGuard(bool);
189
190impl Drop for CpuScopeGuard {
191 fn drop(&mut self) {
192 CPU_ONLY.with(|c| c.set(self.0));
193 }
194}
195
196pub fn enter_cpu_scope() -> CpuScopeGuard {
197 let previous = CPU_ONLY.with(|c| c.replace(true));
198 CpuScopeGuard(previous)
199}
200
201pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
203 let _restore = enter_cpu_scope();
204 f()
205}
206
207pub(crate) fn inherit_cpu_scope() -> impl Fn() -> Option<CpuScopeGuard> + Copy {
212 let on = CPU_ONLY.get();
213 move || on.then(enter_cpu_scope)
214}
215
216pub fn probe_set_device(label: &str) {
221 let _ = DEVICE_LABEL.set(label.to_string());
222}
223
224fn device_label() -> &'static str {
225 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
226}
227
228static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
229
230static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
239
240pub fn set_cache_dir(dir: std::path::PathBuf) {
242 let _ = CACHE_DIR.set(dir);
243}
244
245pub fn cache_dir_pub() -> std::path::PathBuf {
247 cache_dir()
248}
249
250fn cache_dir() -> std::path::PathBuf {
251 if let Some(d) = CACHE_DIR.get() {
252 return d.clone();
253 }
254 match std::env::var_os("TMPDIR") {
255 Some(t) => std::path::PathBuf::from(t),
256 None => std::env::temp_dir(),
257 }
258}
259
260fn probe_cache_path() -> Option<std::path::PathBuf> {
263 match std::env::var("CMF_PROBE_CACHE") {
264 Ok(v) if v == "0" => None,
265 Ok(v) => Some(std::path::PathBuf::from(v)),
266 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
267 }
268}
269
270fn probe_cache_key_named(class: &str) -> String {
274 format!(
275 "{}\t{}\t{}",
276 env!("CARGO_PKG_VERSION"),
277 device_label(),
278 class
279 )
280}
281
282const CLASS_NAMES: [&str; 7] = [
283 "ffn",
284 "matvec",
285 "matmat",
286 "qkv-batch",
287 "matmat-wide",
288 "lm-head",
289 "gemm-nt",
290];
291
292fn probe_cache_load() {
301 static ONCE: std::sync::Once = std::sync::Once::new();
302 ONCE.call_once(|| {
303 let Some(path) = probe_cache_path() else {
304 return;
305 };
306 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
311 return;
312 }
313 let Ok(text) = std::fs::read_to_string(&path) else {
314 return;
315 };
316 probe_cache_adopt(&text);
317 });
318}
319
320fn probe_cache_adopt(text: &str) {
324 for line in text.lines() {
325 let Some((key, verdict)) = line.rsplit_once('\t') else {
326 continue;
327 };
328 let winner = match verdict.trim() {
329 "gpu" => 1u8,
330 "cpu" => 2u8,
331 _ => continue,
332 };
333 for (i, name) in CLASS_NAMES.iter().enumerate() {
334 if probe_cache_key_named(name) == key {
335 let _ = PROBES[i].state.compare_exchange(
336 0,
337 winner,
338 Ordering::Relaxed,
339 Ordering::Relaxed,
340 );
341 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
342 }
343 }
344 }
345}
346
347fn probe_cache_store(c: OpClass, winner: u8) {
350 let Some(path) = probe_cache_path() else {
351 return;
352 };
353 let line = format!(
354 "{}\t{}\n",
355 probe_cache_key_named(CLASS_NAMES[c as usize]),
356 if winner == 1 { "gpu" } else { "cpu" }
357 );
358 use std::io::Write;
359 if let Ok(mut f) = std::fs::OpenOptions::new()
360 .create(true)
361 .append(true)
362 .open(&path)
363 {
364 let _ = f.write_all(line.as_bytes());
365 }
366}
367
368pub fn cold_epoch() -> u64 {
374 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
375}
376static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
377
378pub(crate) fn probe_note_cold() {
379 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
380 PROBE_COLD.with(|c| c.set(true));
381}
382
383pub(crate) fn probe_was_cold() -> bool {
387 PROBE_COLD.with(|c| c.get())
388}
389
390pub fn set_layer(l: i64) {
392 CUR_LAYER.with(|c| c.set(l));
393}
394
395pub fn cur_layer() -> i64 {
397 CUR_LAYER.with(|c| c.get())
398}
399
400pub fn automatic_layer_prefix(
403 model: &Arc<CmfModel>,
404 num_layers: usize,
405 physical_layers: usize,
406) -> Option<usize> {
407 match backend() {
408 #[cfg(feature = "gpu")]
409 Backend::Wgpu => {
410 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
411 }
412 _ => None,
413 }
414}
415
416fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
419 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
420 R.get_or_init(|| {
421 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
422 let mut v = Vec::new();
423 for part in s.split(',') {
424 let part = part.trim();
425 match part.split_once('-') {
426 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
427 None => {
428 let x: i64 = part.parse().ok()?;
429 v.push((x, x));
430 }
431 }
432 }
433 Some(v)
434 })
435}
436
437fn layer_allowed() -> bool {
438 match layer_ranges() {
439 None => true,
440 Some(ranges) => {
441 let cur = CUR_LAYER.with(|c| c.get());
442 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
443 }
444 }
445}
446
447pub fn enabled_here() -> bool {
451 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
452}
453
454pub fn q2tp_gpu_opt_in() -> bool {
460 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
461}
462
463#[derive(Clone, Copy)]
475pub enum OpClass {
476 Ffn = 0,
478 Matvec = 1,
480 Matmat = 2,
482 Batch = 3,
484 MatmatWide = 4,
490 MatvecHead = 5,
497 GemmNt = 6,
504}
505
506pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
510 if rows * cols >= 67_108_864 {
511 OpClass::MatvecHead
512 } else {
513 OpClass::Matvec
514 }
515}
516
517pub enum ProbeArm {
519 Gpu,
521 CpuTimed,
523 Cpu,
525}
526
527const PROBE_SAMPLES: u32 = 6;
529
530const PROBE_DECLINE_LIMIT: u32 = 16;
534
535const PROBE_WARMUP: u32 = 1;
537
538struct Probe {
539 state: AtomicU8,
541 flip: AtomicU32,
542 gpu_ns: AtomicU64,
543 gpu_n: AtomicU32,
544 declines: AtomicU32,
554 gpu_burn: AtomicU32,
567 cpu_ns: AtomicU64,
568 cpu_n: AtomicU32,
569 gpu_min: AtomicU64,
576 cpu_min: AtomicU64,
577}
578
579impl Probe {
580 const fn new() -> Self {
581 Self {
582 state: AtomicU8::new(0),
583 flip: AtomicU32::new(0),
584 gpu_ns: AtomicU64::new(0),
585 gpu_n: AtomicU32::new(0),
586 declines: AtomicU32::new(0),
587 gpu_burn: AtomicU32::new(PROBE_WARMUP),
588 cpu_ns: AtomicU64::new(0),
589 cpu_n: AtomicU32::new(0),
590 gpu_min: AtomicU64::new(u64::MAX),
591 cpu_min: AtomicU64::new(u64::MAX),
592 }
593 }
594}
595
596static PROBES: [Probe; 7] = [
597 Probe::new(),
598 Probe::new(),
599 Probe::new(),
600 Probe::new(),
601 Probe::new(),
602 Probe::new(),
603 Probe::new(),
604];
605
606static TRUST_GPU: AtomicBool = AtomicBool::new(false);
613
614pub fn trust_gpu() -> GpuTrust {
616 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
617 GpuTrust(was)
618}
619
620pub struct GpuTrust(bool);
621
622impl Drop for GpuTrust {
623 fn drop(&mut self) {
624 TRUST_GPU.store(self.0, Ordering::Relaxed);
625 }
626}
627
628fn probe_on_for(c: OpClass) -> bool {
629 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
635 return false;
636 }
637 probe_on()
638}
639
640pub fn probe_enabled() -> bool {
644 probe_on()
645}
646
647fn probe_on() -> bool {
648 static ON: OnceLock<bool> = OnceLock::new();
649 *ON.get_or_init(|| {
650 std::env::var("CMF_GPU_PROBE")
651 .map(|v| v != "0" && v != "off")
652 .unwrap_or(true)
653 })
654}
655
656pub fn q1_force() -> bool {
661 #[cfg(target_os = "macos")]
662 {
663 backend() == Backend::Metal
664 }
665 #[cfg(not(target_os = "macos"))]
666 {
667 false
668 }
669}
670
671pub fn fused_block_trusted() -> bool {
690 #[cfg(target_os = "macos")]
691 if backend() == Backend::Metal {
692 return true;
693 }
694 wgpu_graph_default()
695}
696
697pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
709 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
710 {
711 return crate::gpu_wgpu::weight_is_resident(model, idx);
712 }
713 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
714 {
715 let _ = (model, idx);
716 true
717 }
718}
719
720pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
721 if !weights_resident && probe_deciding(c) {
722 return ProbeArm::Gpu;
723 }
724 probe_arm(c)
725}
726
727pub fn probe_arm(c: OpClass) -> ProbeArm {
728 PROBE_COLD.with(|f| f.set(false));
733 if !probe_on_for(c) {
734 return ProbeArm::Gpu;
735 }
736 probe_cache_load();
737 let p = &PROBES[c as usize];
738 match p.state.load(Ordering::Relaxed) {
739 1 => ProbeArm::Gpu,
740 2 => ProbeArm::Cpu,
741 _ => {
742 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
743 ProbeArm::Gpu
744 } else {
745 ProbeArm::CpuTimed
746 }
747 }
748 }
749}
750
751pub fn probe_note_decline(c: OpClass) {
755 let p = &PROBES[c as usize];
756 if p.state.load(Ordering::Relaxed) != 0 {
757 return;
758 }
759 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
760 if n >= PROBE_DECLINE_LIMIT
761 && p.state
762 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
763 .is_ok()
764 {
765 tracing::info!(
766 "gpu probe [{}]: device declined {n} times → cpu",
767 CLASS_NAMES[c as usize]
768 );
769 }
770}
771
772pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
775 probe_record_into(
776 &PROBES[c as usize],
777 CLASS_NAMES[c as usize],
778 Some(c),
779 gpu,
780 dur,
781 )
782}
783
784fn probe_record_into(
787 p: &Probe,
788 class_name: &str,
789 cache: Option<OpClass>,
790 gpu: bool,
791 dur: std::time::Duration,
792) {
793 if p.state.load(Ordering::Relaxed) != 0 {
794 return;
795 }
796 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
797 return; }
799 if gpu {
800 let left = p.gpu_burn.load(Ordering::Relaxed);
804 if left > 0 {
805 p.gpu_burn.store(left - 1, Ordering::Relaxed);
806 return; }
808 }
809 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
810 if gpu {
811 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
812 p.gpu_n.fetch_add(1, Ordering::Relaxed);
813 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
814 } else {
815 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
816 p.cpu_n.fetch_add(1, Ordering::Relaxed);
817 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
818 }
819 let (gn, cn) = (
820 p.gpu_n.load(Ordering::Relaxed),
821 p.cpu_n.load(Ordering::Relaxed),
822 );
823 if gn >= 2 && cn >= 2 {
824 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
828 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
829 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
839 return;
840 }
841 let winner = if g <= cp { 1 } else { 2 };
842 if p.state
843 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
844 .is_ok()
845 {
846 tracing::info!(
847 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
848 class_name,
849 g / 1e6,
850 cp / 1e6,
851 if winner == 1 { "gpu" } else { "cpu" },
852 );
853 if let Some(c) = cache {
854 probe_cache_store(c, winner);
855 }
856 }
857 }
858}
859
860pub fn probe_deciding(c: OpClass) -> bool {
863 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
864}
865
866#[allow(unused_variables)]
876pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
877 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
878 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
879 let resident = match backend() {
880 #[cfg(target_os = "macos")]
881 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
882 #[cfg(feature = "gpu")]
883 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
884 Backend::None => false,
885 };
886 if !resident && may_upload {
887 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
888 }
889 resident
890}
891
892#[cfg(test)]
894pub(crate) fn probe_reset() {
895 for p in &PROBES {
896 p.state.store(0, Ordering::Relaxed);
897 p.flip.store(0, Ordering::Relaxed);
898 p.gpu_ns.store(0, Ordering::Relaxed);
899 p.gpu_n.store(0, Ordering::Relaxed);
900 p.cpu_ns.store(0, Ordering::Relaxed);
901 p.cpu_n.store(0, Ordering::Relaxed);
902 }
903}
904
905#[cfg(test)]
909static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
910
911#[cfg(test)]
912fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
913 PROBE_TEST_LOCK
914 .lock()
915 .unwrap_or_else(std::sync::PoisonError::into_inner)
916}
917
918#[cfg(test)]
919mod probe_tests {
920 use super::*;
921 use std::time::Duration;
922
923 #[test]
924 fn cpu_only_whole_operator_dispatch_inherits_and_restores_scope() {
925 let pool = crate::pool::Pool::with_spin(3, 0);
926 cpu_scope(|| {
927 let inherit = inherit_cpu_scope();
928 pool.run_rows(64, &|_, _| {
929 let _guard = inherit();
930 assert!(CPU_ONLY.get());
931 });
932 });
933 pool.run_rows(64, &|_, _| assert!(!CPU_ONLY.get()));
934 }
935
936 #[test]
939 fn probe_alternates_discards_cold_and_decides() {
940 let _probe_guard = probe_test_guard();
941 probe_reset();
942 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
944 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
945
946 probe_note_cold();
950 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
951 for _ in 0..PROBE_SAMPLES {
952 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
953 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
954 }
955 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
956
957 for _ in 0..PROBE_SAMPLES {
959 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
960 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
961 }
962 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
963
964 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
966 CPU_ONLY.with(|c| assert!(!c.get()));
967 cpu_scope(|| {
968 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
969 CPU_ONLY.with(|c| assert!(c.get()));
970 });
971 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
972 CPU_ONLY.with(|c| assert!(!c.get()));
973 probe_reset();
974 }
975
976 #[test]
977 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
978 let _probe_guard = probe_test_guard();
979 let mine = probe_cache_key_named("gemm-nt");
991 let state = || {
992 PROBES[OpClass::GemmNt as usize]
993 .state
994 .load(Ordering::Relaxed)
995 };
996
997 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
999 assert_eq!(state(), 0);
1000 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
1002 assert_ne!(older, mine);
1003 probe_cache_adopt(&format!("{older}\tgpu\n"));
1004 assert_eq!(state(), 0);
1005 probe_cache_adopt(&format!("{mine}\tcpu\n"));
1007 assert_eq!(state(), 2);
1008
1009 PROBES[OpClass::GemmNt as usize]
1010 .state
1011 .store(0, Ordering::Relaxed);
1012 }
1013}
1014
1015pub const GPU_MIN_ROWS: usize = 65_536;
1018
1019pub fn min_rows() -> usize {
1026 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
1027 .ok()
1028 .and_then(|v| v.parse().ok())
1029 {
1030 return v;
1031 }
1032 if discrete() { 4096 } else { GPU_MIN_ROWS }
1033}
1034
1035pub fn discrete() -> bool {
1037 match backend() {
1038 #[cfg(feature = "gpu")]
1039 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
1040 #[cfg(target_os = "macos")]
1041 Backend::Metal => false, Backend::None => false,
1043 }
1044}
1045
1046pub struct MoeJob<'a> {
1050 pub gate: (usize, usize, usize, &'a [f32]),
1051 pub up: (usize, usize, usize, &'a [f32]),
1052 pub down: (usize, usize, usize, &'a [f32]),
1053 pub xs_gate: Vec<f32>,
1054 pub xs_up: Vec<f32>,
1055 pub down_col: &'a [f32],
1056 pub w: f32,
1057 pub q1: bool,
1060 pub q4t: bool,
1063 pub q4tp: bool,
1067 pub gu_q2: bool,
1071 pub swiglu_limit: f32,
1076}
1077
1078pub struct BatchJob<'a> {
1080 pub idx: usize,
1081 pub rows: usize,
1082 pub cols: usize,
1083 pub row_scale: &'a [f32],
1084 pub xs: Vec<f32>,
1085 pub layout: BatchLayout,
1089}
1090
1091#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1094pub enum BatchLayout {
1095 Q8,
1096 Q1,
1097 Q4t,
1098 Q4tp,
1099}
1100
1101#[derive(Clone, Copy, PartialEq, Eq)]
1102enum Backend {
1103 None,
1104 #[cfg(target_os = "macos")]
1105 Metal,
1106 #[cfg(feature = "gpu")]
1107 Wgpu,
1108}
1109
1110fn backend() -> Backend {
1111 #[cfg(feature = "gpu")]
1112 if crate::gpu_wgpu::selected() {
1113 return if crate::gpu_wgpu::enabled() {
1114 Backend::Wgpu
1115 } else {
1116 Backend::None
1117 };
1118 }
1119 #[cfg(target_os = "macos")]
1120 if crate::gpu_metal::enabled() {
1121 return Backend::Metal;
1122 }
1123 Backend::None
1124}
1125
1126pub fn backend_available() -> bool {
1132 #[cfg(target_os = "macos")]
1133 {
1134 true
1136 }
1137 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1138 {
1139 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1140 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
1141 }
1142 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
1143 {
1144 false
1145 }
1146}
1147
1148static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
1154
1155pub fn pause_gpu() -> GpuPause {
1157 GPU_PAUSED.store(true, Ordering::Relaxed);
1158 GpuPause(())
1159}
1160
1161pub struct GpuPause(());
1162
1163impl Drop for GpuPause {
1164 fn drop(&mut self) {
1165 GPU_PAUSED.store(false, Ordering::Relaxed);
1166 }
1167}
1168
1169pub fn enabled() -> bool {
1170 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
1171}
1172
1173pub fn wgpu_active() -> bool {
1187 #[cfg(feature = "gpu")]
1188 {
1189 matches!(backend(), Backend::Wgpu)
1190 }
1191 #[cfg(not(feature = "gpu"))]
1192 {
1193 false
1194 }
1195}
1196
1197pub fn default_device() -> usize {
1204 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1205 *D.get_or_init(|| {
1206 std::env::var("CMF_GPU_ADAPTER")
1207 .ok()
1208 .and_then(|v| v.trim().parse::<usize>().ok())
1209 .unwrap_or(0)
1210 })
1211}
1212
1213thread_local! {
1214 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1215}
1216
1217pub fn current_device() -> usize {
1219 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1220}
1221
1222pub fn set_current_device(i: usize) {
1226 CUR_DEV.with(|c| c.set(Some(i)));
1227}
1228
1229pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1231 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1232 let r = f();
1233 CUR_DEV.with(|c| c.set(prev));
1234 r
1235}
1236
1237pub fn device_count() -> usize {
1240 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1241 {
1242 return crate::gpu_wgpu::adapter_count();
1243 }
1244 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1245 {
1246 usize::from(backend_available())
1247 }
1248}
1249
1250pub fn vram_budget() -> u64 {
1254 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1255 {
1256 return crate::gpu_wgpu::device_vram_budget();
1257 }
1258 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1259 {
1260 if backend_available() { u64::MAX } else { 0 }
1261 }
1262}
1263
1264pub fn resident_bytes() -> u64 {
1268 #[cfg(feature = "gpu")]
1269 {
1270 if backend() == Backend::Wgpu {
1271 return crate::gpu_wgpu::resident_bytes();
1272 }
1273 }
1274 0
1275}
1276
1277pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1281 #[cfg(feature = "gpu")]
1282 {
1283 if backend() == Backend::Wgpu {
1284 return crate::gpu_wgpu::o1_device_stats(kv_id);
1285 }
1286 }
1287 let _ = kv_id;
1288 (0, 0)
1289}
1290
1291pub fn upload_bytes() -> u64 {
1295 #[cfg(feature = "gpu")]
1296 {
1297 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1298 }
1299 #[cfg(not(feature = "gpu"))]
1300 0
1301}
1302
1303pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1307 #[cfg(feature = "gpu")]
1308 {
1309 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1310 }
1311 let _ = (block, rounds);
1312 None
1313}
1314
1315#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1327pub enum GraphPhase {
1328 Prefill,
1329 Decode,
1330}
1331
1332pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1340 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1341 Some("0") => false,
1342 Some("prefill") => phase == GraphPhase::Prefill,
1343 Some(_) => true,
1344 None => {
1345 if wgpu_graph_default() {
1346 return true;
1347 }
1348 let _ = phase;
1353 false
1354 }
1355 }
1356}
1357
1358pub fn wgpu_graph_default() -> bool {
1359 #[cfg(feature = "gpu")]
1360 {
1361 matches!(backend(), Backend::Wgpu)
1367 && (crate::gpu_wgpu::discrete_active()
1368 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1369 }
1370 #[cfg(not(feature = "gpu"))]
1371 {
1372 false
1373 }
1374}
1375
1376#[allow(clippy::too_many_arguments, unused_variables)]
1378pub fn q8_matvec_range(
1379 model: &Arc<CmfModel>,
1380 idx: usize,
1381 row0: usize,
1382 row_scale: &[f32],
1383 xs: &[f32],
1384 rows: usize,
1385 cols: usize,
1386 out: &mut [f32],
1387) -> bool {
1388 match backend() {
1389 #[cfg(target_os = "macos")]
1390 Backend::Metal => {
1391 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1392 }
1393 #[cfg(feature = "gpu")]
1394 Backend::Wgpu => {
1395 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1396 }
1397 Backend::None => false,
1398 }
1399}
1400
1401pub(crate) fn q82_short_rows(
1403 model: &Arc<CmfModel>,
1404 idx: usize,
1405 xs: &[f32],
1406 b: usize,
1407 rows: usize,
1408 cols: usize,
1409 out: &mut [f32],
1410) -> bool {
1411 #[cfg(feature = "gpu")]
1412 if enabled_here() && backend() == Backend::Wgpu {
1413 return crate::gpu_wgpu::q82_short_rows(model, idx, xs, b, rows, cols, out);
1414 }
1415 let _ = (model, idx, xs, b, rows, cols, out);
1416 false
1417}
1418
1419#[allow(clippy::too_many_arguments, unused_variables)]
1422#[allow(clippy::too_many_arguments)]
1426pub fn q8_matmat_2f(
1427 model: &Arc<CmfModel>,
1428 idx: usize,
1429 row_scale: &[f32],
1430 col_field: &[f32],
1431 xs: &[f32],
1432 b: usize,
1433 rows: usize,
1434 cols: usize,
1435 out: &mut [f32],
1436) -> bool {
1437 #[allow(unreachable_patterns)]
1438 match backend() {
1439 #[cfg(feature = "gpu")]
1440 Backend::Wgpu => {
1441 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1442 }
1443 _ => false,
1444 }
1445}
1446
1447pub fn q8_matmat(
1448 model: &Arc<CmfModel>,
1449 idx: usize,
1450 row_scale: &[f32],
1451 pre: &[f32],
1452 b: usize,
1453 rows: usize,
1454 cols: usize,
1455 out: &mut [f32],
1456) -> bool {
1457 match backend() {
1458 #[cfg(target_os = "macos")]
1459 Backend::Metal => {
1460 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1461 }
1462 #[cfg(feature = "gpu")]
1463 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1464 Backend::None => false,
1465 }
1466}
1467
1468#[allow(unused_variables)]
1471pub fn q1_matvec(
1472 model: &Arc<CmfModel>,
1473 idx: usize,
1474 xs: &[f32],
1475 rows: usize,
1476 cols: usize,
1477 out: &mut [f32],
1478) -> bool {
1479 match backend() {
1480 #[cfg(target_os = "macos")]
1481 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1482 #[cfg(feature = "gpu")]
1483 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1484 Backend::None => false,
1485 }
1486}
1487
1488#[allow(clippy::too_many_arguments)]
1492pub fn attn_dropin(
1493 model: &Arc<CmfModel>,
1494 kv_id: u64,
1495 layer: usize,
1496 normed: &[f32],
1497 wq_idx: usize,
1498 wk_idx: usize,
1499 wv_idx: usize,
1500 wo_idx: usize,
1501 q_norm: Option<&[f32]>,
1502 k_norm: Option<&[f32]>,
1503 late_qk_norm: bool,
1504 invf: &[f32],
1505 nh: usize,
1506 nkv: usize,
1507 hd: usize,
1508 rd: usize,
1509 hidden: usize,
1510 pos: usize,
1511 cap: usize,
1512 gemma: bool,
1513 eps: f32,
1514 cpu_k: &[Vec<f32>],
1515 cpu_v: &[Vec<f32>],
1516 out: &mut [f32],
1517) -> bool {
1518 match backend() {
1519 #[cfg(feature = "gpu")]
1520 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1521 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1522 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1523 ),
1524 #[allow(unused_variables)]
1525 _ => false,
1526 }
1527}
1528
1529#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1533pub enum GraphPrismOp {
1534 None,
1535 Forward,
1536 InverseEmbedding,
1537}
1538
1539pub struct GraphW<'a> {
1543 pub idx: usize,
1544 pub kind: u8,
1545 pub row_scale: &'a [f32],
1546 pub data: &'a [f32],
1547 pub prism: GraphPrismOp,
1548 pub affine: bool,
1549}
1550
1551pub enum GraphAttn<'a> {
1554 Full {
1555 wq: GraphW<'a>,
1556 wk: GraphW<'a>,
1557 wv: GraphW<'a>,
1558 wo: GraphW<'a>,
1559 q_norm: Option<&'a [f32]>,
1560 k_norm: Option<&'a [f32]>,
1561 late_qk_norm: bool,
1563 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1565 output_gate: bool,
1568 cpu_k: &'a [Vec<f32>],
1569 cpu_v: &'a [Vec<f32>],
1570 geom: Option<GraphAttnGeom<'a>>,
1577 head_gate: Option<GraphW<'a>>,
1582 },
1583 Gdn {
1584 qkv: GraphW<'a>,
1585 z: GraphW<'a>,
1586 a: GraphW<'a>,
1587 b: GraphW<'a>,
1588 out: GraphW<'a>,
1589 conv1d: &'a [f32],
1590 a_log: &'a [f32],
1591 dt_bias: &'a [f32],
1592 norm: &'a [f32],
1593 nv: usize,
1594 nk: usize,
1595 dk: usize,
1596 dv: usize,
1597 kk: usize,
1598 cpu_state: &'a [f32],
1603 },
1604 ShortConv {
1611 inp: GraphW<'a>,
1613 out: GraphW<'a>,
1615 taps: &'a [f32],
1618 kernel: usize,
1619 cpu_state: &'a [f32],
1623 },
1624}
1625
1626#[derive(Clone, Copy)]
1631pub struct GraphAttnGeom<'a> {
1632 pub nkv: usize,
1634 pub dv: usize,
1636 pub rd: usize,
1638 pub invf: &'a [f32],
1640 pub window: Option<usize>,
1643 pub sink: Option<&'a [f32]>,
1646}
1647
1648#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1652pub enum GraphAct {
1653 Silu,
1654 GeluErf,
1657}
1658
1659impl GraphAct {
1660 pub fn code(self) -> u32 {
1661 match self {
1662 Self::Silu => 0,
1663 Self::GeluErf => 1,
1664 }
1665 }
1666}
1667
1668pub struct GraphLayer<'a> {
1670 pub input_norm: &'a [f32],
1671 pub attn: GraphAttn<'a>,
1672 pub post_norm: &'a [f32],
1673 pub ffn: GraphFfn<'a>,
1674}
1675
1676pub enum GraphFfn<'a> {
1681 AttentionOnly,
1684 Dense {
1685 gate: GraphW<'a>,
1686 up: GraphW<'a>,
1687 down: GraphW<'a>,
1688 act: GraphAct,
1691 },
1692 Moe {
1693 router: GraphW<'a>,
1695 shared_gate: GraphW<'a>,
1697 experts: Vec<(usize, usize, usize)>,
1701 n_exp: usize,
1703 top_k: usize,
1704 inter: usize,
1705 norm_topk: bool,
1706 q4tp: bool,
1712 gu_q2: bool,
1716 sigmoid: bool,
1720 bias: Option<&'a [f32]>,
1723 has_shared: bool,
1727 shared_gated: bool,
1732 route_scale: f32,
1735 },
1736}
1737
1738#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1741pub enum TokenGraphOutcome {
1742 Declined,
1744 Completed,
1746 Failed,
1748}
1749
1750#[allow(clippy::too_many_arguments)]
1755pub fn forward_token_graph(
1756 model: &Arc<CmfModel>,
1757 kv_id: u64,
1758 layers: &[GraphLayer],
1759 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1762 o1_epoch: u64,
1763 invf: &[f32],
1764 h: &mut [f32],
1765 nh: usize,
1766 nkv: usize,
1767 hd: usize,
1768 attn_scale: f32,
1769 rd: usize,
1770 hidden: usize,
1771 inter: usize,
1772 position: usize,
1773 cap: usize,
1774 gemma: bool,
1775 eps: f32,
1776 lm_head: Option<(&GraphW, usize)>,
1777 final_norm: &[f32],
1778 logits: &mut Vec<f32>,
1779 loop_norm_at: &[usize],
1780 steps: usize,
1781 embed: Option<(&GraphW, usize, f32)>,
1782 ids_out: Option<&mut Vec<u32>>,
1783 layers_run: Option<&mut usize>,
1786 layer_base: usize,
1790 hidden_too: bool,
1792) -> TokenGraphOutcome {
1793 match backend() {
1794 #[cfg(feature = "gpu")]
1795 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1796 model,
1797 kv_id,
1798 layers,
1799 o1,
1800 o1_epoch,
1801 invf,
1802 h,
1803 nh,
1804 nkv,
1805 hd,
1806 attn_scale,
1807 rd,
1808 hidden,
1809 inter,
1810 position,
1811 cap,
1812 gemma,
1813 eps,
1814 lm_head,
1815 final_norm,
1816 logits,
1817 loop_norm_at,
1818 steps,
1819 embed,
1820 ids_out,
1821 layers_run,
1822 layer_base,
1823 hidden_too,
1824 ),
1825 #[allow(unused_variables)]
1826 _ => {
1827 let _ = (
1828 attn_scale,
1829 lm_head,
1830 final_norm,
1831 logits,
1832 loop_norm_at,
1833 layers_run,
1834 layer_base,
1835 hidden_too,
1836 );
1837 TokenGraphOutcome::Declined
1838 }
1839 }
1840}
1841
1842pub fn forward_embryo_graph(
1847 model: &Arc<EmbryoGraphModel>,
1848 kv_id: u64,
1849 hidden: &[f32],
1850 position: usize,
1851 logits: &mut Vec<f32>,
1852) -> bool {
1853 #[cfg(feature = "gpu")]
1854 if backend() == Backend::Wgpu {
1855 return crate::gpu_wgpu::forward_embryo_graph(model, kv_id, hidden, position, logits);
1856 }
1857 let _ = (model, kv_id, hidden, position, logits);
1858 false
1859}
1860
1861#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1865pub enum BatchGraphOutcome {
1866 Declined,
1869 Completed,
1871 Failed,
1875}
1876
1877pub struct SpecTail<'a> {
1878 pub lm: GraphW<'a>,
1879 pub lm_rows: usize,
1880 pub final_norm: &'a [f32],
1881 pub logits_out: &'a mut Vec<f32>,
1882}
1883
1884#[allow(clippy::too_many_arguments)]
1888pub fn forward_batch_graph(
1889 model: &Arc<CmfModel>,
1890 kv_id: u64,
1891 layers: &[GraphLayer],
1892 invf: &[f32],
1893 h: &mut [f32],
1894 nh: usize,
1895 nkv: usize,
1896 hd: usize,
1897 rd: usize,
1898 hidden: usize,
1899 inter: usize,
1900 positions: &[usize],
1901 cap: usize,
1902 gemma: bool,
1903 eps: f32,
1904 attn_scale: f32,
1905 k: usize,
1906 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1909 o1_epoch: u64,
1910 spec: Option<SpecTail<'_>>,
1911 layers_run: Option<&mut usize>,
1917) -> BatchGraphOutcome {
1918 forward_batch_graph_at(model, kv_id, 0, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run)
1919}
1920
1921thread_local! {
1922 static MIMO_ATTN_SCRATCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1923}
1924
1925pub(crate) fn mimo_attention_scratch_enabled() -> bool {
1926 MIMO_ATTN_SCRATCH.with(std::cell::Cell::get)
1927}
1928
1929#[doc(hidden)]
1932pub fn mimo_attention_scratch_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
1933 struct Restore(bool);
1934 impl Drop for Restore {
1935 fn drop(&mut self) {
1936 MIMO_ATTN_SCRATCH.with(|v| v.set(self.0));
1937 }
1938 }
1939 let _restore = Restore(MIMO_ATTN_SCRATCH.with(|v| v.replace(enabled)));
1940 f()
1941}
1942
1943thread_local! {
1944 static MIMO_Q8_SHORT: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1945}
1946
1947pub(crate) fn mimo_q8_short_enabled() -> bool {
1948 MIMO_Q8_SHORT.with(std::cell::Cell::get)
1949}
1950
1951#[doc(hidden)]
1953pub fn mimo_q8_short_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
1954 struct Restore(bool);
1955 impl Drop for Restore {
1956 fn drop(&mut self) {
1957 MIMO_Q8_SHORT.with(|v| v.set(self.0));
1958 }
1959 }
1960 let _restore = Restore(MIMO_Q8_SHORT.with(|v| v.replace(enabled)));
1961 f()
1962}
1963
1964#[allow(clippy::too_many_arguments)]
1966pub fn forward_batch_graph_at(
1967 model: &Arc<CmfModel>,
1968 kv_id: u64,
1969 layer_base: usize,
1970 layers: &[GraphLayer],
1971 invf: &[f32],
1972 h: &mut [f32],
1973 nh: usize,
1974 nkv: usize,
1975 hd: usize,
1976 rd: usize,
1977 hidden: usize,
1978 inter: usize,
1979 positions: &[usize],
1980 cap: usize,
1981 gemma: bool,
1982 eps: f32,
1983 attn_scale: f32,
1984 k: usize,
1985 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1988 o1_epoch: u64,
1989 spec: Option<SpecTail<'_>>,
1990 layers_run: Option<&mut usize>,
1996) -> BatchGraphOutcome {
1997 match backend() {
1998 #[cfg(feature = "gpu")]
1999 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph_at(
2000 model, kv_id, layer_base, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions,
2001 cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run,
2002 ),
2003 #[allow(unreachable_patterns)]
2004 _ => {
2005 let _ = (o1, o1_epoch, spec, layers_run);
2006 BatchGraphOutcome::Declined
2007 }
2008 }
2009}
2010
2011pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
2016 #[cfg(feature = "gpu")]
2017 if backend() == Backend::Wgpu {
2018 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
2019 }
2020 #[allow(unreachable_code)]
2021 {
2022 let _ = (kv_id, slot, base_pos, expected_layers);
2023 false
2024 }
2025}
2026
2027pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
2033 #[cfg(feature = "gpu")]
2034 if backend() == Backend::Wgpu {
2035 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
2036 }
2037 #[cfg(target_os = "macos")]
2038 if backend() == Backend::Metal {
2039 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
2040 return true;
2041 }
2042 false
2043}
2044
2045pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
2049 #[cfg(feature = "gpu")]
2050 if backend() == Backend::Wgpu {
2051 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
2052 }
2053 None
2054}
2055
2056pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
2059 #[cfg(feature = "gpu")]
2060 if backend() == Backend::Wgpu {
2061 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
2062 }
2063 false
2064}
2065
2066pub fn graph_kv_read_rows(
2070 _kv_id: u64,
2071 _reqs: &[(usize, usize, usize)],
2072 _nkv: usize,
2073 _hd: usize,
2074) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
2075 #[cfg(feature = "gpu")]
2076 if backend() == Backend::Wgpu {
2077 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
2078 }
2079 None
2080}
2081
2082pub fn graph_kv_pull_host(
2088 _kv_id: u64,
2089 _layer: usize,
2090 _from: usize,
2091 _to: usize,
2092 _nkv: usize,
2093 _hd: usize,
2094) -> Option<(Vec<f32>, Vec<f32>, usize)> {
2095 #[cfg(feature = "gpu")]
2096 if backend() == Backend::Wgpu {
2097 return crate::gpu_wgpu::kv_mirror_pull_host(_kv_id, _layer, _from, _to, _nkv, _hd);
2098 }
2099 None
2100}
2101
2102pub fn graph_kv_reset(_kv_id: u64) {
2104 #[cfg(feature = "gpu")]
2105 if backend() == Backend::Wgpu {
2106 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
2107 crate::gpu_wgpu::embryo_graph_reset(_kv_id);
2108 }
2109}
2110
2111pub fn q1t_matvec(
2115 model: &Arc<CmfModel>,
2116 idx: usize,
2117 xs: &[f32],
2118 rows: usize,
2119 cols: usize,
2120 out: &mut [f32],
2121) -> bool {
2122 match backend() {
2123 #[cfg(target_os = "macos")]
2124 Backend::Metal => {
2125 if metal_q1t_enabled() {
2126 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
2127 } else {
2128 false
2129 }
2130 }
2131 #[cfg(feature = "gpu")]
2132 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
2133 Backend::None => false,
2134 }
2135}
2136
2137#[allow(unused_variables)]
2140pub fn q4b_matvec(
2141 model: &Arc<CmfModel>,
2142 idx: usize,
2143 xs: &[f32],
2144 rows: usize,
2145 cols: usize,
2146 out: &mut [f32],
2147) -> bool {
2148 match backend() {
2149 #[cfg(target_os = "macos")]
2150 Backend::Metal => false,
2151 #[cfg(feature = "gpu")]
2152 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
2153 Backend::None => false,
2154 }
2155}
2156
2157pub fn q1t_matmat(
2160 model: &Arc<CmfModel>,
2161 idx: usize,
2162 xs: &[f32],
2163 b: usize,
2164 rows: usize,
2165 cols: usize,
2166 out: &mut [f32],
2167) -> bool {
2168 match backend() {
2169 #[cfg(target_os = "macos")]
2170 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
2174 #[cfg(feature = "gpu")]
2175 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
2176 Backend::None => false,
2177 }
2178}
2179
2180#[cfg(target_os = "macos")]
2184pub(crate) fn metal_q1t_enabled() -> bool {
2185 std::env::var("CMF_METAL_Q1T")
2186 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
2187 .unwrap_or(true)
2188}
2189
2190pub fn q1_matmat(
2192 model: &Arc<CmfModel>,
2193 idx: usize,
2194 xs: &[f32],
2195 b: usize,
2196 rows: usize,
2197 cols: usize,
2198 out: &mut [f32],
2199) -> bool {
2200 match backend() {
2201 #[cfg(feature = "gpu")]
2202 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
2203 #[allow(unused_variables)]
2204 _ => false,
2205 }
2206}
2207
2208static MM_KILL: AtomicBool = AtomicBool::new(false);
2213pub(crate) fn mm_killed() -> bool {
2214 MM_KILL.load(Ordering::Relaxed)
2215}
2216pub(crate) fn mm_kill() {
2217 MM_KILL.store(true, Ordering::Relaxed);
2218}
2219
2220static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2227const MM_STRIKES_TO_KILL: u32 = 3;
2228static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2235
2236pub fn mm_kill_arm(on: bool) {
2239 MM_ARMED.store(on, Ordering::Relaxed);
2240 if on {
2241 MM_STRIKES.store(0, Ordering::Relaxed);
2242 }
2243}
2244
2245pub(crate) fn mm_budget_check(
2252 what: &str,
2253 el: std::time::Duration,
2254 budget: std::time::Duration,
2255 exempt: bool,
2256) {
2257 if el <= budget {
2258 MM_STRIKES.store(0, Ordering::Relaxed);
2259 return;
2260 }
2261 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
2262 return;
2263 }
2264 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2265 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
2266 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
2267 if !on {
2268 tracing::info!(
2269 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
2270 );
2271 return;
2272 }
2273 if n >= MM_STRIKES_TO_KILL {
2274 tracing::warn!(
2275 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
2276 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
2277 );
2278 mm_kill();
2279 } else {
2280 tracing::info!(
2281 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
2282 );
2283 }
2284}
2285
2286#[allow(unused_variables, clippy::too_many_arguments)]
2291pub fn chunk_attend(
2292 q: &[f32],
2293 k: &[&[f32]],
2294 v: &[&[f32]],
2295 b: usize,
2296 s0: usize,
2297 nh: usize,
2298 nkv: usize,
2299 hd: usize,
2300 scale: f32,
2301 out: &mut [f32],
2302) -> bool {
2303 match backend() {
2304 #[cfg(feature = "gpu")]
2305 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
2306 #[allow(unreachable_patterns)]
2307 _ => false,
2308 }
2309}
2310
2311#[allow(unused_variables, clippy::too_many_arguments)]
2314pub fn chunk_attend_win(
2315 q: &[f32],
2316 k: &[&[f32]],
2317 v: &[&[f32]],
2318 b: usize,
2319 s0: usize,
2320 nh: usize,
2321 nkv: usize,
2322 hd: usize,
2323 scale: f32,
2324 window: usize,
2325 out: &mut [f32],
2326) -> bool {
2327 match backend() {
2328 #[cfg(feature = "gpu")]
2329 Backend::Wgpu => {
2330 crate::gpu_wgpu::chunk_attend_win(q, k, v, b, s0, nh, nkv, hd, scale, window, out)
2331 }
2332 #[allow(unreachable_patterns)]
2333 _ => false,
2334 }
2335}
2336
2337#[allow(unused_variables)]
2340pub fn gemm_many_keep(
2341 model: &Arc<CmfModel>,
2342 idxs: &[(usize, usize)],
2343 xs: &[f32],
2344 b: usize,
2345 cols: usize,
2346 out: &mut [f32],
2347) -> bool {
2348 match backend() {
2349 #[cfg(feature = "gpu")]
2350 Backend::Wgpu => crate::gpu_wgpu::gemm_many_keep(model, idxs, xs, b, cols, out),
2351 #[allow(unreachable_patterns)]
2352 _ => false,
2353 }
2354}
2355
2356pub fn chunk_attend_windowed() -> bool {
2358 match backend() {
2359 #[cfg(feature = "gpu")]
2360 Backend::Wgpu => true,
2361 #[allow(unreachable_patterns)]
2362 _ => false,
2363 }
2364}
2365
2366#[allow(unused_variables, clippy::too_many_arguments)]
2370pub fn q4t_qkv(
2371 model: &Arc<CmfModel>,
2372 wq: usize,
2373 wk: usize,
2374 wv: usize,
2375 xs: &[f32],
2376 b: usize,
2377 cols: usize,
2378 rq: usize,
2379 rk: usize,
2380 rv: usize,
2381 out: &mut [f32],
2382) -> bool {
2383 match backend() {
2384 #[cfg(feature = "gpu")]
2385 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
2386 #[allow(unreachable_patterns)]
2387 _ => false,
2388 }
2389}
2390
2391#[allow(unused_variables, clippy::too_many_arguments)]
2393#[allow(clippy::too_many_arguments, unused_variables)]
2397pub fn q4tp_ffn_packed(
2398 model: &Arc<CmfModel>,
2399 w1: usize,
2400 w2: usize,
2401 xs: &[f32],
2402 b: usize,
2403 hidden: usize,
2404 inter: usize,
2405 bias: Option<&[f32]>,
2406 out: &mut [f32],
2407) -> bool {
2408 match backend() {
2409 #[cfg(feature = "gpu")]
2410 Backend::Wgpu => {
2411 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
2412 }
2413 #[allow(unreachable_patterns)]
2414 _ => false,
2415 }
2416}
2417
2418pub fn q4tp_ffn(
2419 model: &Arc<CmfModel>,
2420 w1: usize,
2421 w3: usize,
2422 w2: usize,
2423 xs: &[f32],
2424 b: usize,
2425 hidden: usize,
2426 inter: usize,
2427 out: &mut [f32],
2428) -> bool {
2429 match backend() {
2430 #[cfg(target_os = "macos")]
2431 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2432 #[cfg(feature = "gpu")]
2433 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2434 #[allow(unreachable_patterns)]
2435 _ => false,
2436 }
2437}
2438
2439#[allow(clippy::too_many_arguments, unused_variables)]
2443pub fn q4_ffn_act(
2444 model: &Arc<CmfModel>,
2445 w1: usize,
2446 w3: usize,
2447 w2: usize,
2448 xs: &[f32],
2449 b: usize,
2450 hidden: usize,
2451 inter: usize,
2452 q4tp: bool,
2453 act: GraphAct,
2454 out: &mut [f32],
2455) -> bool {
2456 match act {
2457 GraphAct::Silu if q4tp => q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2458 GraphAct::Silu => q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2459 GraphAct::GeluErf => match backend() {
2460 #[cfg(feature = "gpu")]
2461 Backend::Wgpu => crate::gpu_wgpu::q4_ffn_act(
2462 model,
2463 w1,
2464 w3,
2465 w2,
2466 xs,
2467 b,
2468 hidden,
2469 inter,
2470 q4tp,
2471 act.code(),
2472 out,
2473 ),
2474 #[allow(unreachable_patterns)]
2475 _ => false,
2476 },
2477 }
2478}
2479
2480#[allow(clippy::too_many_arguments, unused_variables)]
2485pub fn ffn_act_keep(
2486 model: &Arc<CmfModel>,
2487 w1: usize,
2488 w3: usize,
2489 w2: usize,
2490 xs: &[f32],
2491 b: usize,
2492 hidden: usize,
2493 inter: usize,
2494 act: GraphAct,
2495 out: &mut [f32],
2496) -> bool {
2497 match backend() {
2498 #[cfg(feature = "gpu")]
2499 Backend::Wgpu => crate::gpu_wgpu::ffn_act_keep(
2500 model,
2501 w1,
2502 w3,
2503 w2,
2504 xs,
2505 b,
2506 hidden,
2507 inter,
2508 act.code(),
2509 out,
2510 ),
2511 #[allow(unreachable_patterns)]
2512 _ => false,
2513 }
2514}
2515
2516#[allow(clippy::too_many_arguments, unused_variables)]
2521pub fn q4tp_gelu_ffn(
2522 model: &Arc<CmfModel>,
2523 w_in: usize,
2524 w_out: usize,
2525 xs: &[f32],
2526 b: usize,
2527 hidden: usize,
2528 inter: usize,
2529 bias_in: &[f32],
2530 bias_out: &[f32],
2531 out: &mut [f32],
2532) -> bool {
2533 match backend() {
2534 #[cfg(feature = "gpu")]
2535 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2536 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2537 ),
2538 #[allow(unreachable_patterns)]
2539 _ => false,
2540 }
2541}
2542
2543pub fn q4t_ffn(
2544 model: &Arc<CmfModel>,
2545 w1: usize,
2546 w3: usize,
2547 w2: usize,
2548 xs: &[f32],
2549 b: usize,
2550 hidden: usize,
2551 inter: usize,
2552 out: &mut [f32],
2553) -> bool {
2554 match backend() {
2555 #[cfg(target_os = "macos")]
2556 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2557 #[cfg(feature = "gpu")]
2558 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2559 #[allow(unreachable_patterns)]
2560 _ => false,
2561 }
2562}
2563
2564pub struct DitBlockArgs<'a> {
2569 pub n: usize,
2570 pub hidden: usize,
2571 pub inter: usize,
2572 pub nh: usize,
2573 pub nkv: usize,
2574 pub hd: usize,
2575 pub eps: f32,
2576 pub rope_cos: &'a [f32],
2577 pub rope_sin: &'a [f32],
2578 pub norm1: &'a [f32],
2579 pub norm2: &'a [f32],
2580 pub ffn_norm1: &'a [f32],
2581 pub ffn_norm2: &'a [f32],
2582 pub norm_q: &'a [f32],
2583 pub norm_k: &'a [f32],
2584 pub s_msa: &'a [f32],
2585 pub gate_msa: &'a [f32],
2586 pub s_mlp: &'a [f32],
2587 pub gate_mlp: &'a [f32],
2588 pub wq: usize,
2589 pub wk: usize,
2590 pub wv: usize,
2591 pub wo: usize,
2592 pub w1: usize,
2593 pub w3: usize,
2594 pub w2: usize,
2595 pub q4tp: bool,
2599 pub resident_in: bool,
2602 pub resident_out: bool,
2606}
2607
2608pub fn dit_chain_supported() -> bool {
2612 #[cfg(feature = "gpu")]
2613 {
2614 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2615 }
2616 #[allow(unreachable_code)]
2617 false
2618}
2619
2620pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2623 #[cfg(feature = "gpu")]
2624 {
2625 if matches!(backend(), Backend::Wgpu) {
2626 return crate::gpu_wgpu::dit_state_fetch(_x);
2627 }
2628 }
2629 false
2630}
2631
2632#[allow(unused_variables)]
2636#[allow(unused_variables, clippy::too_many_arguments)]
2640pub fn dit_qkv(
2641 model: &Arc<CmfModel>,
2642 wq: usize,
2643 wk: usize,
2644 wv: usize,
2645 xs: &[f32],
2646 b: usize,
2647 hidden: usize,
2648 qrows: usize,
2649 kvrows: usize,
2650 q_out: &mut [f32],
2651 k_out: &mut [f32],
2652 v_out: &mut [f32],
2653) -> bool {
2654 match backend() {
2655 #[cfg(feature = "gpu")]
2656 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2657 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2658 ),
2659 #[allow(unreachable_patterns)]
2660 _ => false,
2661 }
2662}
2663
2664pub struct QwenImageAttentionArgs<'a> {
2672 pub image: &'a [f32],
2673 pub text: &'a [f32],
2674 pub image_tokens: usize,
2675 pub text_tokens: usize,
2676 pub heads: usize,
2677 pub head_dim: usize,
2678 pub image_q: usize,
2679 pub image_k: usize,
2680 pub image_v: usize,
2681 pub text_q: usize,
2682 pub text_k: usize,
2683 pub text_v: usize,
2684 pub image_out: usize,
2685 pub text_out: usize,
2686 pub image_q_norm: &'a [f32],
2687 pub image_k_norm: &'a [f32],
2688 pub text_q_norm: &'a [f32],
2689 pub text_k_norm: &'a [f32],
2690 pub image_cos: &'a [f32],
2691 pub image_sin: &'a [f32],
2692 pub text_cos: &'a [f32],
2693 pub text_sin: &'a [f32],
2694 pub image_q_bias: &'a [f32],
2695 pub image_k_bias: &'a [f32],
2696 pub image_v_bias: &'a [f32],
2697 pub text_q_bias: &'a [f32],
2698 pub text_k_bias: &'a [f32],
2699 pub text_v_bias: &'a [f32],
2700 pub image_out_bias: &'a [f32],
2701 pub text_out_bias: &'a [f32],
2702 pub image_proj: &'a mut [f32],
2703 pub text_proj: &'a mut [f32],
2704}
2705
2706#[allow(clippy::too_many_fields)]
2711pub struct QwenImageChainBlock<'a> {
2712 pub image_mod: &'a [f32],
2713 pub text_mod: &'a [f32],
2714 pub image_q: usize,
2715 pub image_k: usize,
2716 pub image_v: usize,
2717 pub text_q: usize,
2718 pub text_k: usize,
2719 pub text_v: usize,
2720 pub image_out: usize,
2721 pub text_out: usize,
2722 pub image_q_norm: &'a [f32],
2723 pub image_k_norm: &'a [f32],
2724 pub text_q_norm: &'a [f32],
2725 pub text_k_norm: &'a [f32],
2726 pub image_q_bias: &'a [f32],
2727 pub image_k_bias: &'a [f32],
2728 pub image_v_bias: &'a [f32],
2729 pub text_q_bias: &'a [f32],
2730 pub text_k_bias: &'a [f32],
2731 pub text_v_bias: &'a [f32],
2732 pub image_out_bias: &'a [f32],
2733 pub text_out_bias: &'a [f32],
2734 pub image_attn_gate: &'a [f32],
2735 pub text_attn_gate: &'a [f32],
2736 pub image_mlp_in: usize,
2737 pub image_mlp_out: usize,
2738 pub text_mlp_in: usize,
2739 pub text_mlp_out: usize,
2740 pub image_mlp_in_bias: &'a [f32],
2741 pub image_mlp_out_bias: &'a [f32],
2742 pub text_mlp_in_bias: &'a [f32],
2743 pub text_mlp_out_bias: &'a [f32],
2744}
2745
2746#[allow(clippy::too_many_fields)]
2752pub struct QwenImageBlockArgs<'a> {
2753 pub image: &'a mut [f32],
2756 pub text: &'a mut [f32],
2757 pub image_norm: &'a [f32],
2758 pub text_norm: &'a [f32],
2759 pub image_tokens: usize,
2760 pub text_tokens: usize,
2761 pub heads: usize,
2762 pub head_dim: usize,
2763 pub image_cos: &'a [f32],
2764 pub image_sin: &'a [f32],
2765 pub text_cos: &'a [f32],
2766 pub text_sin: &'a [f32],
2767 pub image_q: usize,
2768 pub image_k: usize,
2769 pub image_v: usize,
2770 pub text_q: usize,
2771 pub text_k: usize,
2772 pub text_v: usize,
2773 pub image_out: usize,
2774 pub text_out: usize,
2775 pub image_q_norm: &'a [f32],
2776 pub image_k_norm: &'a [f32],
2777 pub text_q_norm: &'a [f32],
2778 pub text_k_norm: &'a [f32],
2779 pub image_q_bias: &'a [f32],
2780 pub image_k_bias: &'a [f32],
2781 pub image_v_bias: &'a [f32],
2782 pub text_q_bias: &'a [f32],
2783 pub text_k_bias: &'a [f32],
2784 pub text_v_bias: &'a [f32],
2785 pub image_out_bias: &'a [f32],
2786 pub text_out_bias: &'a [f32],
2787 pub image_attn_gate: &'a [f32],
2788 pub text_attn_gate: &'a [f32],
2789 pub image_mlp_in: usize,
2790 pub image_mlp_out: usize,
2791 pub text_mlp_in: usize,
2792 pub text_mlp_out: usize,
2793 pub image_mlp_in_bias: &'a [f32],
2794 pub image_mlp_out_bias: &'a [f32],
2795 pub text_mlp_in_bias: &'a [f32],
2796 pub text_mlp_out_bias: &'a [f32],
2797 pub image_mlp_mod: &'a [f32],
2798 pub text_mlp_mod: &'a [f32],
2799 pub image_mlp_gate: &'a [f32],
2800 pub text_mlp_gate: &'a [f32],
2801}
2802
2803pub struct QwenImageChainArgs<'a> {
2808 pub image: &'a mut [f32],
2809 pub text: &'a mut [f32],
2810 pub image_tokens: usize,
2811 pub text_tokens: usize,
2812 pub heads: usize,
2813 pub head_dim: usize,
2814 pub image_cos: &'a [f32],
2815 pub image_sin: &'a [f32],
2816 pub text_cos: &'a [f32],
2817 pub text_sin: &'a [f32],
2818 pub blocks: &'a [QwenImageChainBlock<'a>],
2819}
2820
2821#[allow(unused_variables)]
2822pub fn qwen_image_attention(
2823 model: &Arc<CmfModel>,
2824 args: &mut QwenImageAttentionArgs<'_>,
2825) -> bool {
2826 match backend() {
2827 #[cfg(feature = "gpu")]
2828 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2829 #[allow(unreachable_patterns)]
2830 _ => false,
2831 }
2832}
2833
2834#[allow(unused_variables)]
2835pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2836 match backend() {
2837 #[cfg(feature = "gpu")]
2838 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2839 #[allow(unreachable_patterns)]
2840 _ => false,
2841 }
2842}
2843
2844#[allow(unused_variables)]
2848pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2849 match backend() {
2850 #[cfg(feature = "gpu")]
2851 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2852 #[allow(unreachable_patterns)]
2853 _ => false,
2854 }
2855}
2856
2857#[allow(unused_variables, clippy::too_many_arguments)]
2864pub fn qwen_image_mlp_inplace(
2865 model: &Arc<CmfModel>,
2866 w_in: usize,
2867 w_out: usize,
2868 data: &mut [f32],
2869 batch: usize,
2870 hidden: usize,
2871 inter: usize,
2872 bias_in: &[f32],
2873 bias_out: &[f32],
2874 modulation: &[f32],
2875 gate: &[f32],
2876) -> bool {
2877 match backend() {
2878 #[cfg(feature = "gpu")]
2879 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2880 model,
2881 w_in,
2882 w_out,
2883 data,
2884 batch,
2885 hidden,
2886 inter,
2887 bias_in,
2888 bias_out,
2889 modulation,
2890 gate,
2891 ),
2892 #[allow(unreachable_patterns)]
2893 _ => false,
2894 }
2895}
2896
2897pub fn fused_dit_block_available() -> bool {
2901 #[cfg(target_os = "macos")]
2902 {
2903 matches!(backend(), Backend::Metal) && fused_block_trusted()
2904 }
2905 #[cfg(not(target_os = "macos"))]
2906 {
2907 false
2908 }
2909}
2910
2911pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2912 dit_block_seg(model, a, &[a.n], x)
2913}
2914
2915pub fn dit_block_seg(
2919 model: &Arc<CmfModel>,
2920 a: &DitBlockArgs,
2921 segs: &[usize],
2922 x: &mut [f32],
2923) -> bool {
2924 match backend() {
2925 #[cfg(target_os = "macos")]
2926 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2927 #[cfg(feature = "gpu")]
2934 Backend::Wgpu
2935 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2936 Some("0") => false,
2937 Some(_) => true,
2938 None => crate::gpu_wgpu::discrete_active(),
2939 } =>
2940 {
2941 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2942 }
2943 #[allow(unreachable_patterns)]
2944 _ => false,
2945 }
2946}
2947
2948pub struct VaeResnetArgs<'a> {
2952 pub groups: usize,
2953 pub ic: usize,
2954 pub oc: usize,
2955 pub h: usize,
2956 pub w: usize,
2957 pub n1w: &'a [f32],
2958 pub n1b: &'a [f32],
2959 pub c1w: &'a [f32],
2960 pub c1b: &'a [f32],
2961 pub c1k: usize,
2962 pub n2w: &'a [f32],
2963 pub n2b: &'a [f32],
2964 pub c2w: &'a [f32],
2965 pub c2b: &'a [f32],
2966 pub c2k: usize,
2967 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2968}
2969
2970#[allow(unused_variables)]
2973pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2974 match backend() {
2975 #[cfg(target_os = "macos")]
2976 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2977 _ => false,
2978 }
2979}
2980
2981#[allow(unused_variables, clippy::too_many_arguments)]
2984pub fn vae_upsample_conv(
2985 w: &[f32],
2986 bias: &[f32],
2987 x: &[f32],
2988 ic: usize,
2989 oc: usize,
2990 h: usize,
2991 w_img: usize,
2992 k: usize,
2993 out: &mut [f32],
2994) -> bool {
2995 match backend() {
2996 #[cfg(target_os = "macos")]
2997 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2998 #[cfg(feature = "gpu")]
2999 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3000 #[allow(unreachable_patterns)]
3001 _ => false,
3002 }
3003}
3004
3005#[allow(unused_variables, clippy::too_many_arguments)]
3008pub fn vae_conv2d(
3009 w: &[f32],
3010 bias: &[f32],
3011 x: &[f32],
3012 ic: usize,
3013 oc: usize,
3014 h: usize,
3015 w_img: usize,
3016 k: usize,
3017 out: &mut [f32],
3018) -> bool {
3019 match backend() {
3020 #[cfg(target_os = "macos")]
3021 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3022 #[cfg(feature = "gpu")]
3023 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3024 #[allow(unreachable_patterns)]
3025 _ => false,
3026 }
3027}
3028
3029#[allow(unused_variables, clippy::too_many_arguments)]
3033#[allow(unused_variables)]
3037#[allow(clippy::too_many_arguments)]
3038#[allow(clippy::too_many_arguments, unused_variables)]
3041pub fn dit_qkv_attention(
3042 model: &Arc<CmfModel>,
3043 qkv_idx: usize,
3044 xn: &[f32],
3045 n: usize,
3046 hidden: usize,
3047 nh: usize,
3048 hd: usize,
3049 scale: f32,
3050 nr: (&[f32], &[f32], &[f32], f32),
3051 out: &mut [f32],
3052) -> bool {
3053 match backend() {
3054 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3055 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
3056 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
3057 ),
3058 #[allow(unreachable_patterns)]
3059 _ => false,
3060 }
3061}
3062
3063#[allow(clippy::too_many_arguments)]
3066pub fn dit_qkv_attn_out(
3067 model: &Arc<CmfModel>,
3068 qkv_idx: usize,
3069 out_idx: usize,
3070 xn: &[f32],
3071 n: usize,
3072 hidden: usize,
3073 nh: usize,
3074 hd: usize,
3075 scale: f32,
3076 nr: (&[f32], &[f32], &[f32], f32),
3077 proj: &mut [f32],
3078) -> bool {
3079 match backend() {
3080 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3081 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
3082 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
3083 ),
3084 #[allow(unreachable_patterns)]
3085 _ => false,
3086 }
3087}
3088
3089#[allow(clippy::too_many_arguments)]
3091pub fn vae_qkv_attn_out(
3092 model: &Arc<CmfModel>,
3093 qkv_idx: usize,
3094 out_idx: usize,
3095 xn: &[f32],
3096 n: usize,
3097 dim: usize,
3098 nh: usize,
3099 hd: usize,
3100 scale: f32,
3101 angles: &[f32],
3102 eps: f32,
3103 qkv_bias: &[f32],
3104 proj: &mut [f32],
3105) -> bool {
3106 match backend() {
3107 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3108 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
3109 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
3110 ),
3111 #[allow(unreachable_patterns)]
3112 _ => false,
3113 }
3114}
3115
3116#[allow(clippy::too_many_arguments)]
3117pub fn vae_attention_packed(
3118 qkv: &[f32],
3119 nh: usize,
3120 n: usize,
3121 hd: usize,
3122 scale: f32,
3123 angles: &[f32],
3124 eps: f32,
3125 out: &mut [f32],
3126) -> bool {
3127 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
3128}
3129
3130#[allow(clippy::too_many_arguments)]
3131pub fn vae_attention_packed_layout(
3132 qkv: &[f32],
3133 nh: usize,
3134 n: usize,
3135 hd: usize,
3136 scale: f32,
3137 angles: &[f32],
3138 eps: f32,
3139 out: &mut [f32],
3140 layout: u32,
3141) -> bool {
3142 match backend() {
3143 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3144 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
3145 qkv, nh, n, hd, scale, angles, eps, out, layout,
3146 ),
3147 #[allow(unreachable_patterns)]
3148 _ => false,
3149 }
3150}
3151
3152#[allow(clippy::too_many_arguments)]
3153pub fn dit_split_only(
3154 qkv: &[f32],
3155 nh: usize,
3156 n: usize,
3157 hd: usize,
3158 layout: u32,
3159 norm: Option<(&[f32], f32)>,
3160 out_q: &mut [f32],
3161) -> bool {
3162 match backend() {
3163 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3164 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
3165 #[allow(unreachable_patterns)]
3166 _ => false,
3167 }
3168}
3169
3170pub fn gemm_nt_f32_transient(
3178 x: &[f32],
3179 w: &[f32],
3180 y: &mut [f32],
3181 n: usize,
3182 k: usize,
3183 m: usize,
3184) -> bool {
3185 match backend() {
3186 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3187 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
3188 #[allow(unreachable_patterns)]
3189 _ => false,
3190 }
3191}
3192
3193pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
3194 match backend() {
3195 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3196 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
3197 #[allow(unreachable_patterns)]
3198 _ => false,
3199 }
3200}
3201
3202#[allow(clippy::too_many_arguments)]
3205pub fn music3_ffn(
3206 model: &std::sync::Arc<CmfModel>,
3207 idx_in: usize,
3208 idx_out: usize,
3209 h: &[f32],
3210 bias_in: &[f32],
3211 n: usize,
3212 hs: usize,
3213 inter: usize,
3214 out: &mut [f32],
3215) -> bool {
3216 match backend() {
3217 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3218 Backend::Wgpu => {
3219 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
3220 }
3221 #[allow(unreachable_patterns)]
3222 _ => false,
3223 }
3224}
3225
3226#[allow(clippy::too_many_arguments)]
3230pub fn conv1d_gemm(
3231 x: &[f32],
3232 w: &[f32],
3233 ic: usize,
3234 oc: usize,
3235 n: usize,
3236 k: usize,
3237 pad: usize,
3238 dil: usize,
3239 out_n: usize,
3240 yt: &mut [f32],
3241) -> bool {
3242 match backend() {
3243 #[cfg(target_os = "macos")]
3244 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3245 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3246 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3247 #[allow(unreachable_patterns)]
3248 _ => false,
3249 }
3250}
3251
3252#[allow(clippy::too_many_arguments)]
3254pub fn vae_conv2d_coop(
3255 w: &[f32],
3256 bias: Option<&[f32]>,
3257 x: &[f32],
3258 ic: usize,
3259 oc: usize,
3260 h: usize,
3261 wi: usize,
3262 k: usize,
3263 out: &mut [f32],
3264) -> bool {
3265 match backend() {
3266 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3267 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
3268 #[allow(unreachable_patterns)]
3269 _ => false,
3270 }
3271}
3272
3273pub fn dit_attention_packed(
3274 qkv: &[f32],
3275 nh: usize,
3276 n: usize,
3277 hd: usize,
3278 scale: f32,
3279 nr: Option<(&[f32], &[f32], &[f32], f32)>,
3282 out: &mut [f32],
3283) -> bool {
3284 match backend() {
3285 #[cfg(feature = "gpu")]
3292 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
3293 #[allow(unreachable_patterns)]
3294 _ => false,
3295 }
3296}
3297
3298pub fn dit_attention_packed_available() -> bool {
3306 #[allow(unreachable_patterns)]
3307 match backend() {
3308 #[cfg(feature = "gpu")]
3309 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
3310 _ => false,
3311 }
3312}
3313
3314pub fn dit_attention(
3315 qh: &[f32],
3316 kh: &[f32],
3317 vh: &[f32],
3318 nh: usize,
3319 nkv: usize,
3320 n: usize,
3321 hd: usize,
3322 scale: f32,
3323 out: &mut [f32],
3324) -> bool {
3325 match backend() {
3326 #[cfg(target_os = "macos")]
3327 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3328 #[cfg(feature = "gpu")]
3329 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3330 #[allow(unreachable_patterns)]
3331 _ => false,
3332 }
3333}
3334
3335#[allow(unused_variables)]
3340pub fn q4tp_matmat(
3341 model: &Arc<CmfModel>,
3342 idx: usize,
3343 xs: &[f32],
3344 b: usize,
3345 rows: usize,
3346 cols: usize,
3347 out: &mut [f32],
3348) -> bool {
3349 match backend() {
3350 #[cfg(target_os = "macos")]
3351 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3352 #[cfg(feature = "gpu")]
3353 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3354 #[allow(unreachable_patterns)]
3355 _ => false,
3356 }
3357}
3358
3359pub fn q2tp_matmat(
3362 model: &Arc<CmfModel>,
3363 idx: usize,
3364 xs: &[f32],
3365 b: usize,
3366 rows: usize,
3367 cols: usize,
3368 out: &mut [f32],
3369) -> bool {
3370 match backend() {
3371 #[cfg(target_os = "macos")]
3372 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3373 #[cfg(feature = "gpu")]
3374 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3375 #[allow(unreachable_patterns)]
3376 _ => false,
3377 }
3378}
3379
3380pub fn q2tp_affine_matmat(
3383 model: &Arc<CmfModel>,
3384 idx: usize,
3385 xs: &[f32],
3386 b: usize,
3387 rows: usize,
3388 cols: usize,
3389 out: &mut [f32],
3390) -> bool {
3391 match backend() {
3392 #[cfg(target_os = "macos")]
3393 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3394 #[cfg(feature = "gpu")]
3395 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3396 #[allow(unreachable_patterns)]
3397 _ => false,
3398 }
3399}
3400
3401pub fn q2tp_matvec(
3403 model: &Arc<CmfModel>,
3404 idx: usize,
3405 xs: &[f32],
3406 rows: usize,
3407 cols: usize,
3408 out: &mut [f32],
3409) -> bool {
3410 match backend() {
3411 #[cfg(target_os = "macos")]
3412 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
3413 #[cfg(feature = "gpu")]
3414 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
3415 #[allow(unreachable_patterns)]
3416 _ => false,
3417 }
3418}
3419
3420pub fn q2tp_affine_matvec(
3424 model: &Arc<CmfModel>,
3425 idx: usize,
3426 xs: &[f32],
3427 rows: usize,
3428 cols: usize,
3429 out: &mut [f32],
3430) -> bool {
3431 match backend() {
3432 #[cfg(target_os = "macos")]
3433 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3434 #[cfg(feature = "gpu")]
3435 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3436 #[allow(unreachable_patterns)]
3437 _ => false,
3438 }
3439}
3440
3441pub fn q4tp_matvec(
3446 model: &Arc<CmfModel>,
3447 idx: usize,
3448 xs: &[f32],
3449 rows: usize,
3450 cols: usize,
3451 out: &mut [f32],
3452) -> bool {
3453 match backend() {
3454 #[cfg(target_os = "macos")]
3455 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
3456 #[cfg(feature = "gpu")]
3457 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
3458 #[allow(unreachable_patterns)]
3459 _ => false,
3460 }
3461}
3462
3463pub fn q4t_matvec(
3469 model: &Arc<CmfModel>,
3470 idx: usize,
3471 xs: &[f32],
3472 rows: usize,
3473 cols: usize,
3474 out: &mut [f32],
3475) -> bool {
3476 match backend() {
3477 #[cfg(target_os = "macos")]
3478 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
3479 #[allow(unreachable_patterns)]
3480 _ => false,
3481 }
3482}
3483
3484pub fn q4t_matmat(
3485 model: &Arc<CmfModel>,
3486 idx: usize,
3487 xs: &[f32],
3488 b: usize,
3489 rows: usize,
3490 cols: usize,
3491 out: &mut [f32],
3492) -> bool {
3493 match backend() {
3494 #[cfg(target_os = "macos")]
3495 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
3496 #[cfg(feature = "gpu")]
3497 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
3498 #[allow(unreachable_patterns)]
3499 _ => false,
3500 }
3501}
3502
3503#[cfg(target_os = "macos")]
3505pub use crate::gpu_metal::{
3506 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
3507 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
3508};
3509
3510#[cfg(target_os = "macos")]
3512pub fn gdn_block(
3513 model: &Arc<CmfModel>,
3514 layers: &[GdnGpuLayer],
3515 states: &mut [&mut [f32]],
3516 cfg: &GdnGpuCfg,
3517 h: &mut [f32],
3518) -> bool {
3519 match backend() {
3520 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
3521 _ => false,
3522 }
3523}
3524
3525#[allow(unused_variables)]
3527pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
3528 match backend() {
3529 #[cfg(target_os = "macos")]
3530 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
3531 #[cfg(feature = "gpu")]
3532 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
3533 Backend::None => false,
3534 }
3535}
3536
3537#[allow(unused_variables)]
3539pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
3540 match backend() {
3541 #[cfg(target_os = "macos")]
3542 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
3543 #[cfg(feature = "gpu")]
3544 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
3545 Backend::None => false,
3546 }
3547}
3548
3549static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
3565static GRAPH_RACE_ARM_GRAPH: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_TOK: AtomicU32 = AtomicU32::new(0); static GRAPH_NS: [AtomicU64; 2] = [AtomicU64::new(0), AtomicU64::new(0)]; static GRAPH_N: [AtomicU32; 2] = [AtomicU32::new(0), AtomicU32::new(0)];
3569
3570const GRAPH_RACE_SAMPLES: u32 = 4;
3572
3573pub fn graph_race_begin_generation() {
3592 #[cfg(feature = "gpu")]
3597 {
3598 static FLUSHED: std::sync::Once = std::sync::Once::new();
3610 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3611 if FIRST.swap(false, Ordering::Relaxed) {
3612 } else {
3614 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3615 }
3616 }
3617 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3618 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3619 return;
3620 }
3621 let (gn, cn) = (
3622 GRAPH_N[1].load(Ordering::Relaxed),
3623 GRAPH_N[0].load(Ordering::Relaxed),
3624 );
3625 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3626 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3627 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3628 let verdict = if g_avg < c_avg { 1 } else { 2 };
3629 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3630 tracing::info!(
3631 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3632 g_avg as f64 / 1e6,
3633 c_avg as f64 / 1e6,
3634 if verdict == 1 { "graph" } else { "normal path" }
3635 );
3636 return;
3637 }
3638 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3639 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3640}
3641
3642pub fn graph_race_use_graph(trusted: bool) -> bool {
3646 if trusted {
3647 return true;
3648 }
3649 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3650 1 => true,
3651 2 => false,
3652 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3653 }
3654}
3655
3656pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3661 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3662 return false;
3663 }
3664 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3665 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3666 if !first || cn == 0 {
3667 return false;
3668 }
3669 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3670 let ns = dur.as_nanos() as u64;
3671 if ns > 1_000_000_000 && ns > 4 * c_avg {
3672 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3673 tracing::info!(
3674 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3675 ns as f64 / 1e6,
3676 c_avg as f64 / 1e6
3677 );
3678 return true;
3679 }
3680 false
3681}
3682
3683pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3687 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3688 return;
3689 }
3690 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3691 if tok == 0 {
3692 return;
3693 }
3694 let i = used_graph as usize;
3695 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3696 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3697}
3698
3699pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3709 #[inline]
3710 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3711 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3712 for c in chunks.chunks_exact(8) {
3713 h ^= u64::from_le_bytes(c.try_into().unwrap());
3714 h = h.wrapping_mul(0x100_0000_01b3);
3715 }
3716 for &b in tail {
3717 h ^= b as u64;
3718 h = h.wrapping_mul(0x100_0000_01b3);
3719 }
3720 h
3721 }
3722 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3723 if data.len() <= 4096 {
3724 return fnv(h, data);
3725 }
3726 let step = (data.len() - 64) / 63;
3727 for i in 0..64 {
3728 h = fnv(h, &data[i * step..i * step + 64]);
3729 }
3730 h
3731}
3732
3733pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3736 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3737 fp_bytes(bytes)
3738}
3739
3740#[cfg(test)]
3741mod fp_tests {
3742 use super::fp_bytes;
3743
3744 #[test]
3749 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3750 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3752 let h0 = fp_bytes(&base);
3753 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3754 let mut dense = base.clone();
3757 for b in dense.iter_mut() {
3758 *b = b.wrapping_add(1);
3759 }
3760 assert_ne!(
3761 h0,
3762 fp_bytes(&dense),
3763 "a fully different tensor slipped through"
3764 );
3765 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3768 let mut small = vec![3u8; 4096];
3771 let hs = fp_bytes(&small);
3772 small[2048] ^= 1;
3773 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3774 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3776 let v = vec![9u8; n];
3777 let _ = fp_bytes(&v); }
3779 }
3780}
3781
3782pub fn bake_release() {
3786 #[cfg(feature = "gpu")]
3787 crate::gpu_wgpu::bake_release();
3788}
3789
3790pub fn bake_precision_strict(on: bool) {
3794 #[cfg(feature = "gpu")]
3795 crate::gpu_wgpu::bake_precision_strict(on);
3796 #[cfg(not(feature = "gpu"))]
3797 let _ = on;
3798}
3799
3800pub fn hostprof_encode_done(t0: std::time::Instant) {
3806 use std::sync::atomic::{AtomicU64, Ordering};
3807 static ENC: AtomicU64 = AtomicU64::new(0);
3808 static N: AtomicU64 = AtomicU64::new(0);
3809 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3810 return;
3811 }
3812 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3813 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3814 if n % 100 == 0 {
3815 eprintln!(
3816 "hostprof: encode {:.2} ms/token over {n} tokens",
3817 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3818 );
3819 }
3820}
3821
3822pub fn hostprof_total(t0: std::time::Instant) {
3823 use std::sync::atomic::{AtomicU64, Ordering};
3824 static TOT: AtomicU64 = AtomicU64::new(0);
3825 static N: AtomicU64 = AtomicU64::new(0);
3826 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3827 return;
3828 }
3829 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3830 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3831 if n % 100 == 0 {
3832 eprintln!(
3833 "hostprof: total {:.2} ms/token over {n} tokens",
3834 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3835 );
3836 }
3837}
3838
3839pub fn stageprof(stage: u32, dt: std::time::Duration) {
3843 use std::sync::atomic::{AtomicU64, Ordering};
3844 static NS: [AtomicU64; 4] = [
3845 AtomicU64::new(0),
3846 AtomicU64::new(0),
3847 AtomicU64::new(0),
3848 AtomicU64::new(0),
3849 ];
3850 static N: AtomicU64 = AtomicU64::new(0);
3851 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3852 return;
3853 }
3854 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3855 if stage == 1 {
3856 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3857 if n % 200 == 0 {
3858 eprintln!(
3859 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3860 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3861 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3862 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3863 );
3864 }
3865 }
3866}
3867
3868pub fn weight_bytes_dispatched() -> u64 {
3871 let mut total = 0u64;
3872 #[cfg(target_os = "macos")]
3873 {
3874 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3875 }
3876 #[cfg(feature = "gpu")]
3877 {
3878 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3879 }
3880 total
3881}
3882
3883pub fn weight_bytes_by() -> [u64; 6] {
3886 #[cfg(target_os = "macos")]
3887 {
3888 let mut o = [0u64; 6];
3889 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3890 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3891 }
3892 return o;
3893 }
3894 #[allow(unreachable_code)]
3895 [0; 6]
3896}
3897
3898#[cfg(test)]
3899mod probe_warmup_tests {
3900 use super::*;
3901 use std::time::Duration;
3902
3903 fn ms(v: f64) -> Duration {
3904 Duration::from_nanos((v * 1e6) as u64)
3905 }
3906
3907 #[test]
3912 fn one_cold_first_sample_does_not_lose_the_class() {
3913 let p = Probe::new();
3914 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3916 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3917 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3918 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3919 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3920 assert_eq!(
3921 p.state.load(Ordering::Relaxed),
3922 1,
3923 "the device is 3x faster once warm and must win"
3924 );
3925 }
3926
3927 #[test]
3931 fn the_warmup_is_spent_once_and_never_underflows() {
3932 let p = Probe::new();
3933 for _ in 0..8 {
3934 probe_record_into(&p, "matmat", None, true, ms(10.0));
3935 }
3936 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3937 assert_eq!(
3938 p.gpu_n.load(Ordering::Relaxed),
3939 7,
3940 "one sample burned, the rest counted"
3941 );
3942 }
3943
3944 #[test]
3950 fn a_class_whose_device_always_declines_settles_on_the_host() {
3951 let _probe_guard = probe_test_guard();
3952 let c = OpClass::MatmatWide;
3956 let p = &PROBES[c as usize];
3957 p.state.store(0, Ordering::Relaxed);
3958 p.declines.store(0, Ordering::Relaxed);
3959 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3960 probe_note_decline(c);
3961 }
3962 assert_eq!(
3963 p.state.load(Ordering::Relaxed),
3964 0,
3965 "one short of the limit is still a question, not an answer"
3966 );
3967 probe_note_decline(c);
3968 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3969 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3970 p.state.store(0, Ordering::Relaxed);
3971 p.declines.store(0, Ordering::Relaxed);
3972 }
3973
3974 #[test]
3977 fn a_slow_device_still_loses_after_the_warmup() {
3978 let p = Probe::new();
3979 for _ in 0..4 {
3980 probe_record_into(&p, "matvec", None, true, ms(40.0));
3981 }
3982 for _ in 0..4 {
3983 probe_record_into(&p, "matvec", None, false, ms(2.0));
3984 }
3985 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3986 }
3987}
3988
3989pub(crate) struct ImageStageGuard {
3992 #[cfg(target_os = "macos")]
3993 metal: Option<crate::gpu_metal::ImageStageGuard>,
3994 #[cfg(feature = "gpu")]
3995 wgpu: crate::gpu_wgpu::ImageStageGuard,
3996}
3997
3998pub(crate) fn image_stage_scope() -> ImageStageGuard {
3999 ImageStageGuard {
4000 #[cfg(target_os = "macos")]
4001 metal: if matches!(backend(), Backend::Metal) {
4002 Some(crate::gpu_metal::image_stage_scope())
4003 } else {
4004 None
4005 },
4006 #[cfg(feature = "gpu")]
4007 wgpu: crate::gpu_wgpu::image_stage_scope(),
4008 }
4009}
4010
4011impl ImageStageGuard {
4012 pub(crate) fn track_model(&mut self, uid: u64) {
4013 #[cfg(target_os = "macos")]
4014 if let Some(metal) = &mut self.metal {
4015 metal.track_model(uid);
4016 }
4017 #[cfg(feature = "gpu")]
4018 self.wgpu.track_model(uid);
4019 #[cfg(not(target_os = "macos"))]
4020 let _ = uid;
4021 }
4022}
4023
4024#[derive(Clone, Copy, Debug, PartialEq)]
4044pub struct Qi21Geom {
4045 pub hidden: usize,
4046 pub nh: usize,
4047 pub hd: usize,
4048 pub inter: usize,
4049 pub in_ch: usize,
4050 pub eps: f32,
4051}
4052
4053pub struct Qi21BlockRef<'a> {
4056 pub w: [usize; 7],
4057 pub norm_q: &'a [f32],
4058 pub norm_k: &'a [f32],
4059}
4060
4061pub struct Qi21PrefillArgs<'a> {
4063 pub model: &'a Arc<CmfModel>,
4064 pub geom: Qi21Geom,
4065 pub blocks: &'a [Qi21BlockRef<'a>],
4066 pub img_in: &'a [f32],
4068 pub proj_out: &'a [f32],
4069 pub key: u64,
4070 pub x: &'a [f32],
4072 pub lp: usize,
4073 pub rope_p: (&'a [f32], &'a [f32]),
4075 pub rope_t: (&'a [f32], &'a [f32]),
4076 pub vis: &'a [u32],
4078 pub mods0: &'a [f32],
4080 pub n: usize,
4082}
4083
4084#[allow(unused_variables)]
4086pub fn qi21_prefill(a: &Qi21PrefillArgs) -> bool {
4087 match backend() {
4088 #[cfg(target_os = "macos")]
4089 Backend::Metal => crate::gpu_metal::qi21::prefill(a),
4090 #[cfg(feature = "gpu")]
4091 Backend::Wgpu => crate::gpu_wgpu::qi21::prefill(a),
4092 #[allow(unreachable_patterns)]
4093 _ => false,
4094 }
4095}
4096
4097#[allow(unused_variables)]
4099pub fn qi21_step(key: u64, xtok: &[f32], mods: &[f32], fs: &[f32], out: &mut [f32]) -> bool {
4100 match backend() {
4101 #[cfg(target_os = "macos")]
4102 Backend::Metal => crate::gpu_metal::qi21::step(key, xtok, mods, fs, out),
4103 #[cfg(feature = "gpu")]
4104 Backend::Wgpu => crate::gpu_wgpu::qi21::step(key, xtok, mods, fs, out),
4105 #[allow(unreachable_patterns)]
4106 _ => false,
4107 }
4108}
4109
4110#[allow(unused_variables)]
4113pub fn qi21_release_key(key: u64) {
4114 #[cfg(target_os = "macos")]
4115 if matches!(backend(), Backend::Metal) {
4116 crate::gpu_metal::qi21::release_key(key);
4117 }
4118 #[cfg(feature = "gpu")]
4119 crate::gpu_wgpu::qi21::release_key(key);
4120}
4121
4122pub fn qi21_release() {
4125 #[cfg(target_os = "macos")]
4126 if matches!(backend(), Backend::Metal) {
4127 crate::gpu_metal::qi21::release();
4128 }
4129 #[cfg(feature = "gpu")]
4130 {
4131 crate::gpu_wgpu::qi21::release();
4132 crate::gpu_wgpu::qi21_vae::release();
4133 }
4134}
4135
4136#[derive(Clone, Copy)]
4139pub struct Qi21VaeConvRef<'a> {
4140 pub w: &'a [f32],
4141 pub b: &'a [f32],
4142 pub ci: usize,
4143 pub co: usize,
4144 pub k: usize,
4145}
4146
4147pub struct Qi21VaeResRef<'a> {
4150 pub g1: &'a [f32],
4151 pub c1: Qi21VaeConvRef<'a>,
4152 pub g2: &'a [f32],
4153 pub c2: Qi21VaeConvRef<'a>,
4154 pub shortcut: Option<Qi21VaeConvRef<'a>>,
4155}
4156
4157pub struct Qi21VaeUpRef<'a> {
4161 pub resnets: Vec<Qi21VaeResRef<'a>>,
4162 pub up: Option<(Qi21VaeConvRef<'a>, usize)>,
4163 pub in_dim: usize,
4164 pub out_dim: usize,
4165}
4166
4167pub struct Qi21VaeDecodeArgs<'a> {
4170 pub key: u64,
4172 pub post_quant: Qi21VaeConvRef<'a>,
4173 pub conv_in: Qi21VaeConvRef<'a>,
4174 pub mid_res: [Qi21VaeResRef<'a>; 2],
4175 pub attn_gamma: &'a [f32],
4178 pub attn_qkv: Qi21VaeConvRef<'a>,
4179 pub attn_proj: Qi21VaeConvRef<'a>,
4180 pub ups: Vec<Qi21VaeUpRef<'a>>,
4181 pub norm_out: &'a [f32],
4182 pub conv_out: Qi21VaeConvRef<'a>,
4183}
4184
4185#[allow(unused_variables)]
4189pub fn qi21_vae_decode(a: &Qi21VaeDecodeArgs, z: &[f32], h: usize, w: usize, out: &mut [f32]) -> bool {
4190 match backend() {
4191 #[cfg(feature = "gpu")]
4192 Backend::Wgpu => crate::gpu_wgpu::qi21_vae::decode(a, z, h, w, out),
4193 #[allow(unreachable_patterns)]
4194 _ => false,
4195 }
4196}
4197
4198#[derive(Clone, Copy)]
4206pub struct ZBlockRef<'a> {
4207 pub wq: usize,
4209 pub wk: usize,
4210 pub wv: usize,
4211 pub wo: usize,
4212 pub w1: usize,
4215 pub w3: usize,
4216 pub w2: usize,
4217 pub norm1: &'a [f32],
4219 pub norm2: &'a [f32],
4220 pub ffn_norm1: &'a [f32],
4222 pub ffn_norm2: &'a [f32],
4223 pub norm_q: &'a [f32],
4225 pub norm_k: &'a [f32],
4226}
4227
4228#[derive(Clone, Copy, Debug, PartialEq)]
4232pub struct ZGeom {
4233 pub hidden: usize,
4234 pub nh: usize,
4235 pub hd: usize,
4236 pub inter: usize,
4237 pub eps: f32,
4238 pub final_eps: f32,
4239 pub patch_dim: usize,
4240}
4241
4242pub struct ZPrepareArgs<'a> {
4246 pub model: &'a Arc<CmfModel>,
4247 pub geom: ZGeom,
4248 pub key: u64,
4251 pub n_img: usize,
4254 pub n_img_p: usize,
4255 pub n_cap_p: usize,
4256 pub grid: (usize, usize),
4259 pub cap: &'a [f32],
4261 pub rope_img: (&'a [f32], &'a [f32]),
4264 pub rope_joint: (&'a [f32], &'a [f32]),
4266 pub x_emb_w: &'a [f32],
4269 pub x_emb_b: &'a [f32],
4270 pub x_pad: &'a [f32],
4271 pub final_w: &'a [f32],
4273 pub final_b: &'a [f32],
4274 pub noise_refiner: &'a [ZBlockRef<'a>],
4276 pub layers: &'a [ZBlockRef<'a>],
4277 pub mods_all: Option<&'a [f32]>,
4283 pub final_scale_all: Option<&'a [f32]>,
4284 pub neg: Option<ZNegArgs<'a>>,
4290}
4291
4292pub struct ZNegArgs<'a> {
4296 pub cap: &'a [f32],
4298 pub n_cap_p: usize,
4299 pub rope_img: (&'a [f32], &'a [f32]),
4301 pub rope_joint: (&'a [f32], &'a [f32]),
4303}
4304
4305pub struct ZStepArgs<'a> {
4307 pub key: u64,
4310 pub step: usize,
4313 pub x_tok: &'a [f32],
4317 pub mods: &'a [f32],
4321 pub final_scale: &'a [f32],
4323 pub out: &'a mut [f32],
4326 pub out_neg: Option<&'a mut [f32]>,
4329}
4330
4331#[allow(unused_variables)]
4335pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
4336 match backend() {
4337 #[cfg(target_os = "macos")]
4338 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
4339 #[cfg(feature = "gpu")]
4340 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
4341 #[allow(unreachable_patterns)]
4342 _ => false,
4343 }
4344}
4345
4346#[allow(unused_variables)]
4350pub fn zimage_step(a: &mut ZStepArgs) -> bool {
4351 match backend() {
4352 #[cfg(target_os = "macos")]
4353 Backend::Metal => crate::gpu_metal::zimage::step(a),
4354 #[cfg(feature = "gpu")]
4355 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
4356 #[allow(unreachable_patterns)]
4357 _ => false,
4358 }
4359}
4360
4361#[allow(unused_variables)]
4366pub fn zimage_preload(
4367 model: &Arc<CmfModel>,
4368 geom: &ZGeom,
4369 noise_refiner: &[ZBlockRef],
4370 layers: &[ZBlockRef],
4371 context_refiner: &[ZBlockRef],
4372) -> bool {
4373 match backend() {
4374 #[cfg(target_os = "macos")]
4375 Backend::Metal => {
4376 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
4377 }
4378 #[cfg(feature = "gpu")]
4379 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
4380 #[allow(unreachable_patterns)]
4381 _ => false,
4382 }
4383}
4384
4385pub fn zimage_flush_pipelines() {
4389 #[cfg(feature = "gpu")]
4390 if matches!(backend(), Backend::Wgpu) {
4391 crate::gpu_wgpu::pipeline_cache_flush();
4392 }
4393}
4394
4395pub fn zimage_warmup() -> bool {
4400 match backend() {
4401 #[cfg(target_os = "macos")]
4402 Backend::Metal => crate::gpu_metal::zimage::warmup(),
4403 #[cfg(feature = "gpu")]
4404 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
4405 #[allow(unreachable_patterns)]
4406 _ => false,
4407 }
4408}
4409
4410#[allow(unused_variables)]
4414pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
4415 match backend() {
4416 #[cfg(target_os = "macos")]
4417 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
4418 #[cfg(feature = "gpu")]
4419 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
4420 #[allow(unreachable_patterns)]
4421 _ => false,
4422 }
4423}
4424
4425pub fn zimage_release_dit() {
4428 #[cfg(target_os = "macos")]
4429 crate::gpu_metal::zimage::release_dit();
4430 #[cfg(feature = "gpu")]
4431 crate::gpu_wgpu::zimage::release_dit();
4432}
4433
4434pub fn zimage_release() {
4439 #[cfg(target_os = "macos")]
4440 crate::gpu_metal::zimage::release();
4441 #[cfg(feature = "gpu")]
4442 crate::gpu_wgpu::zimage::release();
4443}
4444
4445#[allow(unused_variables)]
4451pub fn zimage_refine_caption(
4452 model: &Arc<CmfModel>,
4453 geom: &ZGeom,
4454 blocks: &[ZBlockRef],
4455 rope_cap: (&[f32], &[f32]),
4456 cap: &mut [f32],
4457) -> bool {
4458 match backend() {
4459 #[cfg(target_os = "macos")]
4460 Backend::Metal => {
4461 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4462 }
4463 #[cfg(feature = "gpu")]
4464 Backend::Wgpu => {
4465 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4466 }
4467 #[allow(unreachable_patterns)]
4468 _ => false,
4469 }
4470}
4471
4472#[allow(unused_variables)]
4478pub fn vae_decode_chain(
4479 a: &crate::vae::VaeChainArgs,
4480 z: &[f32],
4481 h: usize,
4482 w: usize,
4483 out: &mut [f32],
4484) -> bool {
4485 match backend() {
4486 #[cfg(target_os = "macos")]
4487 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
4488 #[cfg(feature = "gpu")]
4489 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
4490 #[allow(unreachable_patterns)]
4491 _ => false,
4492 }
4493}