1use cortiq_core::CmfModel;
14use std::cell::Cell;
15use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock};
17
18thread_local! {
19 static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
23 static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
28 static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
32}
33
34pub struct CpuScopeGuard(bool);
40
41impl Drop for CpuScopeGuard {
42 fn drop(&mut self) {
43 CPU_ONLY.with(|c| c.set(self.0));
44 }
45}
46
47pub fn enter_cpu_scope() -> CpuScopeGuard {
48 let previous = CPU_ONLY.with(|c| c.replace(true));
49 CpuScopeGuard(previous)
50}
51
52pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
54 let _restore = enter_cpu_scope();
55 f()
56}
57
58pub(crate) fn inherit_cpu_scope() -> impl Fn() -> Option<CpuScopeGuard> + Copy {
63 let on = CPU_ONLY.get();
64 move || on.then(enter_cpu_scope)
65}
66
67pub fn probe_set_device(label: &str) {
72 let _ = DEVICE_LABEL.set(label.to_string());
73}
74
75fn device_label() -> &'static str {
76 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
77}
78
79static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
80
81static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
90
91pub fn set_cache_dir(dir: std::path::PathBuf) {
93 let _ = CACHE_DIR.set(dir);
94}
95
96pub fn cache_dir_pub() -> std::path::PathBuf {
98 cache_dir()
99}
100
101fn cache_dir() -> std::path::PathBuf {
102 if let Some(d) = CACHE_DIR.get() {
103 return d.clone();
104 }
105 match std::env::var_os("TMPDIR") {
106 Some(t) => std::path::PathBuf::from(t),
107 None => std::env::temp_dir(),
108 }
109}
110
111fn probe_cache_path() -> Option<std::path::PathBuf> {
114 match std::env::var("CMF_PROBE_CACHE") {
115 Ok(v) if v == "0" => None,
116 Ok(v) => Some(std::path::PathBuf::from(v)),
117 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
118 }
119}
120
121fn probe_cache_key_named(class: &str) -> String {
125 format!(
126 "{}\t{}\t{}",
127 env!("CARGO_PKG_VERSION"),
128 device_label(),
129 class
130 )
131}
132
133const CLASS_NAMES: [&str; 7] = [
134 "ffn",
135 "matvec",
136 "matmat",
137 "qkv-batch",
138 "matmat-wide",
139 "lm-head",
140 "gemm-nt",
141];
142
143fn probe_cache_load() {
152 static ONCE: std::sync::Once = std::sync::Once::new();
153 ONCE.call_once(|| {
154 let Some(path) = probe_cache_path() else {
155 return;
156 };
157 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
162 return;
163 }
164 let Ok(text) = std::fs::read_to_string(&path) else {
165 return;
166 };
167 probe_cache_adopt(&text);
168 });
169}
170
171fn probe_cache_adopt(text: &str) {
175 for line in text.lines() {
176 let Some((key, verdict)) = line.rsplit_once('\t') else {
177 continue;
178 };
179 let winner = match verdict.trim() {
180 "gpu" => 1u8,
181 "cpu" => 2u8,
182 _ => continue,
183 };
184 for (i, name) in CLASS_NAMES.iter().enumerate() {
185 if probe_cache_key_named(name) == key {
186 let _ = PROBES[i].state.compare_exchange(
187 0,
188 winner,
189 Ordering::Relaxed,
190 Ordering::Relaxed,
191 );
192 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
193 }
194 }
195 }
196}
197
198fn probe_cache_store(c: OpClass, winner: u8) {
201 let Some(path) = probe_cache_path() else {
202 return;
203 };
204 let line = format!(
205 "{}\t{}\n",
206 probe_cache_key_named(CLASS_NAMES[c as usize]),
207 if winner == 1 { "gpu" } else { "cpu" }
208 );
209 use std::io::Write;
210 if let Ok(mut f) = std::fs::OpenOptions::new()
211 .create(true)
212 .append(true)
213 .open(&path)
214 {
215 let _ = f.write_all(line.as_bytes());
216 }
217}
218
219pub fn cold_epoch() -> u64 {
225 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
226}
227static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
228
229pub(crate) fn probe_note_cold() {
230 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
231 PROBE_COLD.with(|c| c.set(true));
232}
233
234pub(crate) fn probe_was_cold() -> bool {
238 PROBE_COLD.with(|c| c.get())
239}
240
241pub fn set_layer(l: i64) {
243 CUR_LAYER.with(|c| c.set(l));
244}
245
246pub fn cur_layer() -> i64 {
248 CUR_LAYER.with(|c| c.get())
249}
250
251pub fn automatic_layer_prefix(
254 model: &Arc<CmfModel>,
255 num_layers: usize,
256 physical_layers: usize,
257) -> Option<usize> {
258 match backend() {
259 #[cfg(feature = "gpu")]
260 Backend::Wgpu => {
261 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
262 }
263 _ => None,
264 }
265}
266
267fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
270 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
271 R.get_or_init(|| {
272 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
273 let mut v = Vec::new();
274 for part in s.split(',') {
275 let part = part.trim();
276 match part.split_once('-') {
277 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
278 None => {
279 let x: i64 = part.parse().ok()?;
280 v.push((x, x));
281 }
282 }
283 }
284 Some(v)
285 })
286}
287
288fn layer_allowed() -> bool {
289 match layer_ranges() {
290 None => true,
291 Some(ranges) => {
292 let cur = CUR_LAYER.with(|c| c.get());
293 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
294 }
295 }
296}
297
298pub fn enabled_here() -> bool {
302 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
303}
304
305pub fn q2tp_gpu_opt_in() -> bool {
311 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
312}
313
314#[derive(Clone, Copy)]
326pub enum OpClass {
327 Ffn = 0,
329 Matvec = 1,
331 Matmat = 2,
333 Batch = 3,
335 MatmatWide = 4,
341 MatvecHead = 5,
348 GemmNt = 6,
355}
356
357pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
361 if rows * cols >= 67_108_864 {
362 OpClass::MatvecHead
363 } else {
364 OpClass::Matvec
365 }
366}
367
368pub enum ProbeArm {
370 Gpu,
372 CpuTimed,
374 Cpu,
376}
377
378const PROBE_SAMPLES: u32 = 6;
380
381const PROBE_DECLINE_LIMIT: u32 = 16;
385
386const PROBE_WARMUP: u32 = 1;
388
389struct Probe {
390 state: AtomicU8,
392 flip: AtomicU32,
393 gpu_ns: AtomicU64,
394 gpu_n: AtomicU32,
395 declines: AtomicU32,
405 gpu_burn: AtomicU32,
418 cpu_ns: AtomicU64,
419 cpu_n: AtomicU32,
420 gpu_min: AtomicU64,
427 cpu_min: AtomicU64,
428}
429
430impl Probe {
431 const fn new() -> Self {
432 Self {
433 state: AtomicU8::new(0),
434 flip: AtomicU32::new(0),
435 gpu_ns: AtomicU64::new(0),
436 gpu_n: AtomicU32::new(0),
437 declines: AtomicU32::new(0),
438 gpu_burn: AtomicU32::new(PROBE_WARMUP),
439 cpu_ns: AtomicU64::new(0),
440 cpu_n: AtomicU32::new(0),
441 gpu_min: AtomicU64::new(u64::MAX),
442 cpu_min: AtomicU64::new(u64::MAX),
443 }
444 }
445}
446
447static PROBES: [Probe; 7] = [
448 Probe::new(),
449 Probe::new(),
450 Probe::new(),
451 Probe::new(),
452 Probe::new(),
453 Probe::new(),
454 Probe::new(),
455];
456
457static TRUST_GPU: AtomicBool = AtomicBool::new(false);
464
465pub fn trust_gpu() -> GpuTrust {
467 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
468 GpuTrust(was)
469}
470
471pub struct GpuTrust(bool);
472
473impl Drop for GpuTrust {
474 fn drop(&mut self) {
475 TRUST_GPU.store(self.0, Ordering::Relaxed);
476 }
477}
478
479fn probe_on_for(c: OpClass) -> bool {
480 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
486 return false;
487 }
488 probe_on()
489}
490
491pub fn probe_enabled() -> bool {
495 probe_on()
496}
497
498fn probe_on() -> bool {
499 static ON: OnceLock<bool> = OnceLock::new();
500 *ON.get_or_init(|| {
501 std::env::var("CMF_GPU_PROBE")
502 .map(|v| v != "0" && v != "off")
503 .unwrap_or(true)
504 })
505}
506
507pub fn q1_force() -> bool {
512 #[cfg(target_os = "macos")]
513 {
514 backend() == Backend::Metal
515 }
516 #[cfg(not(target_os = "macos"))]
517 {
518 false
519 }
520}
521
522pub fn fused_block_trusted() -> bool {
541 #[cfg(target_os = "macos")]
542 if backend() == Backend::Metal {
543 return true;
544 }
545 wgpu_graph_default()
546}
547
548pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
560 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
561 {
562 return crate::gpu_wgpu::weight_is_resident(model, idx);
563 }
564 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
565 {
566 let _ = (model, idx);
567 true
568 }
569}
570
571pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
572 if !weights_resident && probe_deciding(c) {
573 return ProbeArm::Gpu;
574 }
575 probe_arm(c)
576}
577
578pub fn probe_arm(c: OpClass) -> ProbeArm {
579 PROBE_COLD.with(|f| f.set(false));
584 if !probe_on_for(c) {
585 return ProbeArm::Gpu;
586 }
587 probe_cache_load();
588 let p = &PROBES[c as usize];
589 match p.state.load(Ordering::Relaxed) {
590 1 => ProbeArm::Gpu,
591 2 => ProbeArm::Cpu,
592 _ => {
593 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
594 ProbeArm::Gpu
595 } else {
596 ProbeArm::CpuTimed
597 }
598 }
599 }
600}
601
602pub fn probe_note_decline(c: OpClass) {
606 let p = &PROBES[c as usize];
607 if p.state.load(Ordering::Relaxed) != 0 {
608 return;
609 }
610 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
611 if n >= PROBE_DECLINE_LIMIT
612 && p.state
613 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
614 .is_ok()
615 {
616 tracing::info!(
617 "gpu probe [{}]: device declined {n} times → cpu",
618 CLASS_NAMES[c as usize]
619 );
620 }
621}
622
623pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
626 probe_record_into(
627 &PROBES[c as usize],
628 CLASS_NAMES[c as usize],
629 Some(c),
630 gpu,
631 dur,
632 )
633}
634
635fn probe_record_into(
638 p: &Probe,
639 class_name: &str,
640 cache: Option<OpClass>,
641 gpu: bool,
642 dur: std::time::Duration,
643) {
644 if p.state.load(Ordering::Relaxed) != 0 {
645 return;
646 }
647 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
648 return; }
650 if gpu {
651 let left = p.gpu_burn.load(Ordering::Relaxed);
655 if left > 0 {
656 p.gpu_burn.store(left - 1, Ordering::Relaxed);
657 return; }
659 }
660 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
661 if gpu {
662 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
663 p.gpu_n.fetch_add(1, Ordering::Relaxed);
664 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
665 } else {
666 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
667 p.cpu_n.fetch_add(1, Ordering::Relaxed);
668 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
669 }
670 let (gn, cn) = (
671 p.gpu_n.load(Ordering::Relaxed),
672 p.cpu_n.load(Ordering::Relaxed),
673 );
674 if gn >= 2 && cn >= 2 {
675 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
679 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
680 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
690 return;
691 }
692 let winner = if g <= cp { 1 } else { 2 };
693 if p.state
694 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
695 .is_ok()
696 {
697 tracing::info!(
698 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
699 class_name,
700 g / 1e6,
701 cp / 1e6,
702 if winner == 1 { "gpu" } else { "cpu" },
703 );
704 if let Some(c) = cache {
705 probe_cache_store(c, winner);
706 }
707 }
708 }
709}
710
711pub fn probe_deciding(c: OpClass) -> bool {
714 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
715}
716
717#[allow(unused_variables)]
727pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
728 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
729 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
730 let resident = match backend() {
731 #[cfg(target_os = "macos")]
732 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
733 #[cfg(feature = "gpu")]
734 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
735 Backend::None => false,
736 };
737 if !resident && may_upload {
738 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
739 }
740 resident
741}
742
743#[cfg(test)]
745pub(crate) fn probe_reset() {
746 for p in &PROBES {
747 p.state.store(0, Ordering::Relaxed);
748 p.flip.store(0, Ordering::Relaxed);
749 p.gpu_ns.store(0, Ordering::Relaxed);
750 p.gpu_n.store(0, Ordering::Relaxed);
751 p.cpu_ns.store(0, Ordering::Relaxed);
752 p.cpu_n.store(0, Ordering::Relaxed);
753 }
754}
755
756#[cfg(test)]
760static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
761
762#[cfg(test)]
763fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
764 PROBE_TEST_LOCK
765 .lock()
766 .unwrap_or_else(std::sync::PoisonError::into_inner)
767}
768
769#[cfg(test)]
770mod probe_tests {
771 use super::*;
772 use std::time::Duration;
773
774 #[test]
775 fn cpu_only_whole_operator_dispatch_inherits_and_restores_scope() {
776 let pool = crate::pool::Pool::with_spin(3, 0);
777 cpu_scope(|| {
778 let inherit = inherit_cpu_scope();
779 pool.run_rows(64, &|_, _| {
780 let _guard = inherit();
781 assert!(CPU_ONLY.get());
782 });
783 });
784 pool.run_rows(64, &|_, _| assert!(!CPU_ONLY.get()));
785 }
786
787 #[test]
790 fn probe_alternates_discards_cold_and_decides() {
791 let _probe_guard = probe_test_guard();
792 probe_reset();
793 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
795 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
796
797 probe_note_cold();
801 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
802 for _ in 0..PROBE_SAMPLES {
803 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
804 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
805 }
806 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
807
808 for _ in 0..PROBE_SAMPLES {
810 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
811 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
812 }
813 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
814
815 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
817 CPU_ONLY.with(|c| assert!(!c.get()));
818 cpu_scope(|| {
819 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
820 CPU_ONLY.with(|c| assert!(c.get()));
821 });
822 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
823 CPU_ONLY.with(|c| assert!(!c.get()));
824 probe_reset();
825 }
826
827 #[test]
828 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
829 let _probe_guard = probe_test_guard();
830 let mine = probe_cache_key_named("gemm-nt");
842 let state = || {
843 PROBES[OpClass::GemmNt as usize]
844 .state
845 .load(Ordering::Relaxed)
846 };
847
848 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
850 assert_eq!(state(), 0);
851 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
853 assert_ne!(older, mine);
854 probe_cache_adopt(&format!("{older}\tgpu\n"));
855 assert_eq!(state(), 0);
856 probe_cache_adopt(&format!("{mine}\tcpu\n"));
858 assert_eq!(state(), 2);
859
860 PROBES[OpClass::GemmNt as usize]
861 .state
862 .store(0, Ordering::Relaxed);
863 }
864}
865
866pub const GPU_MIN_ROWS: usize = 65_536;
869
870pub fn min_rows() -> usize {
877 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
878 .ok()
879 .and_then(|v| v.parse().ok())
880 {
881 return v;
882 }
883 if discrete() { 4096 } else { GPU_MIN_ROWS }
884}
885
886pub fn discrete() -> bool {
888 match backend() {
889 #[cfg(feature = "gpu")]
890 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
891 #[cfg(target_os = "macos")]
892 Backend::Metal => false, Backend::None => false,
894 }
895}
896
897pub struct MoeJob<'a> {
901 pub gate: (usize, usize, usize, &'a [f32]),
902 pub up: (usize, usize, usize, &'a [f32]),
903 pub down: (usize, usize, usize, &'a [f32]),
904 pub xs_gate: Vec<f32>,
905 pub xs_up: Vec<f32>,
906 pub down_col: &'a [f32],
907 pub w: f32,
908 pub q1: bool,
911 pub q4t: bool,
914 pub q4tp: bool,
918 pub gu_q2: bool,
922 pub swiglu_limit: f32,
927}
928
929pub struct BatchJob<'a> {
931 pub idx: usize,
932 pub rows: usize,
933 pub cols: usize,
934 pub row_scale: &'a [f32],
935 pub xs: Vec<f32>,
936 pub layout: BatchLayout,
940}
941
942#[derive(Clone, Copy, PartialEq, Eq, Debug)]
945pub enum BatchLayout {
946 Q8,
947 Q1,
948 Q4t,
949 Q4tp,
950}
951
952#[derive(Clone, Copy, PartialEq, Eq)]
953enum Backend {
954 None,
955 #[cfg(target_os = "macos")]
956 Metal,
957 #[cfg(feature = "gpu")]
958 Wgpu,
959}
960
961fn backend() -> Backend {
962 #[cfg(feature = "gpu")]
963 if crate::gpu_wgpu::selected() {
964 return if crate::gpu_wgpu::enabled() {
965 Backend::Wgpu
966 } else {
967 Backend::None
968 };
969 }
970 #[cfg(target_os = "macos")]
971 if crate::gpu_metal::enabled() {
972 return Backend::Metal;
973 }
974 Backend::None
975}
976
977pub fn backend_available() -> bool {
983 #[cfg(target_os = "macos")]
984 {
985 true
987 }
988 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
989 {
990 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
991 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
992 }
993 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
994 {
995 false
996 }
997}
998
999static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
1005
1006pub fn pause_gpu() -> GpuPause {
1008 GPU_PAUSED.store(true, Ordering::Relaxed);
1009 GpuPause(())
1010}
1011
1012pub struct GpuPause(());
1013
1014impl Drop for GpuPause {
1015 fn drop(&mut self) {
1016 GPU_PAUSED.store(false, Ordering::Relaxed);
1017 }
1018}
1019
1020pub fn enabled() -> bool {
1021 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
1022}
1023
1024pub fn wgpu_active() -> bool {
1038 #[cfg(feature = "gpu")]
1039 {
1040 matches!(backend(), Backend::Wgpu)
1041 }
1042 #[cfg(not(feature = "gpu"))]
1043 {
1044 false
1045 }
1046}
1047
1048pub fn default_device() -> usize {
1055 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1056 *D.get_or_init(|| {
1057 std::env::var("CMF_GPU_ADAPTER")
1058 .ok()
1059 .and_then(|v| v.trim().parse::<usize>().ok())
1060 .unwrap_or(0)
1061 })
1062}
1063
1064thread_local! {
1065 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1066}
1067
1068pub fn current_device() -> usize {
1070 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1071}
1072
1073pub fn set_current_device(i: usize) {
1077 CUR_DEV.with(|c| c.set(Some(i)));
1078}
1079
1080pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1082 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1083 let r = f();
1084 CUR_DEV.with(|c| c.set(prev));
1085 r
1086}
1087
1088pub fn device_count() -> usize {
1091 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1092 {
1093 return crate::gpu_wgpu::adapter_count();
1094 }
1095 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1096 {
1097 usize::from(backend_available())
1098 }
1099}
1100
1101pub fn vram_budget() -> u64 {
1105 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1106 {
1107 return crate::gpu_wgpu::device_vram_budget();
1108 }
1109 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1110 {
1111 if backend_available() { u64::MAX } else { 0 }
1112 }
1113}
1114
1115pub fn resident_bytes() -> u64 {
1119 #[cfg(feature = "gpu")]
1120 {
1121 if backend() == Backend::Wgpu {
1122 return crate::gpu_wgpu::resident_bytes();
1123 }
1124 }
1125 0
1126}
1127
1128pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1132 #[cfg(feature = "gpu")]
1133 {
1134 if backend() == Backend::Wgpu {
1135 return crate::gpu_wgpu::o1_device_stats(kv_id);
1136 }
1137 }
1138 let _ = kv_id;
1139 (0, 0)
1140}
1141
1142pub fn upload_bytes() -> u64 {
1146 #[cfg(feature = "gpu")]
1147 {
1148 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1149 }
1150 #[cfg(not(feature = "gpu"))]
1151 0
1152}
1153
1154pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1158 #[cfg(feature = "gpu")]
1159 {
1160 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1161 }
1162 let _ = (block, rounds);
1163 None
1164}
1165
1166#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1178pub enum GraphPhase {
1179 Prefill,
1180 Decode,
1181}
1182
1183pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1191 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1192 Some("0") => false,
1193 Some("prefill") => phase == GraphPhase::Prefill,
1194 Some(_) => true,
1195 None => {
1196 if wgpu_graph_default() {
1197 return true;
1198 }
1199 let _ = phase;
1204 false
1205 }
1206 }
1207}
1208
1209pub fn wgpu_graph_default() -> bool {
1210 #[cfg(feature = "gpu")]
1211 {
1212 matches!(backend(), Backend::Wgpu)
1218 && (crate::gpu_wgpu::discrete_active()
1219 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1220 }
1221 #[cfg(not(feature = "gpu"))]
1222 {
1223 false
1224 }
1225}
1226
1227#[allow(clippy::too_many_arguments, unused_variables)]
1229pub fn q8_matvec_range(
1230 model: &Arc<CmfModel>,
1231 idx: usize,
1232 row0: usize,
1233 row_scale: &[f32],
1234 xs: &[f32],
1235 rows: usize,
1236 cols: usize,
1237 out: &mut [f32],
1238) -> bool {
1239 match backend() {
1240 #[cfg(target_os = "macos")]
1241 Backend::Metal => {
1242 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1243 }
1244 #[cfg(feature = "gpu")]
1245 Backend::Wgpu => {
1246 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1247 }
1248 Backend::None => false,
1249 }
1250}
1251
1252pub(crate) fn q82_short_rows(
1254 model: &Arc<CmfModel>,
1255 idx: usize,
1256 xs: &[f32],
1257 b: usize,
1258 rows: usize,
1259 cols: usize,
1260 out: &mut [f32],
1261) -> bool {
1262 #[cfg(feature = "gpu")]
1263 if enabled_here() && backend() == Backend::Wgpu {
1264 return crate::gpu_wgpu::q82_short_rows(model, idx, xs, b, rows, cols, out);
1265 }
1266 let _ = (model, idx, xs, b, rows, cols, out);
1267 false
1268}
1269
1270#[allow(clippy::too_many_arguments, unused_variables)]
1273#[allow(clippy::too_many_arguments)]
1277pub fn q8_matmat_2f(
1278 model: &Arc<CmfModel>,
1279 idx: usize,
1280 row_scale: &[f32],
1281 col_field: &[f32],
1282 xs: &[f32],
1283 b: usize,
1284 rows: usize,
1285 cols: usize,
1286 out: &mut [f32],
1287) -> bool {
1288 #[allow(unreachable_patterns)]
1289 match backend() {
1290 #[cfg(feature = "gpu")]
1291 Backend::Wgpu => {
1292 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1293 }
1294 _ => false,
1295 }
1296}
1297
1298pub fn q8_matmat(
1299 model: &Arc<CmfModel>,
1300 idx: usize,
1301 row_scale: &[f32],
1302 pre: &[f32],
1303 b: usize,
1304 rows: usize,
1305 cols: usize,
1306 out: &mut [f32],
1307) -> bool {
1308 match backend() {
1309 #[cfg(target_os = "macos")]
1310 Backend::Metal => {
1311 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1312 }
1313 #[cfg(feature = "gpu")]
1314 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1315 Backend::None => false,
1316 }
1317}
1318
1319#[allow(unused_variables)]
1322pub fn q1_matvec(
1323 model: &Arc<CmfModel>,
1324 idx: usize,
1325 xs: &[f32],
1326 rows: usize,
1327 cols: usize,
1328 out: &mut [f32],
1329) -> bool {
1330 match backend() {
1331 #[cfg(target_os = "macos")]
1332 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1333 #[cfg(feature = "gpu")]
1334 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1335 Backend::None => false,
1336 }
1337}
1338
1339#[allow(clippy::too_many_arguments)]
1343pub fn attn_dropin(
1344 model: &Arc<CmfModel>,
1345 kv_id: u64,
1346 layer: usize,
1347 normed: &[f32],
1348 wq_idx: usize,
1349 wk_idx: usize,
1350 wv_idx: usize,
1351 wo_idx: usize,
1352 q_norm: Option<&[f32]>,
1353 k_norm: Option<&[f32]>,
1354 late_qk_norm: bool,
1355 invf: &[f32],
1356 nh: usize,
1357 nkv: usize,
1358 hd: usize,
1359 rd: usize,
1360 hidden: usize,
1361 pos: usize,
1362 cap: usize,
1363 gemma: bool,
1364 eps: f32,
1365 cpu_k: &[Vec<f32>],
1366 cpu_v: &[Vec<f32>],
1367 out: &mut [f32],
1368) -> bool {
1369 match backend() {
1370 #[cfg(feature = "gpu")]
1371 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1372 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1373 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1374 ),
1375 #[allow(unused_variables)]
1376 _ => false,
1377 }
1378}
1379
1380#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1384pub enum GraphPrismOp {
1385 None,
1386 Forward,
1387 InverseEmbedding,
1388}
1389
1390pub struct GraphW<'a> {
1394 pub idx: usize,
1395 pub kind: u8,
1396 pub row_scale: &'a [f32],
1397 pub data: &'a [f32],
1398 pub prism: GraphPrismOp,
1399 pub affine: bool,
1400}
1401
1402pub enum GraphAttn<'a> {
1405 Full {
1406 wq: GraphW<'a>,
1407 wk: GraphW<'a>,
1408 wv: GraphW<'a>,
1409 wo: GraphW<'a>,
1410 q_norm: Option<&'a [f32]>,
1411 k_norm: Option<&'a [f32]>,
1412 late_qk_norm: bool,
1414 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1416 output_gate: bool,
1419 cpu_k: &'a [Vec<f32>],
1420 cpu_v: &'a [Vec<f32>],
1421 geom: Option<GraphAttnGeom<'a>>,
1428 },
1429 Gdn {
1430 qkv: GraphW<'a>,
1431 z: GraphW<'a>,
1432 a: GraphW<'a>,
1433 b: GraphW<'a>,
1434 out: GraphW<'a>,
1435 conv1d: &'a [f32],
1436 a_log: &'a [f32],
1437 dt_bias: &'a [f32],
1438 norm: &'a [f32],
1439 nv: usize,
1440 nk: usize,
1441 dk: usize,
1442 dv: usize,
1443 kk: usize,
1444 cpu_state: &'a [f32],
1449 },
1450 ShortConv {
1457 inp: GraphW<'a>,
1459 out: GraphW<'a>,
1461 taps: &'a [f32],
1464 kernel: usize,
1465 cpu_state: &'a [f32],
1469 },
1470}
1471
1472#[derive(Clone, Copy)]
1477pub struct GraphAttnGeom<'a> {
1478 pub nkv: usize,
1480 pub dv: usize,
1482 pub rd: usize,
1484 pub invf: &'a [f32],
1486 pub window: Option<usize>,
1489 pub sink: Option<&'a [f32]>,
1492}
1493
1494pub struct GraphLayer<'a> {
1496 pub input_norm: &'a [f32],
1497 pub attn: GraphAttn<'a>,
1498 pub post_norm: &'a [f32],
1499 pub ffn: GraphFfn<'a>,
1500}
1501
1502pub enum GraphFfn<'a> {
1507 AttentionOnly,
1510 Dense {
1511 gate: GraphW<'a>,
1512 up: GraphW<'a>,
1513 down: GraphW<'a>,
1514 },
1515 Moe {
1516 router: GraphW<'a>,
1518 shared_gate: GraphW<'a>,
1520 experts: Vec<(usize, usize, usize)>,
1524 n_exp: usize,
1526 top_k: usize,
1527 inter: usize,
1528 norm_topk: bool,
1529 q4tp: bool,
1535 gu_q2: bool,
1539 sigmoid: bool,
1543 bias: Option<&'a [f32]>,
1546 has_shared: bool,
1550 shared_gated: bool,
1555 route_scale: f32,
1558 },
1559}
1560
1561#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1564pub enum TokenGraphOutcome {
1565 Declined,
1567 Completed,
1569 Failed,
1571}
1572
1573#[allow(clippy::too_many_arguments)]
1578pub fn forward_token_graph(
1579 model: &Arc<CmfModel>,
1580 kv_id: u64,
1581 layers: &[GraphLayer],
1582 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1585 o1_epoch: u64,
1586 invf: &[f32],
1587 h: &mut [f32],
1588 nh: usize,
1589 nkv: usize,
1590 hd: usize,
1591 attn_scale: f32,
1592 rd: usize,
1593 hidden: usize,
1594 inter: usize,
1595 position: usize,
1596 cap: usize,
1597 gemma: bool,
1598 eps: f32,
1599 lm_head: Option<(&GraphW, usize)>,
1600 final_norm: &[f32],
1601 logits: &mut Vec<f32>,
1602 loop_norm_at: &[usize],
1603 steps: usize,
1604 embed: Option<(&GraphW, usize, f32)>,
1605 ids_out: Option<&mut Vec<u32>>,
1606 layers_run: Option<&mut usize>,
1609 layer_base: usize,
1613 hidden_too: bool,
1615) -> TokenGraphOutcome {
1616 match backend() {
1617 #[cfg(feature = "gpu")]
1618 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1619 model,
1620 kv_id,
1621 layers,
1622 o1,
1623 o1_epoch,
1624 invf,
1625 h,
1626 nh,
1627 nkv,
1628 hd,
1629 attn_scale,
1630 rd,
1631 hidden,
1632 inter,
1633 position,
1634 cap,
1635 gemma,
1636 eps,
1637 lm_head,
1638 final_norm,
1639 logits,
1640 loop_norm_at,
1641 steps,
1642 embed,
1643 ids_out,
1644 layers_run,
1645 layer_base,
1646 hidden_too,
1647 ),
1648 #[allow(unused_variables)]
1649 _ => {
1650 let _ = (
1651 attn_scale,
1652 lm_head,
1653 final_norm,
1654 logits,
1655 loop_norm_at,
1656 layers_run,
1657 layer_base,
1658 hidden_too,
1659 );
1660 TokenGraphOutcome::Declined
1661 }
1662 }
1663}
1664
1665#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1669pub enum BatchGraphOutcome {
1670 Declined,
1673 Completed,
1675 Failed,
1679}
1680
1681pub struct SpecTail<'a> {
1682 pub lm: GraphW<'a>,
1683 pub lm_rows: usize,
1684 pub final_norm: &'a [f32],
1685 pub logits_out: &'a mut Vec<f32>,
1686}
1687
1688#[allow(clippy::too_many_arguments)]
1692pub fn forward_batch_graph(
1693 model: &Arc<CmfModel>,
1694 kv_id: u64,
1695 layers: &[GraphLayer],
1696 invf: &[f32],
1697 h: &mut [f32],
1698 nh: usize,
1699 nkv: usize,
1700 hd: usize,
1701 rd: usize,
1702 hidden: usize,
1703 inter: usize,
1704 positions: &[usize],
1705 cap: usize,
1706 gemma: bool,
1707 eps: f32,
1708 attn_scale: f32,
1709 k: usize,
1710 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1713 o1_epoch: u64,
1714 spec: Option<SpecTail<'_>>,
1715 layers_run: Option<&mut usize>,
1721) -> BatchGraphOutcome {
1722 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)
1723}
1724
1725thread_local! {
1726 static MIMO_ATTN_SCRATCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1727}
1728
1729pub(crate) fn mimo_attention_scratch_enabled() -> bool {
1730 MIMO_ATTN_SCRATCH.with(std::cell::Cell::get)
1731}
1732
1733#[doc(hidden)]
1736pub fn mimo_attention_scratch_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
1737 struct Restore(bool);
1738 impl Drop for Restore {
1739 fn drop(&mut self) {
1740 MIMO_ATTN_SCRATCH.with(|v| v.set(self.0));
1741 }
1742 }
1743 let _restore = Restore(MIMO_ATTN_SCRATCH.with(|v| v.replace(enabled)));
1744 f()
1745}
1746
1747thread_local! {
1748 static MIMO_Q8_SHORT: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1749}
1750
1751pub(crate) fn mimo_q8_short_enabled() -> bool {
1752 MIMO_Q8_SHORT.with(std::cell::Cell::get)
1753}
1754
1755#[doc(hidden)]
1757pub fn mimo_q8_short_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
1758 struct Restore(bool);
1759 impl Drop for Restore {
1760 fn drop(&mut self) {
1761 MIMO_Q8_SHORT.with(|v| v.set(self.0));
1762 }
1763 }
1764 let _restore = Restore(MIMO_Q8_SHORT.with(|v| v.replace(enabled)));
1765 f()
1766}
1767
1768#[allow(clippy::too_many_arguments)]
1770pub fn forward_batch_graph_at(
1771 model: &Arc<CmfModel>,
1772 kv_id: u64,
1773 layer_base: usize,
1774 layers: &[GraphLayer],
1775 invf: &[f32],
1776 h: &mut [f32],
1777 nh: usize,
1778 nkv: usize,
1779 hd: usize,
1780 rd: usize,
1781 hidden: usize,
1782 inter: usize,
1783 positions: &[usize],
1784 cap: usize,
1785 gemma: bool,
1786 eps: f32,
1787 attn_scale: f32,
1788 k: usize,
1789 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1792 o1_epoch: u64,
1793 spec: Option<SpecTail<'_>>,
1794 layers_run: Option<&mut usize>,
1800) -> BatchGraphOutcome {
1801 match backend() {
1802 #[cfg(feature = "gpu")]
1803 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph_at(
1804 model, kv_id, layer_base, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions,
1805 cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run,
1806 ),
1807 #[allow(unreachable_patterns)]
1808 _ => {
1809 let _ = (o1, o1_epoch, spec, layers_run);
1810 BatchGraphOutcome::Declined
1811 }
1812 }
1813}
1814
1815pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1820 #[cfg(feature = "gpu")]
1821 if backend() == Backend::Wgpu {
1822 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1823 }
1824 #[allow(unreachable_code)]
1825 {
1826 let _ = (kv_id, slot, base_pos, expected_layers);
1827 false
1828 }
1829}
1830
1831pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1837 #[cfg(feature = "gpu")]
1838 if backend() == Backend::Wgpu {
1839 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1840 }
1841 #[cfg(target_os = "macos")]
1842 if backend() == Backend::Metal {
1843 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1844 return true;
1845 }
1846 false
1847}
1848
1849pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
1853 #[cfg(feature = "gpu")]
1854 if backend() == Backend::Wgpu {
1855 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
1856 }
1857 None
1858}
1859
1860pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
1863 #[cfg(feature = "gpu")]
1864 if backend() == Backend::Wgpu {
1865 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
1866 }
1867 false
1868}
1869
1870pub fn graph_kv_read_rows(
1874 _kv_id: u64,
1875 _reqs: &[(usize, usize, usize)],
1876 _nkv: usize,
1877 _hd: usize,
1878) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
1879 #[cfg(feature = "gpu")]
1880 if backend() == Backend::Wgpu {
1881 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
1882 }
1883 None
1884}
1885
1886pub fn graph_kv_pull_host(
1892 _kv_id: u64,
1893 _layer: usize,
1894 _from: usize,
1895 _to: usize,
1896 _nkv: usize,
1897 _hd: usize,
1898) -> Option<(Vec<f32>, Vec<f32>, usize)> {
1899 #[cfg(feature = "gpu")]
1900 if backend() == Backend::Wgpu {
1901 return crate::gpu_wgpu::kv_mirror_pull_host(_kv_id, _layer, _from, _to, _nkv, _hd);
1902 }
1903 None
1904}
1905
1906pub fn graph_kv_reset(_kv_id: u64) {
1908 #[cfg(feature = "gpu")]
1909 if backend() == Backend::Wgpu {
1910 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1911 }
1912}
1913
1914pub fn q1t_matvec(
1918 model: &Arc<CmfModel>,
1919 idx: usize,
1920 xs: &[f32],
1921 rows: usize,
1922 cols: usize,
1923 out: &mut [f32],
1924) -> bool {
1925 match backend() {
1926 #[cfg(target_os = "macos")]
1927 Backend::Metal => {
1928 if metal_q1t_enabled() {
1929 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1930 } else {
1931 false
1932 }
1933 }
1934 #[cfg(feature = "gpu")]
1935 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1936 Backend::None => false,
1937 }
1938}
1939
1940#[allow(unused_variables)]
1943pub fn q4b_matvec(
1944 model: &Arc<CmfModel>,
1945 idx: usize,
1946 xs: &[f32],
1947 rows: usize,
1948 cols: usize,
1949 out: &mut [f32],
1950) -> bool {
1951 match backend() {
1952 #[cfg(target_os = "macos")]
1953 Backend::Metal => false,
1954 #[cfg(feature = "gpu")]
1955 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1956 Backend::None => false,
1957 }
1958}
1959
1960pub fn q1t_matmat(
1963 model: &Arc<CmfModel>,
1964 idx: usize,
1965 xs: &[f32],
1966 b: usize,
1967 rows: usize,
1968 cols: usize,
1969 out: &mut [f32],
1970) -> bool {
1971 match backend() {
1972 #[cfg(target_os = "macos")]
1973 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1977 #[cfg(feature = "gpu")]
1978 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1979 Backend::None => false,
1980 }
1981}
1982
1983#[cfg(target_os = "macos")]
1987pub(crate) fn metal_q1t_enabled() -> bool {
1988 std::env::var("CMF_METAL_Q1T")
1989 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1990 .unwrap_or(true)
1991}
1992
1993pub fn q1_matmat(
1995 model: &Arc<CmfModel>,
1996 idx: usize,
1997 xs: &[f32],
1998 b: usize,
1999 rows: usize,
2000 cols: usize,
2001 out: &mut [f32],
2002) -> bool {
2003 match backend() {
2004 #[cfg(feature = "gpu")]
2005 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
2006 #[allow(unused_variables)]
2007 _ => false,
2008 }
2009}
2010
2011static MM_KILL: AtomicBool = AtomicBool::new(false);
2016pub(crate) fn mm_killed() -> bool {
2017 MM_KILL.load(Ordering::Relaxed)
2018}
2019pub(crate) fn mm_kill() {
2020 MM_KILL.store(true, Ordering::Relaxed);
2021}
2022
2023static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2030const MM_STRIKES_TO_KILL: u32 = 3;
2031static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2038
2039pub fn mm_kill_arm(on: bool) {
2042 MM_ARMED.store(on, Ordering::Relaxed);
2043 if on {
2044 MM_STRIKES.store(0, Ordering::Relaxed);
2045 }
2046}
2047
2048pub(crate) fn mm_budget_check(
2055 what: &str,
2056 el: std::time::Duration,
2057 budget: std::time::Duration,
2058 exempt: bool,
2059) {
2060 if el <= budget {
2061 MM_STRIKES.store(0, Ordering::Relaxed);
2062 return;
2063 }
2064 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
2065 return;
2066 }
2067 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2068 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
2069 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
2070 if !on {
2071 tracing::info!(
2072 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
2073 );
2074 return;
2075 }
2076 if n >= MM_STRIKES_TO_KILL {
2077 tracing::warn!(
2078 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
2079 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
2080 );
2081 mm_kill();
2082 } else {
2083 tracing::info!(
2084 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
2085 );
2086 }
2087}
2088
2089#[allow(unused_variables, clippy::too_many_arguments)]
2094pub fn chunk_attend(
2095 q: &[f32],
2096 k: &[&[f32]],
2097 v: &[&[f32]],
2098 b: usize,
2099 s0: usize,
2100 nh: usize,
2101 nkv: usize,
2102 hd: usize,
2103 scale: f32,
2104 out: &mut [f32],
2105) -> bool {
2106 match backend() {
2107 #[cfg(feature = "gpu")]
2108 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
2109 #[allow(unreachable_patterns)]
2110 _ => false,
2111 }
2112}
2113
2114#[allow(unused_variables, clippy::too_many_arguments)]
2118pub fn q4t_qkv(
2119 model: &Arc<CmfModel>,
2120 wq: usize,
2121 wk: usize,
2122 wv: usize,
2123 xs: &[f32],
2124 b: usize,
2125 cols: usize,
2126 rq: usize,
2127 rk: usize,
2128 rv: usize,
2129 out: &mut [f32],
2130) -> bool {
2131 match backend() {
2132 #[cfg(feature = "gpu")]
2133 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
2134 #[allow(unreachable_patterns)]
2135 _ => false,
2136 }
2137}
2138
2139#[allow(unused_variables, clippy::too_many_arguments)]
2141#[allow(clippy::too_many_arguments, unused_variables)]
2145pub fn q4tp_ffn_packed(
2146 model: &Arc<CmfModel>,
2147 w1: usize,
2148 w2: usize,
2149 xs: &[f32],
2150 b: usize,
2151 hidden: usize,
2152 inter: usize,
2153 bias: Option<&[f32]>,
2154 out: &mut [f32],
2155) -> bool {
2156 match backend() {
2157 #[cfg(feature = "gpu")]
2158 Backend::Wgpu => {
2159 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
2160 }
2161 #[allow(unreachable_patterns)]
2162 _ => false,
2163 }
2164}
2165
2166pub fn q4tp_ffn(
2167 model: &Arc<CmfModel>,
2168 w1: usize,
2169 w3: usize,
2170 w2: usize,
2171 xs: &[f32],
2172 b: usize,
2173 hidden: usize,
2174 inter: usize,
2175 out: &mut [f32],
2176) -> bool {
2177 match backend() {
2178 #[cfg(target_os = "macos")]
2179 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2180 #[cfg(feature = "gpu")]
2181 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2182 #[allow(unreachable_patterns)]
2183 _ => false,
2184 }
2185}
2186
2187#[allow(clippy::too_many_arguments, unused_variables)]
2192pub fn q4tp_gelu_ffn(
2193 model: &Arc<CmfModel>,
2194 w_in: usize,
2195 w_out: usize,
2196 xs: &[f32],
2197 b: usize,
2198 hidden: usize,
2199 inter: usize,
2200 bias_in: &[f32],
2201 bias_out: &[f32],
2202 out: &mut [f32],
2203) -> bool {
2204 match backend() {
2205 #[cfg(feature = "gpu")]
2206 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2207 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2208 ),
2209 #[allow(unreachable_patterns)]
2210 _ => false,
2211 }
2212}
2213
2214pub fn q4t_ffn(
2215 model: &Arc<CmfModel>,
2216 w1: usize,
2217 w3: usize,
2218 w2: usize,
2219 xs: &[f32],
2220 b: usize,
2221 hidden: usize,
2222 inter: usize,
2223 out: &mut [f32],
2224) -> bool {
2225 match backend() {
2226 #[cfg(target_os = "macos")]
2227 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2228 #[cfg(feature = "gpu")]
2229 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2230 #[allow(unreachable_patterns)]
2231 _ => false,
2232 }
2233}
2234
2235pub struct DitBlockArgs<'a> {
2240 pub n: usize,
2241 pub hidden: usize,
2242 pub inter: usize,
2243 pub nh: usize,
2244 pub nkv: usize,
2245 pub hd: usize,
2246 pub eps: f32,
2247 pub rope_cos: &'a [f32],
2248 pub rope_sin: &'a [f32],
2249 pub norm1: &'a [f32],
2250 pub norm2: &'a [f32],
2251 pub ffn_norm1: &'a [f32],
2252 pub ffn_norm2: &'a [f32],
2253 pub norm_q: &'a [f32],
2254 pub norm_k: &'a [f32],
2255 pub s_msa: &'a [f32],
2256 pub gate_msa: &'a [f32],
2257 pub s_mlp: &'a [f32],
2258 pub gate_mlp: &'a [f32],
2259 pub wq: usize,
2260 pub wk: usize,
2261 pub wv: usize,
2262 pub wo: usize,
2263 pub w1: usize,
2264 pub w3: usize,
2265 pub w2: usize,
2266 pub q4tp: bool,
2270 pub resident_in: bool,
2273 pub resident_out: bool,
2277}
2278
2279pub fn dit_chain_supported() -> bool {
2283 #[cfg(feature = "gpu")]
2284 {
2285 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2286 }
2287 #[allow(unreachable_code)]
2288 false
2289}
2290
2291pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2294 #[cfg(feature = "gpu")]
2295 {
2296 if matches!(backend(), Backend::Wgpu) {
2297 return crate::gpu_wgpu::dit_state_fetch(_x);
2298 }
2299 }
2300 false
2301}
2302
2303#[allow(unused_variables)]
2307#[allow(unused_variables, clippy::too_many_arguments)]
2311pub fn dit_qkv(
2312 model: &Arc<CmfModel>,
2313 wq: usize,
2314 wk: usize,
2315 wv: usize,
2316 xs: &[f32],
2317 b: usize,
2318 hidden: usize,
2319 qrows: usize,
2320 kvrows: usize,
2321 q_out: &mut [f32],
2322 k_out: &mut [f32],
2323 v_out: &mut [f32],
2324) -> bool {
2325 match backend() {
2326 #[cfg(feature = "gpu")]
2327 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2328 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2329 ),
2330 #[allow(unreachable_patterns)]
2331 _ => false,
2332 }
2333}
2334
2335pub struct QwenImageAttentionArgs<'a> {
2343 pub image: &'a [f32],
2344 pub text: &'a [f32],
2345 pub image_tokens: usize,
2346 pub text_tokens: usize,
2347 pub heads: usize,
2348 pub head_dim: usize,
2349 pub image_q: usize,
2350 pub image_k: usize,
2351 pub image_v: usize,
2352 pub text_q: usize,
2353 pub text_k: usize,
2354 pub text_v: usize,
2355 pub image_out: usize,
2356 pub text_out: usize,
2357 pub image_q_norm: &'a [f32],
2358 pub image_k_norm: &'a [f32],
2359 pub text_q_norm: &'a [f32],
2360 pub text_k_norm: &'a [f32],
2361 pub image_cos: &'a [f32],
2362 pub image_sin: &'a [f32],
2363 pub text_cos: &'a [f32],
2364 pub text_sin: &'a [f32],
2365 pub image_q_bias: &'a [f32],
2366 pub image_k_bias: &'a [f32],
2367 pub image_v_bias: &'a [f32],
2368 pub text_q_bias: &'a [f32],
2369 pub text_k_bias: &'a [f32],
2370 pub text_v_bias: &'a [f32],
2371 pub image_out_bias: &'a [f32],
2372 pub text_out_bias: &'a [f32],
2373 pub image_proj: &'a mut [f32],
2374 pub text_proj: &'a mut [f32],
2375}
2376
2377#[allow(clippy::too_many_fields)]
2382pub struct QwenImageChainBlock<'a> {
2383 pub image_mod: &'a [f32],
2384 pub text_mod: &'a [f32],
2385 pub image_q: usize,
2386 pub image_k: usize,
2387 pub image_v: usize,
2388 pub text_q: usize,
2389 pub text_k: usize,
2390 pub text_v: usize,
2391 pub image_out: usize,
2392 pub text_out: usize,
2393 pub image_q_norm: &'a [f32],
2394 pub image_k_norm: &'a [f32],
2395 pub text_q_norm: &'a [f32],
2396 pub text_k_norm: &'a [f32],
2397 pub image_q_bias: &'a [f32],
2398 pub image_k_bias: &'a [f32],
2399 pub image_v_bias: &'a [f32],
2400 pub text_q_bias: &'a [f32],
2401 pub text_k_bias: &'a [f32],
2402 pub text_v_bias: &'a [f32],
2403 pub image_out_bias: &'a [f32],
2404 pub text_out_bias: &'a [f32],
2405 pub image_attn_gate: &'a [f32],
2406 pub text_attn_gate: &'a [f32],
2407 pub image_mlp_in: usize,
2408 pub image_mlp_out: usize,
2409 pub text_mlp_in: usize,
2410 pub text_mlp_out: usize,
2411 pub image_mlp_in_bias: &'a [f32],
2412 pub image_mlp_out_bias: &'a [f32],
2413 pub text_mlp_in_bias: &'a [f32],
2414 pub text_mlp_out_bias: &'a [f32],
2415}
2416
2417#[allow(clippy::too_many_fields)]
2423pub struct QwenImageBlockArgs<'a> {
2424 pub image: &'a mut [f32],
2427 pub text: &'a mut [f32],
2428 pub image_norm: &'a [f32],
2429 pub text_norm: &'a [f32],
2430 pub image_tokens: usize,
2431 pub text_tokens: usize,
2432 pub heads: usize,
2433 pub head_dim: usize,
2434 pub image_cos: &'a [f32],
2435 pub image_sin: &'a [f32],
2436 pub text_cos: &'a [f32],
2437 pub text_sin: &'a [f32],
2438 pub image_q: usize,
2439 pub image_k: usize,
2440 pub image_v: usize,
2441 pub text_q: usize,
2442 pub text_k: usize,
2443 pub text_v: usize,
2444 pub image_out: usize,
2445 pub text_out: usize,
2446 pub image_q_norm: &'a [f32],
2447 pub image_k_norm: &'a [f32],
2448 pub text_q_norm: &'a [f32],
2449 pub text_k_norm: &'a [f32],
2450 pub image_q_bias: &'a [f32],
2451 pub image_k_bias: &'a [f32],
2452 pub image_v_bias: &'a [f32],
2453 pub text_q_bias: &'a [f32],
2454 pub text_k_bias: &'a [f32],
2455 pub text_v_bias: &'a [f32],
2456 pub image_out_bias: &'a [f32],
2457 pub text_out_bias: &'a [f32],
2458 pub image_attn_gate: &'a [f32],
2459 pub text_attn_gate: &'a [f32],
2460 pub image_mlp_in: usize,
2461 pub image_mlp_out: usize,
2462 pub text_mlp_in: usize,
2463 pub text_mlp_out: usize,
2464 pub image_mlp_in_bias: &'a [f32],
2465 pub image_mlp_out_bias: &'a [f32],
2466 pub text_mlp_in_bias: &'a [f32],
2467 pub text_mlp_out_bias: &'a [f32],
2468 pub image_mlp_mod: &'a [f32],
2469 pub text_mlp_mod: &'a [f32],
2470 pub image_mlp_gate: &'a [f32],
2471 pub text_mlp_gate: &'a [f32],
2472}
2473
2474pub struct QwenImageChainArgs<'a> {
2479 pub image: &'a mut [f32],
2480 pub text: &'a mut [f32],
2481 pub image_tokens: usize,
2482 pub text_tokens: usize,
2483 pub heads: usize,
2484 pub head_dim: usize,
2485 pub image_cos: &'a [f32],
2486 pub image_sin: &'a [f32],
2487 pub text_cos: &'a [f32],
2488 pub text_sin: &'a [f32],
2489 pub blocks: &'a [QwenImageChainBlock<'a>],
2490}
2491
2492#[allow(unused_variables)]
2493pub fn qwen_image_attention(
2494 model: &Arc<CmfModel>,
2495 args: &mut QwenImageAttentionArgs<'_>,
2496) -> bool {
2497 match backend() {
2498 #[cfg(feature = "gpu")]
2499 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2500 #[allow(unreachable_patterns)]
2501 _ => false,
2502 }
2503}
2504
2505#[allow(unused_variables)]
2506pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2507 match backend() {
2508 #[cfg(feature = "gpu")]
2509 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2510 #[allow(unreachable_patterns)]
2511 _ => false,
2512 }
2513}
2514
2515#[allow(unused_variables)]
2519pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2520 match backend() {
2521 #[cfg(feature = "gpu")]
2522 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2523 #[allow(unreachable_patterns)]
2524 _ => false,
2525 }
2526}
2527
2528#[allow(unused_variables, clippy::too_many_arguments)]
2535pub fn qwen_image_mlp_inplace(
2536 model: &Arc<CmfModel>,
2537 w_in: usize,
2538 w_out: usize,
2539 data: &mut [f32],
2540 batch: usize,
2541 hidden: usize,
2542 inter: usize,
2543 bias_in: &[f32],
2544 bias_out: &[f32],
2545 modulation: &[f32],
2546 gate: &[f32],
2547) -> bool {
2548 match backend() {
2549 #[cfg(feature = "gpu")]
2550 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2551 model,
2552 w_in,
2553 w_out,
2554 data,
2555 batch,
2556 hidden,
2557 inter,
2558 bias_in,
2559 bias_out,
2560 modulation,
2561 gate,
2562 ),
2563 #[allow(unreachable_patterns)]
2564 _ => false,
2565 }
2566}
2567
2568pub fn fused_dit_block_available() -> bool {
2572 #[cfg(target_os = "macos")]
2573 {
2574 matches!(backend(), Backend::Metal) && fused_block_trusted()
2575 }
2576 #[cfg(not(target_os = "macos"))]
2577 {
2578 false
2579 }
2580}
2581
2582pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2583 dit_block_seg(model, a, &[a.n], x)
2584}
2585
2586pub fn dit_block_seg(
2590 model: &Arc<CmfModel>,
2591 a: &DitBlockArgs,
2592 segs: &[usize],
2593 x: &mut [f32],
2594) -> bool {
2595 match backend() {
2596 #[cfg(target_os = "macos")]
2597 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2598 #[cfg(feature = "gpu")]
2605 Backend::Wgpu
2606 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2607 Some("0") => false,
2608 Some(_) => true,
2609 None => crate::gpu_wgpu::discrete_active(),
2610 } =>
2611 {
2612 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2613 }
2614 #[allow(unreachable_patterns)]
2615 _ => false,
2616 }
2617}
2618
2619pub struct VaeResnetArgs<'a> {
2623 pub groups: usize,
2624 pub ic: usize,
2625 pub oc: usize,
2626 pub h: usize,
2627 pub w: usize,
2628 pub n1w: &'a [f32],
2629 pub n1b: &'a [f32],
2630 pub c1w: &'a [f32],
2631 pub c1b: &'a [f32],
2632 pub c1k: usize,
2633 pub n2w: &'a [f32],
2634 pub n2b: &'a [f32],
2635 pub c2w: &'a [f32],
2636 pub c2b: &'a [f32],
2637 pub c2k: usize,
2638 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2639}
2640
2641#[allow(unused_variables)]
2644pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2645 match backend() {
2646 #[cfg(target_os = "macos")]
2647 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2648 _ => false,
2649 }
2650}
2651
2652#[allow(unused_variables, clippy::too_many_arguments)]
2655pub fn vae_upsample_conv(
2656 w: &[f32],
2657 bias: &[f32],
2658 x: &[f32],
2659 ic: usize,
2660 oc: usize,
2661 h: usize,
2662 w_img: usize,
2663 k: usize,
2664 out: &mut [f32],
2665) -> bool {
2666 match backend() {
2667 #[cfg(target_os = "macos")]
2668 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2669 #[cfg(feature = "gpu")]
2670 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2671 #[allow(unreachable_patterns)]
2672 _ => false,
2673 }
2674}
2675
2676#[allow(unused_variables, clippy::too_many_arguments)]
2679pub fn vae_conv2d(
2680 w: &[f32],
2681 bias: &[f32],
2682 x: &[f32],
2683 ic: usize,
2684 oc: usize,
2685 h: usize,
2686 w_img: usize,
2687 k: usize,
2688 out: &mut [f32],
2689) -> bool {
2690 match backend() {
2691 #[cfg(target_os = "macos")]
2692 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2693 #[cfg(feature = "gpu")]
2694 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2695 #[allow(unreachable_patterns)]
2696 _ => false,
2697 }
2698}
2699
2700#[allow(unused_variables, clippy::too_many_arguments)]
2704#[allow(unused_variables)]
2708#[allow(clippy::too_many_arguments)]
2709#[allow(clippy::too_many_arguments, unused_variables)]
2712pub fn dit_qkv_attention(
2713 model: &Arc<CmfModel>,
2714 qkv_idx: usize,
2715 xn: &[f32],
2716 n: usize,
2717 hidden: usize,
2718 nh: usize,
2719 hd: usize,
2720 scale: f32,
2721 nr: (&[f32], &[f32], &[f32], f32),
2722 out: &mut [f32],
2723) -> bool {
2724 match backend() {
2725 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2726 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2727 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2728 ),
2729 #[allow(unreachable_patterns)]
2730 _ => false,
2731 }
2732}
2733
2734#[allow(clippy::too_many_arguments)]
2737pub fn dit_qkv_attn_out(
2738 model: &Arc<CmfModel>,
2739 qkv_idx: usize,
2740 out_idx: usize,
2741 xn: &[f32],
2742 n: usize,
2743 hidden: usize,
2744 nh: usize,
2745 hd: usize,
2746 scale: f32,
2747 nr: (&[f32], &[f32], &[f32], f32),
2748 proj: &mut [f32],
2749) -> bool {
2750 match backend() {
2751 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2752 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2753 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2754 ),
2755 #[allow(unreachable_patterns)]
2756 _ => false,
2757 }
2758}
2759
2760#[allow(clippy::too_many_arguments)]
2762pub fn vae_qkv_attn_out(
2763 model: &Arc<CmfModel>,
2764 qkv_idx: usize,
2765 out_idx: usize,
2766 xn: &[f32],
2767 n: usize,
2768 dim: usize,
2769 nh: usize,
2770 hd: usize,
2771 scale: f32,
2772 angles: &[f32],
2773 eps: f32,
2774 qkv_bias: &[f32],
2775 proj: &mut [f32],
2776) -> bool {
2777 match backend() {
2778 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2779 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2780 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2781 ),
2782 #[allow(unreachable_patterns)]
2783 _ => false,
2784 }
2785}
2786
2787#[allow(clippy::too_many_arguments)]
2788pub fn vae_attention_packed(
2789 qkv: &[f32],
2790 nh: usize,
2791 n: usize,
2792 hd: usize,
2793 scale: f32,
2794 angles: &[f32],
2795 eps: f32,
2796 out: &mut [f32],
2797) -> bool {
2798 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2799}
2800
2801#[allow(clippy::too_many_arguments)]
2802pub fn vae_attention_packed_layout(
2803 qkv: &[f32],
2804 nh: usize,
2805 n: usize,
2806 hd: usize,
2807 scale: f32,
2808 angles: &[f32],
2809 eps: f32,
2810 out: &mut [f32],
2811 layout: u32,
2812) -> bool {
2813 match backend() {
2814 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2815 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2816 qkv, nh, n, hd, scale, angles, eps, out, layout,
2817 ),
2818 #[allow(unreachable_patterns)]
2819 _ => false,
2820 }
2821}
2822
2823#[allow(clippy::too_many_arguments)]
2824pub fn dit_split_only(
2825 qkv: &[f32],
2826 nh: usize,
2827 n: usize,
2828 hd: usize,
2829 layout: u32,
2830 norm: Option<(&[f32], f32)>,
2831 out_q: &mut [f32],
2832) -> bool {
2833 match backend() {
2834 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2835 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2836 #[allow(unreachable_patterns)]
2837 _ => false,
2838 }
2839}
2840
2841pub fn gemm_nt_f32_transient(
2849 x: &[f32],
2850 w: &[f32],
2851 y: &mut [f32],
2852 n: usize,
2853 k: usize,
2854 m: usize,
2855) -> bool {
2856 match backend() {
2857 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2858 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2859 #[allow(unreachable_patterns)]
2860 _ => false,
2861 }
2862}
2863
2864pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2865 match backend() {
2866 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2867 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2868 #[allow(unreachable_patterns)]
2869 _ => false,
2870 }
2871}
2872
2873#[allow(clippy::too_many_arguments)]
2876pub fn music3_ffn(
2877 model: &std::sync::Arc<CmfModel>,
2878 idx_in: usize,
2879 idx_out: usize,
2880 h: &[f32],
2881 bias_in: &[f32],
2882 n: usize,
2883 hs: usize,
2884 inter: usize,
2885 out: &mut [f32],
2886) -> bool {
2887 match backend() {
2888 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2889 Backend::Wgpu => {
2890 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2891 }
2892 #[allow(unreachable_patterns)]
2893 _ => false,
2894 }
2895}
2896
2897#[allow(clippy::too_many_arguments)]
2901pub fn conv1d_gemm(
2902 x: &[f32],
2903 w: &[f32],
2904 ic: usize,
2905 oc: usize,
2906 n: usize,
2907 k: usize,
2908 pad: usize,
2909 dil: usize,
2910 out_n: usize,
2911 yt: &mut [f32],
2912) -> bool {
2913 match backend() {
2914 #[cfg(target_os = "macos")]
2915 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2916 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2917 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2918 #[allow(unreachable_patterns)]
2919 _ => false,
2920 }
2921}
2922
2923#[allow(clippy::too_many_arguments)]
2925pub fn vae_conv2d_coop(
2926 w: &[f32],
2927 bias: Option<&[f32]>,
2928 x: &[f32],
2929 ic: usize,
2930 oc: usize,
2931 h: usize,
2932 wi: usize,
2933 k: usize,
2934 out: &mut [f32],
2935) -> bool {
2936 match backend() {
2937 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2938 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2939 #[allow(unreachable_patterns)]
2940 _ => false,
2941 }
2942}
2943
2944pub fn dit_attention_packed(
2945 qkv: &[f32],
2946 nh: usize,
2947 n: usize,
2948 hd: usize,
2949 scale: f32,
2950 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2953 out: &mut [f32],
2954) -> bool {
2955 match backend() {
2956 #[cfg(feature = "gpu")]
2963 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2964 #[allow(unreachable_patterns)]
2965 _ => false,
2966 }
2967}
2968
2969pub fn dit_attention_packed_available() -> bool {
2977 #[allow(unreachable_patterns)]
2978 match backend() {
2979 #[cfg(feature = "gpu")]
2980 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2981 _ => false,
2982 }
2983}
2984
2985pub fn dit_attention(
2986 qh: &[f32],
2987 kh: &[f32],
2988 vh: &[f32],
2989 nh: usize,
2990 nkv: usize,
2991 n: usize,
2992 hd: usize,
2993 scale: f32,
2994 out: &mut [f32],
2995) -> bool {
2996 match backend() {
2997 #[cfg(target_os = "macos")]
2998 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2999 #[cfg(feature = "gpu")]
3000 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3001 #[allow(unreachable_patterns)]
3002 _ => false,
3003 }
3004}
3005
3006#[allow(unused_variables)]
3011pub fn q4tp_matmat(
3012 model: &Arc<CmfModel>,
3013 idx: usize,
3014 xs: &[f32],
3015 b: usize,
3016 rows: usize,
3017 cols: usize,
3018 out: &mut [f32],
3019) -> bool {
3020 match backend() {
3021 #[cfg(target_os = "macos")]
3022 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3023 #[cfg(feature = "gpu")]
3024 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3025 #[allow(unreachable_patterns)]
3026 _ => false,
3027 }
3028}
3029
3030pub fn q2tp_matmat(
3033 model: &Arc<CmfModel>,
3034 idx: usize,
3035 xs: &[f32],
3036 b: usize,
3037 rows: usize,
3038 cols: usize,
3039 out: &mut [f32],
3040) -> bool {
3041 match backend() {
3042 #[cfg(target_os = "macos")]
3043 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3044 #[cfg(feature = "gpu")]
3045 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3046 #[allow(unreachable_patterns)]
3047 _ => false,
3048 }
3049}
3050
3051pub fn q2tp_affine_matmat(
3054 model: &Arc<CmfModel>,
3055 idx: usize,
3056 xs: &[f32],
3057 b: usize,
3058 rows: usize,
3059 cols: usize,
3060 out: &mut [f32],
3061) -> bool {
3062 match backend() {
3063 #[cfg(target_os = "macos")]
3064 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3065 #[cfg(feature = "gpu")]
3066 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3067 #[allow(unreachable_patterns)]
3068 _ => false,
3069 }
3070}
3071
3072pub fn q2tp_matvec(
3074 model: &Arc<CmfModel>,
3075 idx: usize,
3076 xs: &[f32],
3077 rows: usize,
3078 cols: usize,
3079 out: &mut [f32],
3080) -> bool {
3081 match backend() {
3082 #[cfg(target_os = "macos")]
3083 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
3084 #[cfg(feature = "gpu")]
3085 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
3086 #[allow(unreachable_patterns)]
3087 _ => false,
3088 }
3089}
3090
3091pub fn q2tp_affine_matvec(
3095 model: &Arc<CmfModel>,
3096 idx: usize,
3097 xs: &[f32],
3098 rows: usize,
3099 cols: usize,
3100 out: &mut [f32],
3101) -> bool {
3102 match backend() {
3103 #[cfg(target_os = "macos")]
3104 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3105 #[cfg(feature = "gpu")]
3106 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3107 #[allow(unreachable_patterns)]
3108 _ => false,
3109 }
3110}
3111
3112pub fn q4tp_matvec(
3117 model: &Arc<CmfModel>,
3118 idx: usize,
3119 xs: &[f32],
3120 rows: usize,
3121 cols: usize,
3122 out: &mut [f32],
3123) -> bool {
3124 match backend() {
3125 #[cfg(target_os = "macos")]
3126 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
3127 #[cfg(feature = "gpu")]
3128 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
3129 #[allow(unreachable_patterns)]
3130 _ => false,
3131 }
3132}
3133
3134pub fn q4t_matvec(
3140 model: &Arc<CmfModel>,
3141 idx: usize,
3142 xs: &[f32],
3143 rows: usize,
3144 cols: usize,
3145 out: &mut [f32],
3146) -> bool {
3147 match backend() {
3148 #[cfg(target_os = "macos")]
3149 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
3150 #[allow(unreachable_patterns)]
3151 _ => false,
3152 }
3153}
3154
3155pub fn q4t_matmat(
3156 model: &Arc<CmfModel>,
3157 idx: usize,
3158 xs: &[f32],
3159 b: usize,
3160 rows: usize,
3161 cols: usize,
3162 out: &mut [f32],
3163) -> bool {
3164 match backend() {
3165 #[cfg(target_os = "macos")]
3166 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
3167 #[cfg(feature = "gpu")]
3168 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
3169 #[allow(unreachable_patterns)]
3170 _ => false,
3171 }
3172}
3173
3174#[cfg(target_os = "macos")]
3176pub use crate::gpu_metal::{
3177 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
3178 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
3179};
3180
3181#[cfg(target_os = "macos")]
3183pub fn gdn_block(
3184 model: &Arc<CmfModel>,
3185 layers: &[GdnGpuLayer],
3186 states: &mut [&mut [f32]],
3187 cfg: &GdnGpuCfg,
3188 h: &mut [f32],
3189) -> bool {
3190 match backend() {
3191 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
3192 _ => false,
3193 }
3194}
3195
3196#[allow(unused_variables)]
3198pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
3199 match backend() {
3200 #[cfg(target_os = "macos")]
3201 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
3202 #[cfg(feature = "gpu")]
3203 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
3204 Backend::None => false,
3205 }
3206}
3207
3208#[allow(unused_variables)]
3210pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
3211 match backend() {
3212 #[cfg(target_os = "macos")]
3213 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
3214 #[cfg(feature = "gpu")]
3215 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
3216 Backend::None => false,
3217 }
3218}
3219
3220static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
3236static 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)];
3240
3241const GRAPH_RACE_SAMPLES: u32 = 4;
3243
3244static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
3254
3255pub fn graph_mark_unsupported() {
3260 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
3261 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
3262 }
3263}
3264
3265pub fn graph_unsupported() -> bool {
3266 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
3267}
3268
3269pub fn graph_unsupported_reset() {
3271 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
3272}
3273
3274pub fn graph_race_begin_generation() {
3275 #[cfg(feature = "gpu")]
3280 {
3281 static FLUSHED: std::sync::Once = std::sync::Once::new();
3293 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3294 if FIRST.swap(false, Ordering::Relaxed) {
3295 } else {
3297 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3298 }
3299 }
3300 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3301 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3302 return;
3303 }
3304 let (gn, cn) = (
3305 GRAPH_N[1].load(Ordering::Relaxed),
3306 GRAPH_N[0].load(Ordering::Relaxed),
3307 );
3308 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3309 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3310 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3311 let verdict = if g_avg < c_avg { 1 } else { 2 };
3312 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3313 tracing::info!(
3314 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3315 g_avg as f64 / 1e6,
3316 c_avg as f64 / 1e6,
3317 if verdict == 1 { "graph" } else { "normal path" }
3318 );
3319 return;
3320 }
3321 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3322 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3323}
3324
3325pub fn graph_race_use_graph(trusted: bool) -> bool {
3329 if trusted {
3330 return true;
3331 }
3332 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3333 1 => true,
3334 2 => false,
3335 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3336 }
3337}
3338
3339pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3344 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3345 return false;
3346 }
3347 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3348 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3349 if !first || cn == 0 {
3350 return false;
3351 }
3352 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3353 let ns = dur.as_nanos() as u64;
3354 if ns > 1_000_000_000 && ns > 4 * c_avg {
3355 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3356 tracing::info!(
3357 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3358 ns as f64 / 1e6,
3359 c_avg as f64 / 1e6
3360 );
3361 return true;
3362 }
3363 false
3364}
3365
3366pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3370 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3371 return;
3372 }
3373 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3374 if tok == 0 {
3375 return;
3376 }
3377 let i = used_graph as usize;
3378 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3379 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3380}
3381
3382pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3392 #[inline]
3393 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3394 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3395 for c in chunks.chunks_exact(8) {
3396 h ^= u64::from_le_bytes(c.try_into().unwrap());
3397 h = h.wrapping_mul(0x100_0000_01b3);
3398 }
3399 for &b in tail {
3400 h ^= b as u64;
3401 h = h.wrapping_mul(0x100_0000_01b3);
3402 }
3403 h
3404 }
3405 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3406 if data.len() <= 4096 {
3407 return fnv(h, data);
3408 }
3409 let step = (data.len() - 64) / 63;
3410 for i in 0..64 {
3411 h = fnv(h, &data[i * step..i * step + 64]);
3412 }
3413 h
3414}
3415
3416pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3419 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3420 fp_bytes(bytes)
3421}
3422
3423#[cfg(test)]
3424mod fp_tests {
3425 use super::fp_bytes;
3426
3427 #[test]
3432 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3433 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3435 let h0 = fp_bytes(&base);
3436 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3437 let mut dense = base.clone();
3440 for b in dense.iter_mut() {
3441 *b = b.wrapping_add(1);
3442 }
3443 assert_ne!(
3444 h0,
3445 fp_bytes(&dense),
3446 "a fully different tensor slipped through"
3447 );
3448 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3451 let mut small = vec![3u8; 4096];
3454 let hs = fp_bytes(&small);
3455 small[2048] ^= 1;
3456 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3457 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3459 let v = vec![9u8; n];
3460 let _ = fp_bytes(&v); }
3462 }
3463}
3464
3465pub fn bake_release() {
3469 #[cfg(feature = "gpu")]
3470 crate::gpu_wgpu::bake_release();
3471}
3472
3473pub fn bake_precision_strict(on: bool) {
3477 #[cfg(feature = "gpu")]
3478 crate::gpu_wgpu::bake_precision_strict(on);
3479 #[cfg(not(feature = "gpu"))]
3480 let _ = on;
3481}
3482
3483pub fn hostprof_encode_done(t0: std::time::Instant) {
3489 use std::sync::atomic::{AtomicU64, Ordering};
3490 static ENC: AtomicU64 = AtomicU64::new(0);
3491 static N: AtomicU64 = AtomicU64::new(0);
3492 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3493 return;
3494 }
3495 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3496 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3497 if n % 100 == 0 {
3498 eprintln!(
3499 "hostprof: encode {:.2} ms/token over {n} tokens",
3500 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3501 );
3502 }
3503}
3504
3505pub fn hostprof_total(t0: std::time::Instant) {
3506 use std::sync::atomic::{AtomicU64, Ordering};
3507 static TOT: AtomicU64 = AtomicU64::new(0);
3508 static N: AtomicU64 = AtomicU64::new(0);
3509 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3510 return;
3511 }
3512 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3513 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3514 if n % 100 == 0 {
3515 eprintln!(
3516 "hostprof: total {:.2} ms/token over {n} tokens",
3517 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3518 );
3519 }
3520}
3521
3522pub fn stageprof(stage: u32, dt: std::time::Duration) {
3526 use std::sync::atomic::{AtomicU64, Ordering};
3527 static NS: [AtomicU64; 4] = [
3528 AtomicU64::new(0),
3529 AtomicU64::new(0),
3530 AtomicU64::new(0),
3531 AtomicU64::new(0),
3532 ];
3533 static N: AtomicU64 = AtomicU64::new(0);
3534 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3535 return;
3536 }
3537 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3538 if stage == 1 {
3539 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3540 if n % 200 == 0 {
3541 eprintln!(
3542 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3543 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3544 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3545 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3546 );
3547 }
3548 }
3549}
3550
3551pub fn weight_bytes_dispatched() -> u64 {
3554 let mut total = 0u64;
3555 #[cfg(target_os = "macos")]
3556 {
3557 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3558 }
3559 #[cfg(feature = "gpu")]
3560 {
3561 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3562 }
3563 total
3564}
3565
3566pub fn weight_bytes_by() -> [u64; 6] {
3569 #[cfg(target_os = "macos")]
3570 {
3571 let mut o = [0u64; 6];
3572 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3573 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3574 }
3575 return o;
3576 }
3577 #[allow(unreachable_code)]
3578 [0; 6]
3579}
3580
3581#[cfg(test)]
3582mod probe_warmup_tests {
3583 use super::*;
3584 use std::time::Duration;
3585
3586 fn ms(v: f64) -> Duration {
3587 Duration::from_nanos((v * 1e6) as u64)
3588 }
3589
3590 #[test]
3595 fn one_cold_first_sample_does_not_lose_the_class() {
3596 let p = Probe::new();
3597 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3599 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3600 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3601 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3602 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3603 assert_eq!(
3604 p.state.load(Ordering::Relaxed),
3605 1,
3606 "the device is 3x faster once warm and must win"
3607 );
3608 }
3609
3610 #[test]
3614 fn the_warmup_is_spent_once_and_never_underflows() {
3615 let p = Probe::new();
3616 for _ in 0..8 {
3617 probe_record_into(&p, "matmat", None, true, ms(10.0));
3618 }
3619 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3620 assert_eq!(
3621 p.gpu_n.load(Ordering::Relaxed),
3622 7,
3623 "one sample burned, the rest counted"
3624 );
3625 }
3626
3627 #[test]
3633 fn a_class_whose_device_always_declines_settles_on_the_host() {
3634 let _probe_guard = probe_test_guard();
3635 let c = OpClass::MatmatWide;
3639 let p = &PROBES[c as usize];
3640 p.state.store(0, Ordering::Relaxed);
3641 p.declines.store(0, Ordering::Relaxed);
3642 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3643 probe_note_decline(c);
3644 }
3645 assert_eq!(
3646 p.state.load(Ordering::Relaxed),
3647 0,
3648 "one short of the limit is still a question, not an answer"
3649 );
3650 probe_note_decline(c);
3651 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3652 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3653 p.state.store(0, Ordering::Relaxed);
3654 p.declines.store(0, Ordering::Relaxed);
3655 }
3656
3657 #[test]
3660 fn a_slow_device_still_loses_after_the_warmup() {
3661 let p = Probe::new();
3662 for _ in 0..4 {
3663 probe_record_into(&p, "matvec", None, true, ms(40.0));
3664 }
3665 for _ in 0..4 {
3666 probe_record_into(&p, "matvec", None, false, ms(2.0));
3667 }
3668 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3669 }
3670}
3671
3672pub(crate) struct ImageStageGuard {
3675 #[cfg(target_os = "macos")]
3676 metal: Option<crate::gpu_metal::ImageStageGuard>,
3677 #[cfg(feature = "gpu")]
3678 wgpu: crate::gpu_wgpu::ImageStageGuard,
3679}
3680
3681pub(crate) fn image_stage_scope() -> ImageStageGuard {
3682 ImageStageGuard {
3683 #[cfg(target_os = "macos")]
3684 metal: if matches!(backend(), Backend::Metal) {
3685 Some(crate::gpu_metal::image_stage_scope())
3686 } else {
3687 None
3688 },
3689 #[cfg(feature = "gpu")]
3690 wgpu: crate::gpu_wgpu::image_stage_scope(),
3691 }
3692}
3693
3694impl ImageStageGuard {
3695 pub(crate) fn track_model(&mut self, uid: u64) {
3696 #[cfg(target_os = "macos")]
3697 if let Some(metal) = &mut self.metal {
3698 metal.track_model(uid);
3699 }
3700 #[cfg(feature = "gpu")]
3701 self.wgpu.track_model(uid);
3702 #[cfg(not(target_os = "macos"))]
3703 let _ = uid;
3704 }
3705}
3706
3707#[derive(Clone, Copy)]
3731pub struct ZBlockRef<'a> {
3732 pub wq: usize,
3734 pub wk: usize,
3735 pub wv: usize,
3736 pub wo: usize,
3737 pub w1: usize,
3740 pub w3: usize,
3741 pub w2: usize,
3742 pub norm1: &'a [f32],
3744 pub norm2: &'a [f32],
3745 pub ffn_norm1: &'a [f32],
3747 pub ffn_norm2: &'a [f32],
3748 pub norm_q: &'a [f32],
3750 pub norm_k: &'a [f32],
3751}
3752
3753#[derive(Clone, Copy, Debug, PartialEq)]
3757pub struct ZGeom {
3758 pub hidden: usize,
3759 pub nh: usize,
3760 pub hd: usize,
3761 pub inter: usize,
3762 pub eps: f32,
3763 pub final_eps: f32,
3764 pub patch_dim: usize,
3765}
3766
3767pub struct ZPrepareArgs<'a> {
3771 pub model: &'a Arc<CmfModel>,
3772 pub geom: ZGeom,
3773 pub key: u64,
3776 pub n_img: usize,
3779 pub n_img_p: usize,
3780 pub n_cap_p: usize,
3781 pub grid: (usize, usize),
3784 pub cap: &'a [f32],
3786 pub rope_img: (&'a [f32], &'a [f32]),
3789 pub rope_joint: (&'a [f32], &'a [f32]),
3791 pub x_emb_w: &'a [f32],
3794 pub x_emb_b: &'a [f32],
3795 pub x_pad: &'a [f32],
3796 pub final_w: &'a [f32],
3798 pub final_b: &'a [f32],
3799 pub noise_refiner: &'a [ZBlockRef<'a>],
3801 pub layers: &'a [ZBlockRef<'a>],
3802 pub mods_all: Option<&'a [f32]>,
3808 pub final_scale_all: Option<&'a [f32]>,
3809 pub neg: Option<ZNegArgs<'a>>,
3815}
3816
3817pub struct ZNegArgs<'a> {
3821 pub cap: &'a [f32],
3823 pub n_cap_p: usize,
3824 pub rope_img: (&'a [f32], &'a [f32]),
3826 pub rope_joint: (&'a [f32], &'a [f32]),
3828}
3829
3830pub struct ZStepArgs<'a> {
3832 pub key: u64,
3835 pub step: usize,
3838 pub x_tok: &'a [f32],
3842 pub mods: &'a [f32],
3846 pub final_scale: &'a [f32],
3848 pub out: &'a mut [f32],
3851 pub out_neg: Option<&'a mut [f32]>,
3854}
3855
3856#[allow(unused_variables)]
3860pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
3861 match backend() {
3862 #[cfg(target_os = "macos")]
3863 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
3864 #[cfg(feature = "gpu")]
3865 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
3866 #[allow(unreachable_patterns)]
3867 _ => false,
3868 }
3869}
3870
3871#[allow(unused_variables)]
3875pub fn zimage_step(a: &mut ZStepArgs) -> bool {
3876 match backend() {
3877 #[cfg(target_os = "macos")]
3878 Backend::Metal => crate::gpu_metal::zimage::step(a),
3879 #[cfg(feature = "gpu")]
3880 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
3881 #[allow(unreachable_patterns)]
3882 _ => false,
3883 }
3884}
3885
3886#[allow(unused_variables)]
3891pub fn zimage_preload(
3892 model: &Arc<CmfModel>,
3893 geom: &ZGeom,
3894 noise_refiner: &[ZBlockRef],
3895 layers: &[ZBlockRef],
3896 context_refiner: &[ZBlockRef],
3897) -> bool {
3898 match backend() {
3899 #[cfg(target_os = "macos")]
3900 Backend::Metal => {
3901 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
3902 }
3903 #[cfg(feature = "gpu")]
3904 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
3905 #[allow(unreachable_patterns)]
3906 _ => false,
3907 }
3908}
3909
3910pub fn zimage_flush_pipelines() {
3914 #[cfg(feature = "gpu")]
3915 if matches!(backend(), Backend::Wgpu) {
3916 crate::gpu_wgpu::pipeline_cache_flush();
3917 }
3918}
3919
3920pub fn zimage_warmup() -> bool {
3925 match backend() {
3926 #[cfg(target_os = "macos")]
3927 Backend::Metal => crate::gpu_metal::zimage::warmup(),
3928 #[cfg(feature = "gpu")]
3929 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
3930 #[allow(unreachable_patterns)]
3931 _ => false,
3932 }
3933}
3934
3935#[allow(unused_variables)]
3939pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
3940 match backend() {
3941 #[cfg(target_os = "macos")]
3942 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
3943 #[cfg(feature = "gpu")]
3944 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
3945 #[allow(unreachable_patterns)]
3946 _ => false,
3947 }
3948}
3949
3950pub fn zimage_release_dit() {
3953 #[cfg(target_os = "macos")]
3954 crate::gpu_metal::zimage::release_dit();
3955 #[cfg(feature = "gpu")]
3956 crate::gpu_wgpu::zimage::release_dit();
3957}
3958
3959pub fn zimage_release() {
3964 #[cfg(target_os = "macos")]
3965 crate::gpu_metal::zimage::release();
3966 #[cfg(feature = "gpu")]
3967 crate::gpu_wgpu::zimage::release();
3968}
3969
3970#[allow(unused_variables)]
3976pub fn zimage_refine_caption(
3977 model: &Arc<CmfModel>,
3978 geom: &ZGeom,
3979 blocks: &[ZBlockRef],
3980 rope_cap: (&[f32], &[f32]),
3981 cap: &mut [f32],
3982) -> bool {
3983 match backend() {
3984 #[cfg(target_os = "macos")]
3985 Backend::Metal => {
3986 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
3987 }
3988 #[cfg(feature = "gpu")]
3989 Backend::Wgpu => {
3990 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
3991 }
3992 #[allow(unreachable_patterns)]
3993 _ => false,
3994 }
3995}
3996
3997#[allow(unused_variables)]
4003pub fn vae_decode_chain(
4004 a: &crate::vae::VaeChainArgs,
4005 z: &[f32],
4006 h: usize,
4007 w: usize,
4008 out: &mut [f32],
4009) -> bool {
4010 match backend() {
4011 #[cfg(target_os = "macos")]
4012 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
4013 #[cfg(feature = "gpu")]
4014 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
4015 #[allow(unreachable_patterns)]
4016 _ => false,
4017 }
4018}