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 fn probe_set_device(label: &str) {
63 let _ = DEVICE_LABEL.set(label.to_string());
64}
65
66fn device_label() -> &'static str {
67 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
68}
69
70static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
71
72static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
81
82pub fn set_cache_dir(dir: std::path::PathBuf) {
84 let _ = CACHE_DIR.set(dir);
85}
86
87pub fn cache_dir_pub() -> std::path::PathBuf {
89 cache_dir()
90}
91
92fn cache_dir() -> std::path::PathBuf {
93 if let Some(d) = CACHE_DIR.get() {
94 return d.clone();
95 }
96 match std::env::var_os("TMPDIR") {
97 Some(t) => std::path::PathBuf::from(t),
98 None => std::env::temp_dir(),
99 }
100}
101
102fn probe_cache_path() -> Option<std::path::PathBuf> {
105 match std::env::var("CMF_PROBE_CACHE") {
106 Ok(v) if v == "0" => None,
107 Ok(v) => Some(std::path::PathBuf::from(v)),
108 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
109 }
110}
111
112fn probe_cache_key_named(class: &str) -> String {
116 format!(
117 "{}\t{}\t{}",
118 env!("CARGO_PKG_VERSION"),
119 device_label(),
120 class
121 )
122}
123
124const CLASS_NAMES: [&str; 7] = [
125 "ffn",
126 "matvec",
127 "matmat",
128 "qkv-batch",
129 "matmat-wide",
130 "lm-head",
131 "gemm-nt",
132];
133
134fn probe_cache_load() {
143 static ONCE: std::sync::Once = std::sync::Once::new();
144 ONCE.call_once(|| {
145 let Some(path) = probe_cache_path() else {
146 return;
147 };
148 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
153 return;
154 }
155 let Ok(text) = std::fs::read_to_string(&path) else {
156 return;
157 };
158 probe_cache_adopt(&text);
159 });
160}
161
162fn probe_cache_adopt(text: &str) {
166 for line in text.lines() {
167 let Some((key, verdict)) = line.rsplit_once('\t') else {
168 continue;
169 };
170 let winner = match verdict.trim() {
171 "gpu" => 1u8,
172 "cpu" => 2u8,
173 _ => continue,
174 };
175 for (i, name) in CLASS_NAMES.iter().enumerate() {
176 if probe_cache_key_named(name) == key {
177 let _ = PROBES[i].state.compare_exchange(
178 0,
179 winner,
180 Ordering::Relaxed,
181 Ordering::Relaxed,
182 );
183 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
184 }
185 }
186 }
187}
188
189fn probe_cache_store(c: OpClass, winner: u8) {
192 let Some(path) = probe_cache_path() else {
193 return;
194 };
195 let line = format!(
196 "{}\t{}\n",
197 probe_cache_key_named(CLASS_NAMES[c as usize]),
198 if winner == 1 { "gpu" } else { "cpu" }
199 );
200 use std::io::Write;
201 if let Ok(mut f) = std::fs::OpenOptions::new()
202 .create(true)
203 .append(true)
204 .open(&path)
205 {
206 let _ = f.write_all(line.as_bytes());
207 }
208}
209
210pub fn cold_epoch() -> u64 {
216 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
217}
218static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
219
220pub(crate) fn probe_note_cold() {
221 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
222 PROBE_COLD.with(|c| c.set(true));
223}
224
225pub(crate) fn probe_was_cold() -> bool {
229 PROBE_COLD.with(|c| c.get())
230}
231
232pub fn set_layer(l: i64) {
234 CUR_LAYER.with(|c| c.set(l));
235}
236
237pub fn cur_layer() -> i64 {
239 CUR_LAYER.with(|c| c.get())
240}
241
242pub fn automatic_layer_prefix(
245 model: &Arc<CmfModel>,
246 num_layers: usize,
247 physical_layers: usize,
248) -> Option<usize> {
249 match backend() {
250 #[cfg(feature = "gpu")]
251 Backend::Wgpu => {
252 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
253 }
254 _ => None,
255 }
256}
257
258fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
261 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
262 R.get_or_init(|| {
263 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
264 let mut v = Vec::new();
265 for part in s.split(',') {
266 let part = part.trim();
267 match part.split_once('-') {
268 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
269 None => {
270 let x: i64 = part.parse().ok()?;
271 v.push((x, x));
272 }
273 }
274 }
275 Some(v)
276 })
277}
278
279fn layer_allowed() -> bool {
280 match layer_ranges() {
281 None => true,
282 Some(ranges) => {
283 let cur = CUR_LAYER.with(|c| c.get());
284 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
285 }
286 }
287}
288
289pub fn enabled_here() -> bool {
293 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
294}
295
296pub fn q2tp_gpu_opt_in() -> bool {
302 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
303}
304
305#[derive(Clone, Copy)]
317pub enum OpClass {
318 Ffn = 0,
320 Matvec = 1,
322 Matmat = 2,
324 Batch = 3,
326 MatmatWide = 4,
332 MatvecHead = 5,
339 GemmNt = 6,
346}
347
348pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
352 if rows * cols >= 67_108_864 {
353 OpClass::MatvecHead
354 } else {
355 OpClass::Matvec
356 }
357}
358
359pub enum ProbeArm {
361 Gpu,
363 CpuTimed,
365 Cpu,
367}
368
369const PROBE_SAMPLES: u32 = 6;
371
372const PROBE_DECLINE_LIMIT: u32 = 16;
376
377const PROBE_WARMUP: u32 = 1;
379
380struct Probe {
381 state: AtomicU8,
383 flip: AtomicU32,
384 gpu_ns: AtomicU64,
385 gpu_n: AtomicU32,
386 declines: AtomicU32,
396 gpu_burn: AtomicU32,
409 cpu_ns: AtomicU64,
410 cpu_n: AtomicU32,
411 gpu_min: AtomicU64,
418 cpu_min: AtomicU64,
419}
420
421impl Probe {
422 const fn new() -> Self {
423 Self {
424 state: AtomicU8::new(0),
425 flip: AtomicU32::new(0),
426 gpu_ns: AtomicU64::new(0),
427 gpu_n: AtomicU32::new(0),
428 declines: AtomicU32::new(0),
429 gpu_burn: AtomicU32::new(PROBE_WARMUP),
430 cpu_ns: AtomicU64::new(0),
431 cpu_n: AtomicU32::new(0),
432 gpu_min: AtomicU64::new(u64::MAX),
433 cpu_min: AtomicU64::new(u64::MAX),
434 }
435 }
436}
437
438static PROBES: [Probe; 7] = [
439 Probe::new(),
440 Probe::new(),
441 Probe::new(),
442 Probe::new(),
443 Probe::new(),
444 Probe::new(),
445 Probe::new(),
446];
447
448static TRUST_GPU: AtomicBool = AtomicBool::new(false);
455
456pub fn trust_gpu() -> GpuTrust {
458 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
459 GpuTrust(was)
460}
461
462pub struct GpuTrust(bool);
463
464impl Drop for GpuTrust {
465 fn drop(&mut self) {
466 TRUST_GPU.store(self.0, Ordering::Relaxed);
467 }
468}
469
470fn probe_on_for(c: OpClass) -> bool {
471 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
477 return false;
478 }
479 probe_on()
480}
481
482fn probe_on() -> bool {
483 static ON: OnceLock<bool> = OnceLock::new();
484 *ON.get_or_init(|| {
485 std::env::var("CMF_GPU_PROBE")
486 .map(|v| v != "0" && v != "off")
487 .unwrap_or(true)
488 })
489}
490
491pub fn q1_force() -> bool {
496 #[cfg(target_os = "macos")]
497 {
498 backend() == Backend::Metal
499 }
500 #[cfg(not(target_os = "macos"))]
501 {
502 false
503 }
504}
505
506pub fn fused_block_trusted() -> bool {
525 #[cfg(target_os = "macos")]
526 if backend() == Backend::Metal {
527 return true;
528 }
529 wgpu_graph_default()
530}
531
532pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
544 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
545 {
546 return crate::gpu_wgpu::weight_is_resident(model, idx);
547 }
548 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
549 {
550 let _ = (model, idx);
551 true
552 }
553}
554
555pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
556 if !weights_resident && probe_deciding(c) {
557 return ProbeArm::Gpu;
558 }
559 probe_arm(c)
560}
561
562pub fn probe_arm(c: OpClass) -> ProbeArm {
563 PROBE_COLD.with(|f| f.set(false));
568 if !probe_on_for(c) {
569 return ProbeArm::Gpu;
570 }
571 probe_cache_load();
572 let p = &PROBES[c as usize];
573 match p.state.load(Ordering::Relaxed) {
574 1 => ProbeArm::Gpu,
575 2 => ProbeArm::Cpu,
576 _ => {
577 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
578 ProbeArm::Gpu
579 } else {
580 ProbeArm::CpuTimed
581 }
582 }
583 }
584}
585
586pub fn probe_note_decline(c: OpClass) {
590 let p = &PROBES[c as usize];
591 if p.state.load(Ordering::Relaxed) != 0 {
592 return;
593 }
594 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
595 if n >= PROBE_DECLINE_LIMIT
596 && p.state
597 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
598 .is_ok()
599 {
600 tracing::info!(
601 "gpu probe [{}]: device declined {n} times → cpu",
602 CLASS_NAMES[c as usize]
603 );
604 }
605}
606
607pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
610 probe_record_into(
611 &PROBES[c as usize],
612 CLASS_NAMES[c as usize],
613 Some(c),
614 gpu,
615 dur,
616 )
617}
618
619fn probe_record_into(
622 p: &Probe,
623 class_name: &str,
624 cache: Option<OpClass>,
625 gpu: bool,
626 dur: std::time::Duration,
627) {
628 if p.state.load(Ordering::Relaxed) != 0 {
629 return;
630 }
631 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
632 return; }
634 if gpu {
635 let left = p.gpu_burn.load(Ordering::Relaxed);
639 if left > 0 {
640 p.gpu_burn.store(left - 1, Ordering::Relaxed);
641 return; }
643 }
644 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
645 if gpu {
646 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
647 p.gpu_n.fetch_add(1, Ordering::Relaxed);
648 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
649 } else {
650 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
651 p.cpu_n.fetch_add(1, Ordering::Relaxed);
652 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
653 }
654 let (gn, cn) = (
655 p.gpu_n.load(Ordering::Relaxed),
656 p.cpu_n.load(Ordering::Relaxed),
657 );
658 if gn >= 2 && cn >= 2 {
659 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
663 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
664 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
674 return;
675 }
676 let winner = if g <= cp { 1 } else { 2 };
677 if p.state
678 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
679 .is_ok()
680 {
681 tracing::info!(
682 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
683 class_name,
684 g / 1e6,
685 cp / 1e6,
686 if winner == 1 { "gpu" } else { "cpu" },
687 );
688 if let Some(c) = cache {
689 probe_cache_store(c, winner);
690 }
691 }
692 }
693}
694
695pub fn probe_deciding(c: OpClass) -> bool {
698 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
699}
700
701#[allow(unused_variables)]
711pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
712 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
713 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
714 let resident = match backend() {
715 #[cfg(target_os = "macos")]
716 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
717 #[cfg(feature = "gpu")]
718 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
719 Backend::None => false,
720 };
721 if !resident && may_upload {
722 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
723 }
724 resident
725}
726
727#[cfg(test)]
729pub(crate) fn probe_reset() {
730 for p in &PROBES {
731 p.state.store(0, Ordering::Relaxed);
732 p.flip.store(0, Ordering::Relaxed);
733 p.gpu_ns.store(0, Ordering::Relaxed);
734 p.gpu_n.store(0, Ordering::Relaxed);
735 p.cpu_ns.store(0, Ordering::Relaxed);
736 p.cpu_n.store(0, Ordering::Relaxed);
737 }
738}
739
740#[cfg(test)]
744static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
745
746#[cfg(test)]
747fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
748 PROBE_TEST_LOCK
749 .lock()
750 .unwrap_or_else(std::sync::PoisonError::into_inner)
751}
752
753#[cfg(test)]
754mod probe_tests {
755 use super::*;
756 use std::time::Duration;
757
758 #[test]
761 fn probe_alternates_discards_cold_and_decides() {
762 let _probe_guard = probe_test_guard();
763 probe_reset();
764 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
766 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
767
768 probe_note_cold();
772 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
773 for _ in 0..PROBE_SAMPLES {
774 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
775 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
776 }
777 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
778
779 for _ in 0..PROBE_SAMPLES {
781 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
782 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
783 }
784 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
785
786 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
788 CPU_ONLY.with(|c| assert!(!c.get()));
789 cpu_scope(|| {
790 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
791 CPU_ONLY.with(|c| assert!(c.get()));
792 });
793 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
794 CPU_ONLY.with(|c| assert!(!c.get()));
795 probe_reset();
796 }
797
798 #[test]
799 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
800 let _probe_guard = probe_test_guard();
801 let mine = probe_cache_key_named("gemm-nt");
813 let state = || {
814 PROBES[OpClass::GemmNt as usize]
815 .state
816 .load(Ordering::Relaxed)
817 };
818
819 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
821 assert_eq!(state(), 0);
822 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
824 assert_ne!(older, mine);
825 probe_cache_adopt(&format!("{older}\tgpu\n"));
826 assert_eq!(state(), 0);
827 probe_cache_adopt(&format!("{mine}\tcpu\n"));
829 assert_eq!(state(), 2);
830
831 PROBES[OpClass::GemmNt as usize]
832 .state
833 .store(0, Ordering::Relaxed);
834 }
835}
836
837pub const GPU_MIN_ROWS: usize = 65_536;
840
841pub fn min_rows() -> usize {
848 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
849 .ok()
850 .and_then(|v| v.parse().ok())
851 {
852 return v;
853 }
854 if discrete() { 4096 } else { GPU_MIN_ROWS }
855}
856
857pub fn discrete() -> bool {
859 match backend() {
860 #[cfg(feature = "gpu")]
861 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
862 #[cfg(target_os = "macos")]
863 Backend::Metal => false, Backend::None => false,
865 }
866}
867
868pub struct MoeJob<'a> {
872 pub gate: (usize, usize, usize, &'a [f32]),
873 pub up: (usize, usize, usize, &'a [f32]),
874 pub down: (usize, usize, usize, &'a [f32]),
875 pub xs_gate: Vec<f32>,
876 pub xs_up: Vec<f32>,
877 pub down_col: &'a [f32],
878 pub w: f32,
879 pub q1: bool,
882 pub q4t: bool,
885 pub q4tp: bool,
889 pub gu_q2: bool,
893 pub swiglu_limit: f32,
898}
899
900pub struct BatchJob<'a> {
902 pub idx: usize,
903 pub rows: usize,
904 pub cols: usize,
905 pub row_scale: &'a [f32],
906 pub xs: Vec<f32>,
907 pub layout: BatchLayout,
911}
912
913#[derive(Clone, Copy, PartialEq, Eq, Debug)]
916pub enum BatchLayout {
917 Q8,
918 Q1,
919 Q4t,
920 Q4tp,
921}
922
923#[derive(Clone, Copy, PartialEq, Eq)]
924enum Backend {
925 None,
926 #[cfg(target_os = "macos")]
927 Metal,
928 #[cfg(feature = "gpu")]
929 Wgpu,
930}
931
932fn backend() -> Backend {
933 #[cfg(feature = "gpu")]
934 if crate::gpu_wgpu::selected() {
935 return if crate::gpu_wgpu::enabled() {
936 Backend::Wgpu
937 } else {
938 Backend::None
939 };
940 }
941 #[cfg(target_os = "macos")]
942 if crate::gpu_metal::enabled() {
943 return Backend::Metal;
944 }
945 Backend::None
946}
947
948pub fn backend_available() -> bool {
954 #[cfg(target_os = "macos")]
955 {
956 true
958 }
959 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
960 {
961 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
962 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
963 }
964 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
965 {
966 false
967 }
968}
969
970static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
976
977pub fn pause_gpu() -> GpuPause {
979 GPU_PAUSED.store(true, Ordering::Relaxed);
980 GpuPause(())
981}
982
983pub struct GpuPause(());
984
985impl Drop for GpuPause {
986 fn drop(&mut self) {
987 GPU_PAUSED.store(false, Ordering::Relaxed);
988 }
989}
990
991pub fn enabled() -> bool {
992 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
993}
994
995pub fn wgpu_active() -> bool {
1009 #[cfg(feature = "gpu")]
1010 {
1011 matches!(backend(), Backend::Wgpu)
1012 }
1013 #[cfg(not(feature = "gpu"))]
1014 {
1015 false
1016 }
1017}
1018
1019pub fn default_device() -> usize {
1026 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1027 *D.get_or_init(|| {
1028 std::env::var("CMF_GPU_ADAPTER")
1029 .ok()
1030 .and_then(|v| v.trim().parse::<usize>().ok())
1031 .unwrap_or(0)
1032 })
1033}
1034
1035thread_local! {
1036 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1037}
1038
1039pub fn current_device() -> usize {
1041 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1042}
1043
1044pub fn set_current_device(i: usize) {
1048 CUR_DEV.with(|c| c.set(Some(i)));
1049}
1050
1051pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1053 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1054 let r = f();
1055 CUR_DEV.with(|c| c.set(prev));
1056 r
1057}
1058
1059pub fn device_count() -> usize {
1062 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1063 {
1064 return crate::gpu_wgpu::adapter_count();
1065 }
1066 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1067 {
1068 usize::from(backend_available())
1069 }
1070}
1071
1072pub fn vram_budget() -> u64 {
1076 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1077 {
1078 return crate::gpu_wgpu::device_vram_budget();
1079 }
1080 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1081 {
1082 if backend_available() { u64::MAX } else { 0 }
1083 }
1084}
1085
1086pub fn resident_bytes() -> u64 {
1090 #[cfg(feature = "gpu")]
1091 {
1092 if backend() == Backend::Wgpu {
1093 return crate::gpu_wgpu::resident_bytes();
1094 }
1095 }
1096 0
1097}
1098
1099pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1103 #[cfg(feature = "gpu")]
1104 {
1105 if backend() == Backend::Wgpu {
1106 return crate::gpu_wgpu::o1_device_stats(kv_id);
1107 }
1108 }
1109 let _ = kv_id;
1110 (0, 0)
1111}
1112
1113pub fn upload_bytes() -> u64 {
1117 #[cfg(feature = "gpu")]
1118 {
1119 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1120 }
1121 #[cfg(not(feature = "gpu"))]
1122 0
1123}
1124
1125pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1129 #[cfg(feature = "gpu")]
1130 {
1131 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1132 }
1133 let _ = (block, rounds);
1134 None
1135}
1136
1137#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1149pub enum GraphPhase {
1150 Prefill,
1151 Decode,
1152}
1153
1154pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1162 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1163 Some("0") => false,
1164 Some("prefill") => phase == GraphPhase::Prefill,
1165 Some(_) => true,
1166 None => {
1167 if wgpu_graph_default() {
1168 return true;
1169 }
1170 let _ = phase;
1175 false
1176 }
1177 }
1178}
1179
1180pub fn wgpu_graph_default() -> bool {
1181 #[cfg(feature = "gpu")]
1182 {
1183 matches!(backend(), Backend::Wgpu)
1189 && (crate::gpu_wgpu::discrete_active()
1190 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1191 }
1192 #[cfg(not(feature = "gpu"))]
1193 {
1194 false
1195 }
1196}
1197
1198#[allow(clippy::too_many_arguments, unused_variables)]
1200pub fn q8_matvec_range(
1201 model: &Arc<CmfModel>,
1202 idx: usize,
1203 row0: usize,
1204 row_scale: &[f32],
1205 xs: &[f32],
1206 rows: usize,
1207 cols: usize,
1208 out: &mut [f32],
1209) -> bool {
1210 match backend() {
1211 #[cfg(target_os = "macos")]
1212 Backend::Metal => {
1213 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1214 }
1215 #[cfg(feature = "gpu")]
1216 Backend::Wgpu => {
1217 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1218 }
1219 Backend::None => false,
1220 }
1221}
1222
1223#[allow(clippy::too_many_arguments, unused_variables)]
1226#[allow(clippy::too_many_arguments)]
1230pub fn q8_matmat_2f(
1231 model: &Arc<CmfModel>,
1232 idx: usize,
1233 row_scale: &[f32],
1234 col_field: &[f32],
1235 xs: &[f32],
1236 b: usize,
1237 rows: usize,
1238 cols: usize,
1239 out: &mut [f32],
1240) -> bool {
1241 #[allow(unreachable_patterns)]
1242 match backend() {
1243 #[cfg(feature = "gpu")]
1244 Backend::Wgpu => {
1245 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1246 }
1247 _ => false,
1248 }
1249}
1250
1251pub fn q8_matmat(
1252 model: &Arc<CmfModel>,
1253 idx: usize,
1254 row_scale: &[f32],
1255 pre: &[f32],
1256 b: usize,
1257 rows: usize,
1258 cols: usize,
1259 out: &mut [f32],
1260) -> bool {
1261 match backend() {
1262 #[cfg(target_os = "macos")]
1263 Backend::Metal => {
1264 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1265 }
1266 #[cfg(feature = "gpu")]
1267 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1268 Backend::None => false,
1269 }
1270}
1271
1272#[allow(unused_variables)]
1275pub fn q1_matvec(
1276 model: &Arc<CmfModel>,
1277 idx: usize,
1278 xs: &[f32],
1279 rows: usize,
1280 cols: usize,
1281 out: &mut [f32],
1282) -> bool {
1283 match backend() {
1284 #[cfg(target_os = "macos")]
1285 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1286 #[cfg(feature = "gpu")]
1287 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1288 Backend::None => false,
1289 }
1290}
1291
1292#[allow(clippy::too_many_arguments)]
1296pub fn attn_dropin(
1297 model: &Arc<CmfModel>,
1298 kv_id: u64,
1299 layer: usize,
1300 normed: &[f32],
1301 wq_idx: usize,
1302 wk_idx: usize,
1303 wv_idx: usize,
1304 wo_idx: usize,
1305 q_norm: Option<&[f32]>,
1306 k_norm: Option<&[f32]>,
1307 late_qk_norm: bool,
1308 invf: &[f32],
1309 nh: usize,
1310 nkv: usize,
1311 hd: usize,
1312 rd: usize,
1313 hidden: usize,
1314 pos: usize,
1315 cap: usize,
1316 gemma: bool,
1317 eps: f32,
1318 cpu_k: &[Vec<f32>],
1319 cpu_v: &[Vec<f32>],
1320 out: &mut [f32],
1321) -> bool {
1322 match backend() {
1323 #[cfg(feature = "gpu")]
1324 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1325 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1326 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1327 ),
1328 #[allow(unused_variables)]
1329 _ => false,
1330 }
1331}
1332
1333#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1337pub enum GraphPrismOp {
1338 None,
1339 Forward,
1340 InverseEmbedding,
1341}
1342
1343pub struct GraphW<'a> {
1347 pub idx: usize,
1348 pub kind: u8,
1349 pub row_scale: &'a [f32],
1350 pub data: &'a [f32],
1351 pub prism: GraphPrismOp,
1352 pub affine: bool,
1353}
1354
1355pub enum GraphAttn<'a> {
1358 Full {
1359 wq: GraphW<'a>,
1360 wk: GraphW<'a>,
1361 wv: GraphW<'a>,
1362 wo: GraphW<'a>,
1363 q_norm: Option<&'a [f32]>,
1364 k_norm: Option<&'a [f32]>,
1365 late_qk_norm: bool,
1367 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1369 output_gate: bool,
1372 cpu_k: &'a [Vec<f32>],
1373 cpu_v: &'a [Vec<f32>],
1374 },
1375 Gdn {
1376 qkv: GraphW<'a>,
1377 z: GraphW<'a>,
1378 a: GraphW<'a>,
1379 b: GraphW<'a>,
1380 out: GraphW<'a>,
1381 conv1d: &'a [f32],
1382 a_log: &'a [f32],
1383 dt_bias: &'a [f32],
1384 norm: &'a [f32],
1385 nv: usize,
1386 nk: usize,
1387 dk: usize,
1388 dv: usize,
1389 kk: usize,
1390 cpu_state: &'a [f32],
1395 },
1396 ShortConv {
1403 inp: GraphW<'a>,
1405 out: GraphW<'a>,
1407 taps: &'a [f32],
1410 kernel: usize,
1411 cpu_state: &'a [f32],
1415 },
1416}
1417
1418pub struct GraphLayer<'a> {
1420 pub input_norm: &'a [f32],
1421 pub attn: GraphAttn<'a>,
1422 pub post_norm: &'a [f32],
1423 pub ffn: GraphFfn<'a>,
1424}
1425
1426pub enum GraphFfn<'a> {
1431 Dense {
1432 gate: GraphW<'a>,
1433 up: GraphW<'a>,
1434 down: GraphW<'a>,
1435 },
1436 Moe {
1437 router: GraphW<'a>,
1439 shared_gate: GraphW<'a>,
1441 experts: Vec<(usize, usize, usize)>,
1445 n_exp: usize,
1447 top_k: usize,
1448 inter: usize,
1449 norm_topk: bool,
1450 q4tp: bool,
1456 gu_q2: bool,
1460 sigmoid: bool,
1464 bias: Option<&'a [f32]>,
1467 has_shared: bool,
1471 shared_gated: bool,
1476 route_scale: f32,
1479 },
1480}
1481
1482#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1485pub enum TokenGraphOutcome {
1486 Declined,
1488 Completed,
1490 Failed,
1492}
1493
1494#[allow(clippy::too_many_arguments)]
1499pub fn forward_token_graph(
1500 model: &Arc<CmfModel>,
1501 kv_id: u64,
1502 layers: &[GraphLayer],
1503 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1506 o1_epoch: u64,
1507 invf: &[f32],
1508 h: &mut [f32],
1509 nh: usize,
1510 nkv: usize,
1511 hd: usize,
1512 attn_scale: f32,
1513 rd: usize,
1514 hidden: usize,
1515 inter: usize,
1516 position: usize,
1517 cap: usize,
1518 gemma: bool,
1519 eps: f32,
1520 lm_head: Option<(&GraphW, usize)>,
1521 final_norm: &[f32],
1522 logits: &mut Vec<f32>,
1523 loop_norm_at: &[usize],
1524 steps: usize,
1525 embed: Option<(&GraphW, usize, f32)>,
1526 ids_out: Option<&mut Vec<u32>>,
1527 layers_run: Option<&mut usize>,
1530 layer_base: usize,
1534 hidden_too: bool,
1536) -> TokenGraphOutcome {
1537 match backend() {
1538 #[cfg(feature = "gpu")]
1539 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1540 model,
1541 kv_id,
1542 layers,
1543 o1,
1544 o1_epoch,
1545 invf,
1546 h,
1547 nh,
1548 nkv,
1549 hd,
1550 attn_scale,
1551 rd,
1552 hidden,
1553 inter,
1554 position,
1555 cap,
1556 gemma,
1557 eps,
1558 lm_head,
1559 final_norm,
1560 logits,
1561 loop_norm_at,
1562 steps,
1563 embed,
1564 ids_out,
1565 layers_run,
1566 layer_base,
1567 hidden_too,
1568 ),
1569 #[allow(unused_variables)]
1570 _ => {
1571 let _ = (
1572 attn_scale,
1573 lm_head,
1574 final_norm,
1575 logits,
1576 loop_norm_at,
1577 layers_run,
1578 layer_base,
1579 hidden_too,
1580 );
1581 TokenGraphOutcome::Declined
1582 }
1583 }
1584}
1585
1586#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1590pub enum BatchGraphOutcome {
1591 Declined,
1594 Completed,
1596 Failed,
1600}
1601
1602pub struct SpecTail<'a> {
1603 pub lm: GraphW<'a>,
1604 pub lm_rows: usize,
1605 pub final_norm: &'a [f32],
1606 pub logits_out: &'a mut Vec<f32>,
1607}
1608
1609#[allow(clippy::too_many_arguments)]
1613pub fn forward_batch_graph(
1614 model: &Arc<CmfModel>,
1615 kv_id: u64,
1616 layers: &[GraphLayer],
1617 invf: &[f32],
1618 h: &mut [f32],
1619 nh: usize,
1620 nkv: usize,
1621 hd: usize,
1622 rd: usize,
1623 hidden: usize,
1624 inter: usize,
1625 positions: &[usize],
1626 cap: usize,
1627 gemma: bool,
1628 eps: f32,
1629 attn_scale: f32,
1630 k: usize,
1631 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1634 o1_epoch: u64,
1635 spec: Option<SpecTail<'_>>,
1636) -> BatchGraphOutcome {
1637 match backend() {
1638 #[cfg(feature = "gpu")]
1639 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1640 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1641 eps, attn_scale, k, o1, o1_epoch, spec,
1642 ),
1643 #[allow(unreachable_patterns)]
1644 _ => {
1645 let _ = (o1, o1_epoch, spec);
1646 BatchGraphOutcome::Declined
1647 }
1648 }
1649}
1650
1651pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1656 #[cfg(feature = "gpu")]
1657 if backend() == Backend::Wgpu {
1658 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1659 }
1660 #[allow(unreachable_code)]
1661 {
1662 let _ = (kv_id, slot, base_pos, expected_layers);
1663 false
1664 }
1665}
1666
1667pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1673 #[cfg(feature = "gpu")]
1674 if backend() == Backend::Wgpu {
1675 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1676 }
1677 #[cfg(target_os = "macos")]
1678 if backend() == Backend::Metal {
1679 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1680 return true;
1681 }
1682 false
1683}
1684
1685pub fn graph_kv_reset(_kv_id: u64) {
1687 #[cfg(feature = "gpu")]
1688 if backend() == Backend::Wgpu {
1689 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1690 }
1691}
1692
1693pub fn q1t_matvec(
1697 model: &Arc<CmfModel>,
1698 idx: usize,
1699 xs: &[f32],
1700 rows: usize,
1701 cols: usize,
1702 out: &mut [f32],
1703) -> bool {
1704 match backend() {
1705 #[cfg(target_os = "macos")]
1706 Backend::Metal => {
1707 if metal_q1t_enabled() {
1708 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1709 } else {
1710 false
1711 }
1712 }
1713 #[cfg(feature = "gpu")]
1714 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1715 Backend::None => false,
1716 }
1717}
1718
1719#[allow(unused_variables)]
1722pub fn q4b_matvec(
1723 model: &Arc<CmfModel>,
1724 idx: usize,
1725 xs: &[f32],
1726 rows: usize,
1727 cols: usize,
1728 out: &mut [f32],
1729) -> bool {
1730 match backend() {
1731 #[cfg(target_os = "macos")]
1732 Backend::Metal => false,
1733 #[cfg(feature = "gpu")]
1734 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1735 Backend::None => false,
1736 }
1737}
1738
1739pub fn q1t_matmat(
1742 model: &Arc<CmfModel>,
1743 idx: usize,
1744 xs: &[f32],
1745 b: usize,
1746 rows: usize,
1747 cols: usize,
1748 out: &mut [f32],
1749) -> bool {
1750 match backend() {
1751 #[cfg(target_os = "macos")]
1752 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1756 #[cfg(feature = "gpu")]
1757 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1758 Backend::None => false,
1759 }
1760}
1761
1762#[cfg(target_os = "macos")]
1766pub(crate) fn metal_q1t_enabled() -> bool {
1767 std::env::var("CMF_METAL_Q1T")
1768 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1769 .unwrap_or(true)
1770}
1771
1772pub fn q1_matmat(
1774 model: &Arc<CmfModel>,
1775 idx: usize,
1776 xs: &[f32],
1777 b: usize,
1778 rows: usize,
1779 cols: usize,
1780 out: &mut [f32],
1781) -> bool {
1782 match backend() {
1783 #[cfg(feature = "gpu")]
1784 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1785 #[allow(unused_variables)]
1786 _ => false,
1787 }
1788}
1789
1790static MM_KILL: AtomicBool = AtomicBool::new(false);
1795pub(crate) fn mm_killed() -> bool {
1796 MM_KILL.load(Ordering::Relaxed)
1797}
1798pub(crate) fn mm_kill() {
1799 MM_KILL.store(true, Ordering::Relaxed);
1800}
1801
1802static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1809const MM_STRIKES_TO_KILL: u32 = 3;
1810static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1817
1818pub fn mm_kill_arm(on: bool) {
1821 MM_ARMED.store(on, Ordering::Relaxed);
1822 if on {
1823 MM_STRIKES.store(0, Ordering::Relaxed);
1824 }
1825}
1826
1827pub(crate) fn mm_budget_check(
1834 what: &str,
1835 el: std::time::Duration,
1836 budget: std::time::Duration,
1837 exempt: bool,
1838) {
1839 if el <= budget {
1840 MM_STRIKES.store(0, Ordering::Relaxed);
1841 return;
1842 }
1843 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1844 return;
1845 }
1846 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1847 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1848 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1849 if !on {
1850 tracing::info!(
1851 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1852 );
1853 return;
1854 }
1855 if n >= MM_STRIKES_TO_KILL {
1856 tracing::warn!(
1857 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1858 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1859 );
1860 mm_kill();
1861 } else {
1862 tracing::info!(
1863 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1864 );
1865 }
1866}
1867
1868#[allow(unused_variables, clippy::too_many_arguments)]
1873pub fn chunk_attend(
1874 q: &[f32],
1875 k: &[&[f32]],
1876 v: &[&[f32]],
1877 b: usize,
1878 s0: usize,
1879 nh: usize,
1880 nkv: usize,
1881 hd: usize,
1882 scale: f32,
1883 out: &mut [f32],
1884) -> bool {
1885 match backend() {
1886 #[cfg(feature = "gpu")]
1887 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1888 #[allow(unreachable_patterns)]
1889 _ => false,
1890 }
1891}
1892
1893#[allow(unused_variables, clippy::too_many_arguments)]
1897pub fn q4t_qkv(
1898 model: &Arc<CmfModel>,
1899 wq: usize,
1900 wk: usize,
1901 wv: usize,
1902 xs: &[f32],
1903 b: usize,
1904 cols: usize,
1905 rq: usize,
1906 rk: usize,
1907 rv: usize,
1908 out: &mut [f32],
1909) -> bool {
1910 match backend() {
1911 #[cfg(feature = "gpu")]
1912 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1913 #[allow(unreachable_patterns)]
1914 _ => false,
1915 }
1916}
1917
1918#[allow(unused_variables, clippy::too_many_arguments)]
1920#[allow(clippy::too_many_arguments, unused_variables)]
1924pub fn q4tp_ffn_packed(
1925 model: &Arc<CmfModel>,
1926 w1: usize,
1927 w2: usize,
1928 xs: &[f32],
1929 b: usize,
1930 hidden: usize,
1931 inter: usize,
1932 bias: Option<&[f32]>,
1933 out: &mut [f32],
1934) -> bool {
1935 match backend() {
1936 #[cfg(feature = "gpu")]
1937 Backend::Wgpu => {
1938 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1939 }
1940 #[allow(unreachable_patterns)]
1941 _ => false,
1942 }
1943}
1944
1945pub fn q4tp_ffn(
1946 model: &Arc<CmfModel>,
1947 w1: usize,
1948 w3: usize,
1949 w2: usize,
1950 xs: &[f32],
1951 b: usize,
1952 hidden: usize,
1953 inter: usize,
1954 out: &mut [f32],
1955) -> bool {
1956 match backend() {
1957 #[cfg(target_os = "macos")]
1958 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1959 #[cfg(feature = "gpu")]
1960 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1961 #[allow(unreachable_patterns)]
1962 _ => false,
1963 }
1964}
1965
1966#[allow(clippy::too_many_arguments, unused_variables)]
1971pub fn q4tp_gelu_ffn(
1972 model: &Arc<CmfModel>,
1973 w_in: usize,
1974 w_out: usize,
1975 xs: &[f32],
1976 b: usize,
1977 hidden: usize,
1978 inter: usize,
1979 bias_in: &[f32],
1980 bias_out: &[f32],
1981 out: &mut [f32],
1982) -> bool {
1983 match backend() {
1984 #[cfg(feature = "gpu")]
1985 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
1986 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
1987 ),
1988 #[allow(unreachable_patterns)]
1989 _ => false,
1990 }
1991}
1992
1993pub fn q4t_ffn(
1994 model: &Arc<CmfModel>,
1995 w1: usize,
1996 w3: usize,
1997 w2: usize,
1998 xs: &[f32],
1999 b: usize,
2000 hidden: usize,
2001 inter: usize,
2002 out: &mut [f32],
2003) -> bool {
2004 match backend() {
2005 #[cfg(target_os = "macos")]
2006 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2007 #[cfg(feature = "gpu")]
2008 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2009 #[allow(unreachable_patterns)]
2010 _ => false,
2011 }
2012}
2013
2014pub struct DitBlockArgs<'a> {
2019 pub n: usize,
2020 pub hidden: usize,
2021 pub inter: usize,
2022 pub nh: usize,
2023 pub nkv: usize,
2024 pub hd: usize,
2025 pub eps: f32,
2026 pub rope_cos: &'a [f32],
2027 pub rope_sin: &'a [f32],
2028 pub norm1: &'a [f32],
2029 pub norm2: &'a [f32],
2030 pub ffn_norm1: &'a [f32],
2031 pub ffn_norm2: &'a [f32],
2032 pub norm_q: &'a [f32],
2033 pub norm_k: &'a [f32],
2034 pub s_msa: &'a [f32],
2035 pub gate_msa: &'a [f32],
2036 pub s_mlp: &'a [f32],
2037 pub gate_mlp: &'a [f32],
2038 pub wq: usize,
2039 pub wk: usize,
2040 pub wv: usize,
2041 pub wo: usize,
2042 pub w1: usize,
2043 pub w3: usize,
2044 pub w2: usize,
2045 pub q4tp: bool,
2049 pub resident_in: bool,
2052 pub resident_out: bool,
2056}
2057
2058pub fn dit_chain_supported() -> bool {
2062 #[cfg(feature = "gpu")]
2063 {
2064 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2065 }
2066 #[allow(unreachable_code)]
2067 false
2068}
2069
2070pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2073 #[cfg(feature = "gpu")]
2074 {
2075 if matches!(backend(), Backend::Wgpu) {
2076 return crate::gpu_wgpu::dit_state_fetch(_x);
2077 }
2078 }
2079 false
2080}
2081
2082#[allow(unused_variables)]
2086#[allow(unused_variables, clippy::too_many_arguments)]
2090pub fn dit_qkv(
2091 model: &Arc<CmfModel>,
2092 wq: usize,
2093 wk: usize,
2094 wv: usize,
2095 xs: &[f32],
2096 b: usize,
2097 hidden: usize,
2098 qrows: usize,
2099 kvrows: usize,
2100 q_out: &mut [f32],
2101 k_out: &mut [f32],
2102 v_out: &mut [f32],
2103) -> bool {
2104 match backend() {
2105 #[cfg(feature = "gpu")]
2106 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2107 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2108 ),
2109 #[allow(unreachable_patterns)]
2110 _ => false,
2111 }
2112}
2113
2114pub struct QwenImageAttentionArgs<'a> {
2122 pub image: &'a [f32],
2123 pub text: &'a [f32],
2124 pub image_tokens: usize,
2125 pub text_tokens: usize,
2126 pub heads: usize,
2127 pub head_dim: usize,
2128 pub image_q: usize,
2129 pub image_k: usize,
2130 pub image_v: usize,
2131 pub text_q: usize,
2132 pub text_k: usize,
2133 pub text_v: usize,
2134 pub image_out: usize,
2135 pub text_out: usize,
2136 pub image_q_norm: &'a [f32],
2137 pub image_k_norm: &'a [f32],
2138 pub text_q_norm: &'a [f32],
2139 pub text_k_norm: &'a [f32],
2140 pub image_cos: &'a [f32],
2141 pub image_sin: &'a [f32],
2142 pub text_cos: &'a [f32],
2143 pub text_sin: &'a [f32],
2144 pub image_q_bias: &'a [f32],
2145 pub image_k_bias: &'a [f32],
2146 pub image_v_bias: &'a [f32],
2147 pub text_q_bias: &'a [f32],
2148 pub text_k_bias: &'a [f32],
2149 pub text_v_bias: &'a [f32],
2150 pub image_out_bias: &'a [f32],
2151 pub text_out_bias: &'a [f32],
2152 pub image_proj: &'a mut [f32],
2153 pub text_proj: &'a mut [f32],
2154}
2155
2156#[allow(clippy::too_many_fields)]
2161pub struct QwenImageChainBlock<'a> {
2162 pub image_mod: &'a [f32],
2163 pub text_mod: &'a [f32],
2164 pub image_q: usize,
2165 pub image_k: usize,
2166 pub image_v: usize,
2167 pub text_q: usize,
2168 pub text_k: usize,
2169 pub text_v: usize,
2170 pub image_out: usize,
2171 pub text_out: usize,
2172 pub image_q_norm: &'a [f32],
2173 pub image_k_norm: &'a [f32],
2174 pub text_q_norm: &'a [f32],
2175 pub text_k_norm: &'a [f32],
2176 pub image_q_bias: &'a [f32],
2177 pub image_k_bias: &'a [f32],
2178 pub image_v_bias: &'a [f32],
2179 pub text_q_bias: &'a [f32],
2180 pub text_k_bias: &'a [f32],
2181 pub text_v_bias: &'a [f32],
2182 pub image_out_bias: &'a [f32],
2183 pub text_out_bias: &'a [f32],
2184 pub image_attn_gate: &'a [f32],
2185 pub text_attn_gate: &'a [f32],
2186 pub image_mlp_in: usize,
2187 pub image_mlp_out: usize,
2188 pub text_mlp_in: usize,
2189 pub text_mlp_out: usize,
2190 pub image_mlp_in_bias: &'a [f32],
2191 pub image_mlp_out_bias: &'a [f32],
2192 pub text_mlp_in_bias: &'a [f32],
2193 pub text_mlp_out_bias: &'a [f32],
2194}
2195
2196#[allow(clippy::too_many_fields)]
2202pub struct QwenImageBlockArgs<'a> {
2203 pub image: &'a mut [f32],
2206 pub text: &'a mut [f32],
2207 pub image_norm: &'a [f32],
2208 pub text_norm: &'a [f32],
2209 pub image_tokens: usize,
2210 pub text_tokens: usize,
2211 pub heads: usize,
2212 pub head_dim: usize,
2213 pub image_cos: &'a [f32],
2214 pub image_sin: &'a [f32],
2215 pub text_cos: &'a [f32],
2216 pub text_sin: &'a [f32],
2217 pub image_q: usize,
2218 pub image_k: usize,
2219 pub image_v: usize,
2220 pub text_q: usize,
2221 pub text_k: usize,
2222 pub text_v: usize,
2223 pub image_out: usize,
2224 pub text_out: usize,
2225 pub image_q_norm: &'a [f32],
2226 pub image_k_norm: &'a [f32],
2227 pub text_q_norm: &'a [f32],
2228 pub text_k_norm: &'a [f32],
2229 pub image_q_bias: &'a [f32],
2230 pub image_k_bias: &'a [f32],
2231 pub image_v_bias: &'a [f32],
2232 pub text_q_bias: &'a [f32],
2233 pub text_k_bias: &'a [f32],
2234 pub text_v_bias: &'a [f32],
2235 pub image_out_bias: &'a [f32],
2236 pub text_out_bias: &'a [f32],
2237 pub image_attn_gate: &'a [f32],
2238 pub text_attn_gate: &'a [f32],
2239 pub image_mlp_in: usize,
2240 pub image_mlp_out: usize,
2241 pub text_mlp_in: usize,
2242 pub text_mlp_out: usize,
2243 pub image_mlp_in_bias: &'a [f32],
2244 pub image_mlp_out_bias: &'a [f32],
2245 pub text_mlp_in_bias: &'a [f32],
2246 pub text_mlp_out_bias: &'a [f32],
2247 pub image_mlp_mod: &'a [f32],
2248 pub text_mlp_mod: &'a [f32],
2249 pub image_mlp_gate: &'a [f32],
2250 pub text_mlp_gate: &'a [f32],
2251}
2252
2253pub struct QwenImageChainArgs<'a> {
2258 pub image: &'a mut [f32],
2259 pub text: &'a mut [f32],
2260 pub image_tokens: usize,
2261 pub text_tokens: usize,
2262 pub heads: usize,
2263 pub head_dim: usize,
2264 pub image_cos: &'a [f32],
2265 pub image_sin: &'a [f32],
2266 pub text_cos: &'a [f32],
2267 pub text_sin: &'a [f32],
2268 pub blocks: &'a [QwenImageChainBlock<'a>],
2269}
2270
2271#[allow(unused_variables)]
2272pub fn qwen_image_attention(
2273 model: &Arc<CmfModel>,
2274 args: &mut QwenImageAttentionArgs<'_>,
2275) -> bool {
2276 match backend() {
2277 #[cfg(feature = "gpu")]
2278 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2279 #[allow(unreachable_patterns)]
2280 _ => false,
2281 }
2282}
2283
2284#[allow(unused_variables)]
2285pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2286 match backend() {
2287 #[cfg(feature = "gpu")]
2288 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2289 #[allow(unreachable_patterns)]
2290 _ => false,
2291 }
2292}
2293
2294#[allow(unused_variables)]
2298pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2299 match backend() {
2300 #[cfg(feature = "gpu")]
2301 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2302 #[allow(unreachable_patterns)]
2303 _ => false,
2304 }
2305}
2306
2307#[allow(unused_variables, clippy::too_many_arguments)]
2314pub fn qwen_image_mlp_inplace(
2315 model: &Arc<CmfModel>,
2316 w_in: usize,
2317 w_out: usize,
2318 data: &mut [f32],
2319 batch: usize,
2320 hidden: usize,
2321 inter: usize,
2322 bias_in: &[f32],
2323 bias_out: &[f32],
2324 modulation: &[f32],
2325 gate: &[f32],
2326) -> bool {
2327 match backend() {
2328 #[cfg(feature = "gpu")]
2329 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2330 model,
2331 w_in,
2332 w_out,
2333 data,
2334 batch,
2335 hidden,
2336 inter,
2337 bias_in,
2338 bias_out,
2339 modulation,
2340 gate,
2341 ),
2342 #[allow(unreachable_patterns)]
2343 _ => false,
2344 }
2345}
2346
2347pub fn fused_dit_block_available() -> bool {
2351 #[cfg(target_os = "macos")]
2352 {
2353 matches!(backend(), Backend::Metal) && fused_block_trusted()
2354 }
2355 #[cfg(not(target_os = "macos"))]
2356 {
2357 false
2358 }
2359}
2360
2361pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2362 dit_block_seg(model, a, &[a.n], x)
2363}
2364
2365pub fn dit_block_seg(
2369 model: &Arc<CmfModel>,
2370 a: &DitBlockArgs,
2371 segs: &[usize],
2372 x: &mut [f32],
2373) -> bool {
2374 match backend() {
2375 #[cfg(target_os = "macos")]
2376 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2377 #[cfg(feature = "gpu")]
2384 Backend::Wgpu
2385 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2386 Some("0") => false,
2387 Some(_) => true,
2388 None => crate::gpu_wgpu::discrete_active(),
2389 } =>
2390 {
2391 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2392 }
2393 #[allow(unreachable_patterns)]
2394 _ => false,
2395 }
2396}
2397
2398pub struct VaeResnetArgs<'a> {
2402 pub groups: usize,
2403 pub ic: usize,
2404 pub oc: usize,
2405 pub h: usize,
2406 pub w: usize,
2407 pub n1w: &'a [f32],
2408 pub n1b: &'a [f32],
2409 pub c1w: &'a [f32],
2410 pub c1b: &'a [f32],
2411 pub c1k: usize,
2412 pub n2w: &'a [f32],
2413 pub n2b: &'a [f32],
2414 pub c2w: &'a [f32],
2415 pub c2b: &'a [f32],
2416 pub c2k: usize,
2417 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2418}
2419
2420#[allow(unused_variables)]
2423pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2424 match backend() {
2425 #[cfg(target_os = "macos")]
2426 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2427 _ => false,
2428 }
2429}
2430
2431#[allow(unused_variables, clippy::too_many_arguments)]
2434pub fn vae_upsample_conv(
2435 w: &[f32],
2436 bias: &[f32],
2437 x: &[f32],
2438 ic: usize,
2439 oc: usize,
2440 h: usize,
2441 w_img: usize,
2442 k: usize,
2443 out: &mut [f32],
2444) -> bool {
2445 match backend() {
2446 #[cfg(target_os = "macos")]
2447 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2448 #[cfg(feature = "gpu")]
2449 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2450 #[allow(unreachable_patterns)]
2451 _ => false,
2452 }
2453}
2454
2455#[allow(unused_variables, clippy::too_many_arguments)]
2458pub fn vae_conv2d(
2459 w: &[f32],
2460 bias: &[f32],
2461 x: &[f32],
2462 ic: usize,
2463 oc: usize,
2464 h: usize,
2465 w_img: usize,
2466 k: usize,
2467 out: &mut [f32],
2468) -> bool {
2469 match backend() {
2470 #[cfg(target_os = "macos")]
2471 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2472 #[cfg(feature = "gpu")]
2473 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2474 #[allow(unreachable_patterns)]
2475 _ => false,
2476 }
2477}
2478
2479#[allow(unused_variables, clippy::too_many_arguments)]
2483#[allow(unused_variables)]
2487#[allow(clippy::too_many_arguments)]
2488#[allow(clippy::too_many_arguments, unused_variables)]
2491pub fn dit_qkv_attention(
2492 model: &Arc<CmfModel>,
2493 qkv_idx: usize,
2494 xn: &[f32],
2495 n: usize,
2496 hidden: usize,
2497 nh: usize,
2498 hd: usize,
2499 scale: f32,
2500 nr: (&[f32], &[f32], &[f32], f32),
2501 out: &mut [f32],
2502) -> bool {
2503 match backend() {
2504 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2505 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2506 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2507 ),
2508 #[allow(unreachable_patterns)]
2509 _ => false,
2510 }
2511}
2512
2513#[allow(clippy::too_many_arguments)]
2516pub fn dit_qkv_attn_out(
2517 model: &Arc<CmfModel>,
2518 qkv_idx: usize,
2519 out_idx: usize,
2520 xn: &[f32],
2521 n: usize,
2522 hidden: usize,
2523 nh: usize,
2524 hd: usize,
2525 scale: f32,
2526 nr: (&[f32], &[f32], &[f32], f32),
2527 proj: &mut [f32],
2528) -> bool {
2529 match backend() {
2530 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2531 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2532 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2533 ),
2534 #[allow(unreachable_patterns)]
2535 _ => false,
2536 }
2537}
2538
2539#[allow(clippy::too_many_arguments)]
2541pub fn vae_qkv_attn_out(
2542 model: &Arc<CmfModel>,
2543 qkv_idx: usize,
2544 out_idx: usize,
2545 xn: &[f32],
2546 n: usize,
2547 dim: usize,
2548 nh: usize,
2549 hd: usize,
2550 scale: f32,
2551 angles: &[f32],
2552 eps: f32,
2553 qkv_bias: &[f32],
2554 proj: &mut [f32],
2555) -> bool {
2556 match backend() {
2557 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2558 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2559 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2560 ),
2561 #[allow(unreachable_patterns)]
2562 _ => false,
2563 }
2564}
2565
2566#[allow(clippy::too_many_arguments)]
2567pub fn vae_attention_packed(
2568 qkv: &[f32],
2569 nh: usize,
2570 n: usize,
2571 hd: usize,
2572 scale: f32,
2573 angles: &[f32],
2574 eps: f32,
2575 out: &mut [f32],
2576) -> bool {
2577 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2578}
2579
2580#[allow(clippy::too_many_arguments)]
2581pub fn vae_attention_packed_layout(
2582 qkv: &[f32],
2583 nh: usize,
2584 n: usize,
2585 hd: usize,
2586 scale: f32,
2587 angles: &[f32],
2588 eps: f32,
2589 out: &mut [f32],
2590 layout: u32,
2591) -> bool {
2592 match backend() {
2593 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2594 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2595 qkv, nh, n, hd, scale, angles, eps, out, layout,
2596 ),
2597 #[allow(unreachable_patterns)]
2598 _ => false,
2599 }
2600}
2601
2602#[allow(clippy::too_many_arguments)]
2603pub fn dit_split_only(
2604 qkv: &[f32],
2605 nh: usize,
2606 n: usize,
2607 hd: usize,
2608 layout: u32,
2609 norm: Option<(&[f32], f32)>,
2610 out_q: &mut [f32],
2611) -> bool {
2612 match backend() {
2613 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2614 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2615 #[allow(unreachable_patterns)]
2616 _ => false,
2617 }
2618}
2619
2620pub fn gemm_nt_f32_transient(
2628 x: &[f32],
2629 w: &[f32],
2630 y: &mut [f32],
2631 n: usize,
2632 k: usize,
2633 m: usize,
2634) -> bool {
2635 match backend() {
2636 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2637 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2638 #[allow(unreachable_patterns)]
2639 _ => false,
2640 }
2641}
2642
2643pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2644 match backend() {
2645 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2646 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2647 #[allow(unreachable_patterns)]
2648 _ => false,
2649 }
2650}
2651
2652#[allow(clippy::too_many_arguments)]
2655pub fn music3_ffn(
2656 model: &std::sync::Arc<CmfModel>,
2657 idx_in: usize,
2658 idx_out: usize,
2659 h: &[f32],
2660 bias_in: &[f32],
2661 n: usize,
2662 hs: usize,
2663 inter: usize,
2664 out: &mut [f32],
2665) -> bool {
2666 match backend() {
2667 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2668 Backend::Wgpu => {
2669 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2670 }
2671 #[allow(unreachable_patterns)]
2672 _ => false,
2673 }
2674}
2675
2676#[allow(clippy::too_many_arguments)]
2680pub fn conv1d_gemm(
2681 x: &[f32],
2682 w: &[f32],
2683 ic: usize,
2684 oc: usize,
2685 n: usize,
2686 k: usize,
2687 pad: usize,
2688 dil: usize,
2689 out_n: usize,
2690 yt: &mut [f32],
2691) -> bool {
2692 match backend() {
2693 #[cfg(target_os = "macos")]
2694 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2695 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2696 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2697 #[allow(unreachable_patterns)]
2698 _ => false,
2699 }
2700}
2701
2702#[allow(clippy::too_many_arguments)]
2704pub fn vae_conv2d_coop(
2705 w: &[f32],
2706 bias: Option<&[f32]>,
2707 x: &[f32],
2708 ic: usize,
2709 oc: usize,
2710 h: usize,
2711 wi: usize,
2712 k: usize,
2713 out: &mut [f32],
2714) -> bool {
2715 match backend() {
2716 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2717 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2718 #[allow(unreachable_patterns)]
2719 _ => false,
2720 }
2721}
2722
2723pub fn dit_attention_packed(
2724 qkv: &[f32],
2725 nh: usize,
2726 n: usize,
2727 hd: usize,
2728 scale: f32,
2729 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2732 out: &mut [f32],
2733) -> bool {
2734 match backend() {
2735 #[cfg(feature = "gpu")]
2742 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2743 #[allow(unreachable_patterns)]
2744 _ => false,
2745 }
2746}
2747
2748pub fn dit_attention_packed_available() -> bool {
2756 #[allow(unreachable_patterns)]
2757 match backend() {
2758 #[cfg(feature = "gpu")]
2759 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2760 _ => false,
2761 }
2762}
2763
2764pub fn dit_attention(
2765 qh: &[f32],
2766 kh: &[f32],
2767 vh: &[f32],
2768 nh: usize,
2769 nkv: usize,
2770 n: usize,
2771 hd: usize,
2772 scale: f32,
2773 out: &mut [f32],
2774) -> bool {
2775 match backend() {
2776 #[cfg(target_os = "macos")]
2777 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2778 #[cfg(feature = "gpu")]
2779 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2780 #[allow(unreachable_patterns)]
2781 _ => false,
2782 }
2783}
2784
2785#[allow(unused_variables)]
2790pub fn q4tp_matmat(
2791 model: &Arc<CmfModel>,
2792 idx: usize,
2793 xs: &[f32],
2794 b: usize,
2795 rows: usize,
2796 cols: usize,
2797 out: &mut [f32],
2798) -> bool {
2799 match backend() {
2800 #[cfg(target_os = "macos")]
2801 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2802 #[cfg(feature = "gpu")]
2803 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2804 #[allow(unreachable_patterns)]
2805 _ => false,
2806 }
2807}
2808
2809pub fn q2tp_matmat(
2812 model: &Arc<CmfModel>,
2813 idx: usize,
2814 xs: &[f32],
2815 b: usize,
2816 rows: usize,
2817 cols: usize,
2818 out: &mut [f32],
2819) -> bool {
2820 match backend() {
2821 #[cfg(target_os = "macos")]
2822 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2823 #[cfg(feature = "gpu")]
2824 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2825 #[allow(unreachable_patterns)]
2826 _ => false,
2827 }
2828}
2829
2830pub fn q2tp_affine_matmat(
2833 model: &Arc<CmfModel>,
2834 idx: usize,
2835 xs: &[f32],
2836 b: usize,
2837 rows: usize,
2838 cols: usize,
2839 out: &mut [f32],
2840) -> bool {
2841 match backend() {
2842 #[cfg(target_os = "macos")]
2843 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2844 #[cfg(feature = "gpu")]
2845 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2846 #[allow(unreachable_patterns)]
2847 _ => false,
2848 }
2849}
2850
2851pub fn q2tp_matvec(
2853 model: &Arc<CmfModel>,
2854 idx: usize,
2855 xs: &[f32],
2856 rows: usize,
2857 cols: usize,
2858 out: &mut [f32],
2859) -> bool {
2860 match backend() {
2861 #[cfg(target_os = "macos")]
2862 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
2863 #[cfg(feature = "gpu")]
2864 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
2865 #[allow(unreachable_patterns)]
2866 _ => false,
2867 }
2868}
2869
2870pub fn q2tp_affine_matvec(
2874 model: &Arc<CmfModel>,
2875 idx: usize,
2876 xs: &[f32],
2877 rows: usize,
2878 cols: usize,
2879 out: &mut [f32],
2880) -> bool {
2881 match backend() {
2882 #[cfg(target_os = "macos")]
2883 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2884 #[cfg(feature = "gpu")]
2885 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2886 #[allow(unreachable_patterns)]
2887 _ => false,
2888 }
2889}
2890
2891pub fn q4tp_matvec(
2896 model: &Arc<CmfModel>,
2897 idx: usize,
2898 xs: &[f32],
2899 rows: usize,
2900 cols: usize,
2901 out: &mut [f32],
2902) -> bool {
2903 match backend() {
2904 #[cfg(target_os = "macos")]
2905 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2906 #[cfg(feature = "gpu")]
2907 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2908 #[allow(unreachable_patterns)]
2909 _ => false,
2910 }
2911}
2912
2913pub fn q4t_matvec(
2919 model: &Arc<CmfModel>,
2920 idx: usize,
2921 xs: &[f32],
2922 rows: usize,
2923 cols: usize,
2924 out: &mut [f32],
2925) -> bool {
2926 match backend() {
2927 #[cfg(target_os = "macos")]
2928 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2929 #[allow(unreachable_patterns)]
2930 _ => false,
2931 }
2932}
2933
2934pub fn q4t_matmat(
2935 model: &Arc<CmfModel>,
2936 idx: usize,
2937 xs: &[f32],
2938 b: usize,
2939 rows: usize,
2940 cols: usize,
2941 out: &mut [f32],
2942) -> bool {
2943 match backend() {
2944 #[cfg(target_os = "macos")]
2945 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2946 #[cfg(feature = "gpu")]
2947 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2948 #[allow(unreachable_patterns)]
2949 _ => false,
2950 }
2951}
2952
2953#[cfg(target_os = "macos")]
2955pub use crate::gpu_metal::{
2956 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2957 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2958};
2959
2960#[cfg(target_os = "macos")]
2962pub fn gdn_block(
2963 model: &Arc<CmfModel>,
2964 layers: &[GdnGpuLayer],
2965 states: &mut [&mut [f32]],
2966 cfg: &GdnGpuCfg,
2967 h: &mut [f32],
2968) -> bool {
2969 match backend() {
2970 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2971 _ => false,
2972 }
2973}
2974
2975#[allow(unused_variables)]
2977pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2978 match backend() {
2979 #[cfg(target_os = "macos")]
2980 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2981 #[cfg(feature = "gpu")]
2982 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2983 Backend::None => false,
2984 }
2985}
2986
2987#[allow(unused_variables)]
2989pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2990 match backend() {
2991 #[cfg(target_os = "macos")]
2992 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2993 #[cfg(feature = "gpu")]
2994 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2995 Backend::None => false,
2996 }
2997}
2998
2999static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
3015static 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)];
3019
3020const GRAPH_RACE_SAMPLES: u32 = 4;
3022
3023static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
3033
3034pub fn graph_mark_unsupported() {
3039 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
3040 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
3041 }
3042}
3043
3044pub fn graph_unsupported() -> bool {
3045 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
3046}
3047
3048pub fn graph_unsupported_reset() {
3050 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
3051}
3052
3053pub fn graph_race_begin_generation() {
3054 #[cfg(feature = "gpu")]
3059 {
3060 static FLUSHED: std::sync::Once = std::sync::Once::new();
3072 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3073 if FIRST.swap(false, Ordering::Relaxed) {
3074 } else {
3076 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3077 }
3078 }
3079 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3080 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3081 return;
3082 }
3083 let (gn, cn) = (
3084 GRAPH_N[1].load(Ordering::Relaxed),
3085 GRAPH_N[0].load(Ordering::Relaxed),
3086 );
3087 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3088 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3089 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3090 let verdict = if g_avg < c_avg { 1 } else { 2 };
3091 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3092 tracing::info!(
3093 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3094 g_avg as f64 / 1e6,
3095 c_avg as f64 / 1e6,
3096 if verdict == 1 { "graph" } else { "normal path" }
3097 );
3098 return;
3099 }
3100 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3101 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3102}
3103
3104pub fn graph_race_use_graph(trusted: bool) -> bool {
3108 if trusted {
3109 return true;
3110 }
3111 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3112 1 => true,
3113 2 => false,
3114 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3115 }
3116}
3117
3118pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3123 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3124 return false;
3125 }
3126 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3127 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3128 if !first || cn == 0 {
3129 return false;
3130 }
3131 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3132 let ns = dur.as_nanos() as u64;
3133 if ns > 1_000_000_000 && ns > 4 * c_avg {
3134 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3135 tracing::info!(
3136 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3137 ns as f64 / 1e6,
3138 c_avg as f64 / 1e6
3139 );
3140 return true;
3141 }
3142 false
3143}
3144
3145pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3149 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3150 return;
3151 }
3152 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3153 if tok == 0 {
3154 return;
3155 }
3156 let i = used_graph as usize;
3157 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3158 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3159}
3160
3161pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3171 #[inline]
3172 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3173 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3174 for c in chunks.chunks_exact(8) {
3175 h ^= u64::from_le_bytes(c.try_into().unwrap());
3176 h = h.wrapping_mul(0x100_0000_01b3);
3177 }
3178 for &b in tail {
3179 h ^= b as u64;
3180 h = h.wrapping_mul(0x100_0000_01b3);
3181 }
3182 h
3183 }
3184 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3185 if data.len() <= 4096 {
3186 return fnv(h, data);
3187 }
3188 let step = (data.len() - 64) / 63;
3189 for i in 0..64 {
3190 h = fnv(h, &data[i * step..i * step + 64]);
3191 }
3192 h
3193}
3194
3195pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3198 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3199 fp_bytes(bytes)
3200}
3201
3202#[cfg(test)]
3203mod fp_tests {
3204 use super::fp_bytes;
3205
3206 #[test]
3211 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3212 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3214 let h0 = fp_bytes(&base);
3215 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3216 let mut dense = base.clone();
3219 for b in dense.iter_mut() {
3220 *b = b.wrapping_add(1);
3221 }
3222 assert_ne!(
3223 h0,
3224 fp_bytes(&dense),
3225 "a fully different tensor slipped through"
3226 );
3227 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3230 let mut small = vec![3u8; 4096];
3233 let hs = fp_bytes(&small);
3234 small[2048] ^= 1;
3235 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3236 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3238 let v = vec![9u8; n];
3239 let _ = fp_bytes(&v); }
3241 }
3242}
3243
3244pub fn bake_release() {
3248 #[cfg(feature = "gpu")]
3249 crate::gpu_wgpu::bake_release();
3250}
3251
3252pub fn bake_precision_strict(on: bool) {
3256 #[cfg(feature = "gpu")]
3257 crate::gpu_wgpu::bake_precision_strict(on);
3258 #[cfg(not(feature = "gpu"))]
3259 let _ = on;
3260}
3261
3262pub fn hostprof_encode_done(t0: std::time::Instant) {
3268 use std::sync::atomic::{AtomicU64, Ordering};
3269 static ENC: AtomicU64 = AtomicU64::new(0);
3270 static N: AtomicU64 = AtomicU64::new(0);
3271 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3272 return;
3273 }
3274 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3275 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3276 if n % 100 == 0 {
3277 eprintln!(
3278 "hostprof: encode {:.2} ms/token over {n} tokens",
3279 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3280 );
3281 }
3282}
3283
3284pub fn hostprof_total(t0: std::time::Instant) {
3285 use std::sync::atomic::{AtomicU64, Ordering};
3286 static TOT: AtomicU64 = AtomicU64::new(0);
3287 static N: AtomicU64 = AtomicU64::new(0);
3288 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3289 return;
3290 }
3291 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3292 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3293 if n % 100 == 0 {
3294 eprintln!(
3295 "hostprof: total {:.2} ms/token over {n} tokens",
3296 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3297 );
3298 }
3299}
3300
3301pub fn stageprof(stage: u32, dt: std::time::Duration) {
3305 use std::sync::atomic::{AtomicU64, Ordering};
3306 static NS: [AtomicU64; 4] = [
3307 AtomicU64::new(0),
3308 AtomicU64::new(0),
3309 AtomicU64::new(0),
3310 AtomicU64::new(0),
3311 ];
3312 static N: AtomicU64 = AtomicU64::new(0);
3313 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3314 return;
3315 }
3316 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3317 if stage == 1 {
3318 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3319 if n % 200 == 0 {
3320 eprintln!(
3321 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3322 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3323 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3324 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3325 );
3326 }
3327 }
3328}
3329
3330pub fn weight_bytes_dispatched() -> u64 {
3333 let mut total = 0u64;
3334 #[cfg(target_os = "macos")]
3335 {
3336 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3337 }
3338 #[cfg(feature = "gpu")]
3339 {
3340 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3341 }
3342 total
3343}
3344
3345pub fn weight_bytes_by() -> [u64; 6] {
3348 #[cfg(target_os = "macos")]
3349 {
3350 let mut o = [0u64; 6];
3351 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3352 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3353 }
3354 return o;
3355 }
3356 #[allow(unreachable_code)]
3357 [0; 6]
3358}
3359
3360#[cfg(test)]
3361mod probe_warmup_tests {
3362 use super::*;
3363 use std::time::Duration;
3364
3365 fn ms(v: f64) -> Duration {
3366 Duration::from_nanos((v * 1e6) as u64)
3367 }
3368
3369 #[test]
3374 fn one_cold_first_sample_does_not_lose_the_class() {
3375 let p = Probe::new();
3376 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3378 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3379 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3380 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3381 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3382 assert_eq!(
3383 p.state.load(Ordering::Relaxed),
3384 1,
3385 "the device is 3x faster once warm and must win"
3386 );
3387 }
3388
3389 #[test]
3393 fn the_warmup_is_spent_once_and_never_underflows() {
3394 let p = Probe::new();
3395 for _ in 0..8 {
3396 probe_record_into(&p, "matmat", None, true, ms(10.0));
3397 }
3398 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3399 assert_eq!(
3400 p.gpu_n.load(Ordering::Relaxed),
3401 7,
3402 "one sample burned, the rest counted"
3403 );
3404 }
3405
3406 #[test]
3412 fn a_class_whose_device_always_declines_settles_on_the_host() {
3413 let _probe_guard = probe_test_guard();
3414 let c = OpClass::MatmatWide;
3418 let p = &PROBES[c as usize];
3419 p.state.store(0, Ordering::Relaxed);
3420 p.declines.store(0, Ordering::Relaxed);
3421 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3422 probe_note_decline(c);
3423 }
3424 assert_eq!(
3425 p.state.load(Ordering::Relaxed),
3426 0,
3427 "one short of the limit is still a question, not an answer"
3428 );
3429 probe_note_decline(c);
3430 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3431 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3432 p.state.store(0, Ordering::Relaxed);
3433 p.declines.store(0, Ordering::Relaxed);
3434 }
3435
3436 #[test]
3439 fn a_slow_device_still_loses_after_the_warmup() {
3440 let p = Probe::new();
3441 for _ in 0..4 {
3442 probe_record_into(&p, "matvec", None, true, ms(40.0));
3443 }
3444 for _ in 0..4 {
3445 probe_record_into(&p, "matvec", None, false, ms(2.0));
3446 }
3447 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3448 }
3449}
3450
3451pub(crate) struct ImageStageGuard {
3454 #[cfg(target_os = "macos")]
3455 metal: Option<crate::gpu_metal::ImageStageGuard>,
3456 #[cfg(feature = "gpu")]
3457 wgpu: crate::gpu_wgpu::ImageStageGuard,
3458}
3459
3460pub(crate) fn image_stage_scope() -> ImageStageGuard {
3461 ImageStageGuard {
3462 #[cfg(target_os = "macos")]
3463 metal: if matches!(backend(), Backend::Metal) {
3464 Some(crate::gpu_metal::image_stage_scope())
3465 } else {
3466 None
3467 },
3468 #[cfg(feature = "gpu")]
3469 wgpu: crate::gpu_wgpu::image_stage_scope(),
3470 }
3471}
3472
3473impl ImageStageGuard {
3474 pub(crate) fn track_model(&mut self, uid: u64) {
3475 #[cfg(target_os = "macos")]
3476 if let Some(metal) = &mut self.metal {
3477 metal.track_model(uid);
3478 }
3479 #[cfg(feature = "gpu")]
3480 self.wgpu.track_model(uid);
3481 #[cfg(not(target_os = "macos"))]
3482 let _ = uid;
3483 }
3484}