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
482pub fn probe_enabled() -> bool {
486 probe_on()
487}
488
489fn probe_on() -> bool {
490 static ON: OnceLock<bool> = OnceLock::new();
491 *ON.get_or_init(|| {
492 std::env::var("CMF_GPU_PROBE")
493 .map(|v| v != "0" && v != "off")
494 .unwrap_or(true)
495 })
496}
497
498pub fn q1_force() -> bool {
503 #[cfg(target_os = "macos")]
504 {
505 backend() == Backend::Metal
506 }
507 #[cfg(not(target_os = "macos"))]
508 {
509 false
510 }
511}
512
513pub fn fused_block_trusted() -> bool {
532 #[cfg(target_os = "macos")]
533 if backend() == Backend::Metal {
534 return true;
535 }
536 wgpu_graph_default()
537}
538
539pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
551 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
552 {
553 return crate::gpu_wgpu::weight_is_resident(model, idx);
554 }
555 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
556 {
557 let _ = (model, idx);
558 true
559 }
560}
561
562pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
563 if !weights_resident && probe_deciding(c) {
564 return ProbeArm::Gpu;
565 }
566 probe_arm(c)
567}
568
569pub fn probe_arm(c: OpClass) -> ProbeArm {
570 PROBE_COLD.with(|f| f.set(false));
575 if !probe_on_for(c) {
576 return ProbeArm::Gpu;
577 }
578 probe_cache_load();
579 let p = &PROBES[c as usize];
580 match p.state.load(Ordering::Relaxed) {
581 1 => ProbeArm::Gpu,
582 2 => ProbeArm::Cpu,
583 _ => {
584 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
585 ProbeArm::Gpu
586 } else {
587 ProbeArm::CpuTimed
588 }
589 }
590 }
591}
592
593pub fn probe_note_decline(c: OpClass) {
597 let p = &PROBES[c as usize];
598 if p.state.load(Ordering::Relaxed) != 0 {
599 return;
600 }
601 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
602 if n >= PROBE_DECLINE_LIMIT
603 && p.state
604 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
605 .is_ok()
606 {
607 tracing::info!(
608 "gpu probe [{}]: device declined {n} times → cpu",
609 CLASS_NAMES[c as usize]
610 );
611 }
612}
613
614pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
617 probe_record_into(
618 &PROBES[c as usize],
619 CLASS_NAMES[c as usize],
620 Some(c),
621 gpu,
622 dur,
623 )
624}
625
626fn probe_record_into(
629 p: &Probe,
630 class_name: &str,
631 cache: Option<OpClass>,
632 gpu: bool,
633 dur: std::time::Duration,
634) {
635 if p.state.load(Ordering::Relaxed) != 0 {
636 return;
637 }
638 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
639 return; }
641 if gpu {
642 let left = p.gpu_burn.load(Ordering::Relaxed);
646 if left > 0 {
647 p.gpu_burn.store(left - 1, Ordering::Relaxed);
648 return; }
650 }
651 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
652 if gpu {
653 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
654 p.gpu_n.fetch_add(1, Ordering::Relaxed);
655 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
656 } else {
657 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
658 p.cpu_n.fetch_add(1, Ordering::Relaxed);
659 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
660 }
661 let (gn, cn) = (
662 p.gpu_n.load(Ordering::Relaxed),
663 p.cpu_n.load(Ordering::Relaxed),
664 );
665 if gn >= 2 && cn >= 2 {
666 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
670 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
671 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
681 return;
682 }
683 let winner = if g <= cp { 1 } else { 2 };
684 if p.state
685 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
686 .is_ok()
687 {
688 tracing::info!(
689 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
690 class_name,
691 g / 1e6,
692 cp / 1e6,
693 if winner == 1 { "gpu" } else { "cpu" },
694 );
695 if let Some(c) = cache {
696 probe_cache_store(c, winner);
697 }
698 }
699 }
700}
701
702pub fn probe_deciding(c: OpClass) -> bool {
705 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
706}
707
708#[allow(unused_variables)]
718pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
719 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
720 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
721 let resident = match backend() {
722 #[cfg(target_os = "macos")]
723 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
724 #[cfg(feature = "gpu")]
725 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
726 Backend::None => false,
727 };
728 if !resident && may_upload {
729 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
730 }
731 resident
732}
733
734#[cfg(test)]
736pub(crate) fn probe_reset() {
737 for p in &PROBES {
738 p.state.store(0, Ordering::Relaxed);
739 p.flip.store(0, Ordering::Relaxed);
740 p.gpu_ns.store(0, Ordering::Relaxed);
741 p.gpu_n.store(0, Ordering::Relaxed);
742 p.cpu_ns.store(0, Ordering::Relaxed);
743 p.cpu_n.store(0, Ordering::Relaxed);
744 }
745}
746
747#[cfg(test)]
751static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
752
753#[cfg(test)]
754fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
755 PROBE_TEST_LOCK
756 .lock()
757 .unwrap_or_else(std::sync::PoisonError::into_inner)
758}
759
760#[cfg(test)]
761mod probe_tests {
762 use super::*;
763 use std::time::Duration;
764
765 #[test]
768 fn probe_alternates_discards_cold_and_decides() {
769 let _probe_guard = probe_test_guard();
770 probe_reset();
771 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
773 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
774
775 probe_note_cold();
779 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
780 for _ in 0..PROBE_SAMPLES {
781 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
782 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
783 }
784 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
785
786 for _ in 0..PROBE_SAMPLES {
788 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
789 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
790 }
791 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
792
793 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
795 CPU_ONLY.with(|c| assert!(!c.get()));
796 cpu_scope(|| {
797 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
798 CPU_ONLY.with(|c| assert!(c.get()));
799 });
800 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
801 CPU_ONLY.with(|c| assert!(!c.get()));
802 probe_reset();
803 }
804
805 #[test]
806 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
807 let _probe_guard = probe_test_guard();
808 let mine = probe_cache_key_named("gemm-nt");
820 let state = || {
821 PROBES[OpClass::GemmNt as usize]
822 .state
823 .load(Ordering::Relaxed)
824 };
825
826 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
828 assert_eq!(state(), 0);
829 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
831 assert_ne!(older, mine);
832 probe_cache_adopt(&format!("{older}\tgpu\n"));
833 assert_eq!(state(), 0);
834 probe_cache_adopt(&format!("{mine}\tcpu\n"));
836 assert_eq!(state(), 2);
837
838 PROBES[OpClass::GemmNt as usize]
839 .state
840 .store(0, Ordering::Relaxed);
841 }
842}
843
844pub const GPU_MIN_ROWS: usize = 65_536;
847
848pub fn min_rows() -> usize {
855 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
856 .ok()
857 .and_then(|v| v.parse().ok())
858 {
859 return v;
860 }
861 if discrete() { 4096 } else { GPU_MIN_ROWS }
862}
863
864pub fn discrete() -> bool {
866 match backend() {
867 #[cfg(feature = "gpu")]
868 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
869 #[cfg(target_os = "macos")]
870 Backend::Metal => false, Backend::None => false,
872 }
873}
874
875pub struct MoeJob<'a> {
879 pub gate: (usize, usize, usize, &'a [f32]),
880 pub up: (usize, usize, usize, &'a [f32]),
881 pub down: (usize, usize, usize, &'a [f32]),
882 pub xs_gate: Vec<f32>,
883 pub xs_up: Vec<f32>,
884 pub down_col: &'a [f32],
885 pub w: f32,
886 pub q1: bool,
889 pub q4t: bool,
892 pub q4tp: bool,
896 pub gu_q2: bool,
900 pub swiglu_limit: f32,
905}
906
907pub struct BatchJob<'a> {
909 pub idx: usize,
910 pub rows: usize,
911 pub cols: usize,
912 pub row_scale: &'a [f32],
913 pub xs: Vec<f32>,
914 pub layout: BatchLayout,
918}
919
920#[derive(Clone, Copy, PartialEq, Eq, Debug)]
923pub enum BatchLayout {
924 Q8,
925 Q1,
926 Q4t,
927 Q4tp,
928}
929
930#[derive(Clone, Copy, PartialEq, Eq)]
931enum Backend {
932 None,
933 #[cfg(target_os = "macos")]
934 Metal,
935 #[cfg(feature = "gpu")]
936 Wgpu,
937}
938
939fn backend() -> Backend {
940 #[cfg(feature = "gpu")]
941 if crate::gpu_wgpu::selected() {
942 return if crate::gpu_wgpu::enabled() {
943 Backend::Wgpu
944 } else {
945 Backend::None
946 };
947 }
948 #[cfg(target_os = "macos")]
949 if crate::gpu_metal::enabled() {
950 return Backend::Metal;
951 }
952 Backend::None
953}
954
955pub fn backend_available() -> bool {
961 #[cfg(target_os = "macos")]
962 {
963 true
965 }
966 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
967 {
968 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
969 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
970 }
971 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
972 {
973 false
974 }
975}
976
977static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
983
984pub fn pause_gpu() -> GpuPause {
986 GPU_PAUSED.store(true, Ordering::Relaxed);
987 GpuPause(())
988}
989
990pub struct GpuPause(());
991
992impl Drop for GpuPause {
993 fn drop(&mut self) {
994 GPU_PAUSED.store(false, Ordering::Relaxed);
995 }
996}
997
998pub fn enabled() -> bool {
999 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
1000}
1001
1002pub fn wgpu_active() -> bool {
1016 #[cfg(feature = "gpu")]
1017 {
1018 matches!(backend(), Backend::Wgpu)
1019 }
1020 #[cfg(not(feature = "gpu"))]
1021 {
1022 false
1023 }
1024}
1025
1026pub fn default_device() -> usize {
1033 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1034 *D.get_or_init(|| {
1035 std::env::var("CMF_GPU_ADAPTER")
1036 .ok()
1037 .and_then(|v| v.trim().parse::<usize>().ok())
1038 .unwrap_or(0)
1039 })
1040}
1041
1042thread_local! {
1043 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1044}
1045
1046pub fn current_device() -> usize {
1048 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1049}
1050
1051pub fn set_current_device(i: usize) {
1055 CUR_DEV.with(|c| c.set(Some(i)));
1056}
1057
1058pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1060 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1061 let r = f();
1062 CUR_DEV.with(|c| c.set(prev));
1063 r
1064}
1065
1066pub fn device_count() -> usize {
1069 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1070 {
1071 return crate::gpu_wgpu::adapter_count();
1072 }
1073 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1074 {
1075 usize::from(backend_available())
1076 }
1077}
1078
1079pub fn vram_budget() -> u64 {
1083 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1084 {
1085 return crate::gpu_wgpu::device_vram_budget();
1086 }
1087 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1088 {
1089 if backend_available() { u64::MAX } else { 0 }
1090 }
1091}
1092
1093pub fn resident_bytes() -> u64 {
1097 #[cfg(feature = "gpu")]
1098 {
1099 if backend() == Backend::Wgpu {
1100 return crate::gpu_wgpu::resident_bytes();
1101 }
1102 }
1103 0
1104}
1105
1106pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1110 #[cfg(feature = "gpu")]
1111 {
1112 if backend() == Backend::Wgpu {
1113 return crate::gpu_wgpu::o1_device_stats(kv_id);
1114 }
1115 }
1116 let _ = kv_id;
1117 (0, 0)
1118}
1119
1120pub fn upload_bytes() -> u64 {
1124 #[cfg(feature = "gpu")]
1125 {
1126 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1127 }
1128 #[cfg(not(feature = "gpu"))]
1129 0
1130}
1131
1132pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1136 #[cfg(feature = "gpu")]
1137 {
1138 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1139 }
1140 let _ = (block, rounds);
1141 None
1142}
1143
1144#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1156pub enum GraphPhase {
1157 Prefill,
1158 Decode,
1159}
1160
1161pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1169 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1170 Some("0") => false,
1171 Some("prefill") => phase == GraphPhase::Prefill,
1172 Some(_) => true,
1173 None => {
1174 if wgpu_graph_default() {
1175 return true;
1176 }
1177 let _ = phase;
1182 false
1183 }
1184 }
1185}
1186
1187pub fn wgpu_graph_default() -> bool {
1188 #[cfg(feature = "gpu")]
1189 {
1190 matches!(backend(), Backend::Wgpu)
1196 && (crate::gpu_wgpu::discrete_active()
1197 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1198 }
1199 #[cfg(not(feature = "gpu"))]
1200 {
1201 false
1202 }
1203}
1204
1205#[allow(clippy::too_many_arguments, unused_variables)]
1207pub fn q8_matvec_range(
1208 model: &Arc<CmfModel>,
1209 idx: usize,
1210 row0: usize,
1211 row_scale: &[f32],
1212 xs: &[f32],
1213 rows: usize,
1214 cols: usize,
1215 out: &mut [f32],
1216) -> bool {
1217 match backend() {
1218 #[cfg(target_os = "macos")]
1219 Backend::Metal => {
1220 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1221 }
1222 #[cfg(feature = "gpu")]
1223 Backend::Wgpu => {
1224 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1225 }
1226 Backend::None => false,
1227 }
1228}
1229
1230#[allow(clippy::too_many_arguments, unused_variables)]
1233#[allow(clippy::too_many_arguments)]
1237pub fn q8_matmat_2f(
1238 model: &Arc<CmfModel>,
1239 idx: usize,
1240 row_scale: &[f32],
1241 col_field: &[f32],
1242 xs: &[f32],
1243 b: usize,
1244 rows: usize,
1245 cols: usize,
1246 out: &mut [f32],
1247) -> bool {
1248 #[allow(unreachable_patterns)]
1249 match backend() {
1250 #[cfg(feature = "gpu")]
1251 Backend::Wgpu => {
1252 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1253 }
1254 _ => false,
1255 }
1256}
1257
1258pub fn q8_matmat(
1259 model: &Arc<CmfModel>,
1260 idx: usize,
1261 row_scale: &[f32],
1262 pre: &[f32],
1263 b: usize,
1264 rows: usize,
1265 cols: usize,
1266 out: &mut [f32],
1267) -> bool {
1268 match backend() {
1269 #[cfg(target_os = "macos")]
1270 Backend::Metal => {
1271 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1272 }
1273 #[cfg(feature = "gpu")]
1274 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1275 Backend::None => false,
1276 }
1277}
1278
1279#[allow(unused_variables)]
1282pub fn q1_matvec(
1283 model: &Arc<CmfModel>,
1284 idx: usize,
1285 xs: &[f32],
1286 rows: usize,
1287 cols: usize,
1288 out: &mut [f32],
1289) -> bool {
1290 match backend() {
1291 #[cfg(target_os = "macos")]
1292 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1293 #[cfg(feature = "gpu")]
1294 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1295 Backend::None => false,
1296 }
1297}
1298
1299#[allow(clippy::too_many_arguments)]
1303pub fn attn_dropin(
1304 model: &Arc<CmfModel>,
1305 kv_id: u64,
1306 layer: usize,
1307 normed: &[f32],
1308 wq_idx: usize,
1309 wk_idx: usize,
1310 wv_idx: usize,
1311 wo_idx: usize,
1312 q_norm: Option<&[f32]>,
1313 k_norm: Option<&[f32]>,
1314 late_qk_norm: bool,
1315 invf: &[f32],
1316 nh: usize,
1317 nkv: usize,
1318 hd: usize,
1319 rd: usize,
1320 hidden: usize,
1321 pos: usize,
1322 cap: usize,
1323 gemma: bool,
1324 eps: f32,
1325 cpu_k: &[Vec<f32>],
1326 cpu_v: &[Vec<f32>],
1327 out: &mut [f32],
1328) -> bool {
1329 match backend() {
1330 #[cfg(feature = "gpu")]
1331 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1332 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1333 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1334 ),
1335 #[allow(unused_variables)]
1336 _ => false,
1337 }
1338}
1339
1340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1344pub enum GraphPrismOp {
1345 None,
1346 Forward,
1347 InverseEmbedding,
1348}
1349
1350pub struct GraphW<'a> {
1354 pub idx: usize,
1355 pub kind: u8,
1356 pub row_scale: &'a [f32],
1357 pub data: &'a [f32],
1358 pub prism: GraphPrismOp,
1359 pub affine: bool,
1360}
1361
1362pub enum GraphAttn<'a> {
1365 Full {
1366 wq: GraphW<'a>,
1367 wk: GraphW<'a>,
1368 wv: GraphW<'a>,
1369 wo: GraphW<'a>,
1370 q_norm: Option<&'a [f32]>,
1371 k_norm: Option<&'a [f32]>,
1372 late_qk_norm: bool,
1374 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1376 output_gate: bool,
1379 cpu_k: &'a [Vec<f32>],
1380 cpu_v: &'a [Vec<f32>],
1381 },
1382 Gdn {
1383 qkv: GraphW<'a>,
1384 z: GraphW<'a>,
1385 a: GraphW<'a>,
1386 b: GraphW<'a>,
1387 out: GraphW<'a>,
1388 conv1d: &'a [f32],
1389 a_log: &'a [f32],
1390 dt_bias: &'a [f32],
1391 norm: &'a [f32],
1392 nv: usize,
1393 nk: usize,
1394 dk: usize,
1395 dv: usize,
1396 kk: usize,
1397 cpu_state: &'a [f32],
1402 },
1403 ShortConv {
1410 inp: GraphW<'a>,
1412 out: GraphW<'a>,
1414 taps: &'a [f32],
1417 kernel: usize,
1418 cpu_state: &'a [f32],
1422 },
1423}
1424
1425pub struct GraphLayer<'a> {
1427 pub input_norm: &'a [f32],
1428 pub attn: GraphAttn<'a>,
1429 pub post_norm: &'a [f32],
1430 pub ffn: GraphFfn<'a>,
1431}
1432
1433pub enum GraphFfn<'a> {
1438 Dense {
1439 gate: GraphW<'a>,
1440 up: GraphW<'a>,
1441 down: GraphW<'a>,
1442 },
1443 Moe {
1444 router: GraphW<'a>,
1446 shared_gate: GraphW<'a>,
1448 experts: Vec<(usize, usize, usize)>,
1452 n_exp: usize,
1454 top_k: usize,
1455 inter: usize,
1456 norm_topk: bool,
1457 q4tp: bool,
1463 gu_q2: bool,
1467 sigmoid: bool,
1471 bias: Option<&'a [f32]>,
1474 has_shared: bool,
1478 shared_gated: bool,
1483 route_scale: f32,
1486 },
1487}
1488
1489#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1492pub enum TokenGraphOutcome {
1493 Declined,
1495 Completed,
1497 Failed,
1499}
1500
1501#[allow(clippy::too_many_arguments)]
1506pub fn forward_token_graph(
1507 model: &Arc<CmfModel>,
1508 kv_id: u64,
1509 layers: &[GraphLayer],
1510 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1513 o1_epoch: u64,
1514 invf: &[f32],
1515 h: &mut [f32],
1516 nh: usize,
1517 nkv: usize,
1518 hd: usize,
1519 attn_scale: f32,
1520 rd: usize,
1521 hidden: usize,
1522 inter: usize,
1523 position: usize,
1524 cap: usize,
1525 gemma: bool,
1526 eps: f32,
1527 lm_head: Option<(&GraphW, usize)>,
1528 final_norm: &[f32],
1529 logits: &mut Vec<f32>,
1530 loop_norm_at: &[usize],
1531 steps: usize,
1532 embed: Option<(&GraphW, usize, f32)>,
1533 ids_out: Option<&mut Vec<u32>>,
1534 layers_run: Option<&mut usize>,
1537 layer_base: usize,
1541 hidden_too: bool,
1543) -> TokenGraphOutcome {
1544 match backend() {
1545 #[cfg(feature = "gpu")]
1546 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1547 model,
1548 kv_id,
1549 layers,
1550 o1,
1551 o1_epoch,
1552 invf,
1553 h,
1554 nh,
1555 nkv,
1556 hd,
1557 attn_scale,
1558 rd,
1559 hidden,
1560 inter,
1561 position,
1562 cap,
1563 gemma,
1564 eps,
1565 lm_head,
1566 final_norm,
1567 logits,
1568 loop_norm_at,
1569 steps,
1570 embed,
1571 ids_out,
1572 layers_run,
1573 layer_base,
1574 hidden_too,
1575 ),
1576 #[allow(unused_variables)]
1577 _ => {
1578 let _ = (
1579 attn_scale,
1580 lm_head,
1581 final_norm,
1582 logits,
1583 loop_norm_at,
1584 layers_run,
1585 layer_base,
1586 hidden_too,
1587 );
1588 TokenGraphOutcome::Declined
1589 }
1590 }
1591}
1592
1593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1597pub enum BatchGraphOutcome {
1598 Declined,
1601 Completed,
1603 Failed,
1607}
1608
1609pub struct SpecTail<'a> {
1610 pub lm: GraphW<'a>,
1611 pub lm_rows: usize,
1612 pub final_norm: &'a [f32],
1613 pub logits_out: &'a mut Vec<f32>,
1614}
1615
1616#[allow(clippy::too_many_arguments)]
1620pub fn forward_batch_graph(
1621 model: &Arc<CmfModel>,
1622 kv_id: u64,
1623 layers: &[GraphLayer],
1624 invf: &[f32],
1625 h: &mut [f32],
1626 nh: usize,
1627 nkv: usize,
1628 hd: usize,
1629 rd: usize,
1630 hidden: usize,
1631 inter: usize,
1632 positions: &[usize],
1633 cap: usize,
1634 gemma: bool,
1635 eps: f32,
1636 attn_scale: f32,
1637 k: usize,
1638 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1641 o1_epoch: u64,
1642 spec: Option<SpecTail<'_>>,
1643) -> BatchGraphOutcome {
1644 match backend() {
1645 #[cfg(feature = "gpu")]
1646 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1647 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1648 eps, attn_scale, k, o1, o1_epoch, spec,
1649 ),
1650 #[allow(unreachable_patterns)]
1651 _ => {
1652 let _ = (o1, o1_epoch, spec);
1653 BatchGraphOutcome::Declined
1654 }
1655 }
1656}
1657
1658pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1663 #[cfg(feature = "gpu")]
1664 if backend() == Backend::Wgpu {
1665 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1666 }
1667 #[allow(unreachable_code)]
1668 {
1669 let _ = (kv_id, slot, base_pos, expected_layers);
1670 false
1671 }
1672}
1673
1674pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1680 #[cfg(feature = "gpu")]
1681 if backend() == Backend::Wgpu {
1682 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1683 }
1684 #[cfg(target_os = "macos")]
1685 if backend() == Backend::Metal {
1686 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1687 return true;
1688 }
1689 false
1690}
1691
1692pub fn graph_kv_reset(_kv_id: u64) {
1694 #[cfg(feature = "gpu")]
1695 if backend() == Backend::Wgpu {
1696 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1697 }
1698}
1699
1700pub fn q1t_matvec(
1704 model: &Arc<CmfModel>,
1705 idx: usize,
1706 xs: &[f32],
1707 rows: usize,
1708 cols: usize,
1709 out: &mut [f32],
1710) -> bool {
1711 match backend() {
1712 #[cfg(target_os = "macos")]
1713 Backend::Metal => {
1714 if metal_q1t_enabled() {
1715 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1716 } else {
1717 false
1718 }
1719 }
1720 #[cfg(feature = "gpu")]
1721 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1722 Backend::None => false,
1723 }
1724}
1725
1726#[allow(unused_variables)]
1729pub fn q4b_matvec(
1730 model: &Arc<CmfModel>,
1731 idx: usize,
1732 xs: &[f32],
1733 rows: usize,
1734 cols: usize,
1735 out: &mut [f32],
1736) -> bool {
1737 match backend() {
1738 #[cfg(target_os = "macos")]
1739 Backend::Metal => false,
1740 #[cfg(feature = "gpu")]
1741 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1742 Backend::None => false,
1743 }
1744}
1745
1746pub fn q1t_matmat(
1749 model: &Arc<CmfModel>,
1750 idx: usize,
1751 xs: &[f32],
1752 b: usize,
1753 rows: usize,
1754 cols: usize,
1755 out: &mut [f32],
1756) -> bool {
1757 match backend() {
1758 #[cfg(target_os = "macos")]
1759 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1763 #[cfg(feature = "gpu")]
1764 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1765 Backend::None => false,
1766 }
1767}
1768
1769#[cfg(target_os = "macos")]
1773pub(crate) fn metal_q1t_enabled() -> bool {
1774 std::env::var("CMF_METAL_Q1T")
1775 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1776 .unwrap_or(true)
1777}
1778
1779pub fn q1_matmat(
1781 model: &Arc<CmfModel>,
1782 idx: usize,
1783 xs: &[f32],
1784 b: usize,
1785 rows: usize,
1786 cols: usize,
1787 out: &mut [f32],
1788) -> bool {
1789 match backend() {
1790 #[cfg(feature = "gpu")]
1791 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1792 #[allow(unused_variables)]
1793 _ => false,
1794 }
1795}
1796
1797static MM_KILL: AtomicBool = AtomicBool::new(false);
1802pub(crate) fn mm_killed() -> bool {
1803 MM_KILL.load(Ordering::Relaxed)
1804}
1805pub(crate) fn mm_kill() {
1806 MM_KILL.store(true, Ordering::Relaxed);
1807}
1808
1809static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1816const MM_STRIKES_TO_KILL: u32 = 3;
1817static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1824
1825pub fn mm_kill_arm(on: bool) {
1828 MM_ARMED.store(on, Ordering::Relaxed);
1829 if on {
1830 MM_STRIKES.store(0, Ordering::Relaxed);
1831 }
1832}
1833
1834pub(crate) fn mm_budget_check(
1841 what: &str,
1842 el: std::time::Duration,
1843 budget: std::time::Duration,
1844 exempt: bool,
1845) {
1846 if el <= budget {
1847 MM_STRIKES.store(0, Ordering::Relaxed);
1848 return;
1849 }
1850 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1851 return;
1852 }
1853 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1854 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1855 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1856 if !on {
1857 tracing::info!(
1858 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1859 );
1860 return;
1861 }
1862 if n >= MM_STRIKES_TO_KILL {
1863 tracing::warn!(
1864 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1865 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1866 );
1867 mm_kill();
1868 } else {
1869 tracing::info!(
1870 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1871 );
1872 }
1873}
1874
1875#[allow(unused_variables, clippy::too_many_arguments)]
1880pub fn chunk_attend(
1881 q: &[f32],
1882 k: &[&[f32]],
1883 v: &[&[f32]],
1884 b: usize,
1885 s0: usize,
1886 nh: usize,
1887 nkv: usize,
1888 hd: usize,
1889 scale: f32,
1890 out: &mut [f32],
1891) -> bool {
1892 match backend() {
1893 #[cfg(feature = "gpu")]
1894 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1895 #[allow(unreachable_patterns)]
1896 _ => false,
1897 }
1898}
1899
1900#[allow(unused_variables, clippy::too_many_arguments)]
1904pub fn q4t_qkv(
1905 model: &Arc<CmfModel>,
1906 wq: usize,
1907 wk: usize,
1908 wv: usize,
1909 xs: &[f32],
1910 b: usize,
1911 cols: usize,
1912 rq: usize,
1913 rk: usize,
1914 rv: usize,
1915 out: &mut [f32],
1916) -> bool {
1917 match backend() {
1918 #[cfg(feature = "gpu")]
1919 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1920 #[allow(unreachable_patterns)]
1921 _ => false,
1922 }
1923}
1924
1925#[allow(unused_variables, clippy::too_many_arguments)]
1927#[allow(clippy::too_many_arguments, unused_variables)]
1931pub fn q4tp_ffn_packed(
1932 model: &Arc<CmfModel>,
1933 w1: usize,
1934 w2: usize,
1935 xs: &[f32],
1936 b: usize,
1937 hidden: usize,
1938 inter: usize,
1939 bias: Option<&[f32]>,
1940 out: &mut [f32],
1941) -> bool {
1942 match backend() {
1943 #[cfg(feature = "gpu")]
1944 Backend::Wgpu => {
1945 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1946 }
1947 #[allow(unreachable_patterns)]
1948 _ => false,
1949 }
1950}
1951
1952pub fn q4tp_ffn(
1953 model: &Arc<CmfModel>,
1954 w1: usize,
1955 w3: usize,
1956 w2: usize,
1957 xs: &[f32],
1958 b: usize,
1959 hidden: usize,
1960 inter: usize,
1961 out: &mut [f32],
1962) -> bool {
1963 match backend() {
1964 #[cfg(target_os = "macos")]
1965 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1966 #[cfg(feature = "gpu")]
1967 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1968 #[allow(unreachable_patterns)]
1969 _ => false,
1970 }
1971}
1972
1973#[allow(clippy::too_many_arguments, unused_variables)]
1978pub fn q4tp_gelu_ffn(
1979 model: &Arc<CmfModel>,
1980 w_in: usize,
1981 w_out: usize,
1982 xs: &[f32],
1983 b: usize,
1984 hidden: usize,
1985 inter: usize,
1986 bias_in: &[f32],
1987 bias_out: &[f32],
1988 out: &mut [f32],
1989) -> bool {
1990 match backend() {
1991 #[cfg(feature = "gpu")]
1992 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
1993 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
1994 ),
1995 #[allow(unreachable_patterns)]
1996 _ => false,
1997 }
1998}
1999
2000pub fn q4t_ffn(
2001 model: &Arc<CmfModel>,
2002 w1: usize,
2003 w3: usize,
2004 w2: usize,
2005 xs: &[f32],
2006 b: usize,
2007 hidden: usize,
2008 inter: usize,
2009 out: &mut [f32],
2010) -> bool {
2011 match backend() {
2012 #[cfg(target_os = "macos")]
2013 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2014 #[cfg(feature = "gpu")]
2015 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2016 #[allow(unreachable_patterns)]
2017 _ => false,
2018 }
2019}
2020
2021pub struct DitBlockArgs<'a> {
2026 pub n: usize,
2027 pub hidden: usize,
2028 pub inter: usize,
2029 pub nh: usize,
2030 pub nkv: usize,
2031 pub hd: usize,
2032 pub eps: f32,
2033 pub rope_cos: &'a [f32],
2034 pub rope_sin: &'a [f32],
2035 pub norm1: &'a [f32],
2036 pub norm2: &'a [f32],
2037 pub ffn_norm1: &'a [f32],
2038 pub ffn_norm2: &'a [f32],
2039 pub norm_q: &'a [f32],
2040 pub norm_k: &'a [f32],
2041 pub s_msa: &'a [f32],
2042 pub gate_msa: &'a [f32],
2043 pub s_mlp: &'a [f32],
2044 pub gate_mlp: &'a [f32],
2045 pub wq: usize,
2046 pub wk: usize,
2047 pub wv: usize,
2048 pub wo: usize,
2049 pub w1: usize,
2050 pub w3: usize,
2051 pub w2: usize,
2052 pub q4tp: bool,
2056 pub resident_in: bool,
2059 pub resident_out: bool,
2063}
2064
2065pub fn dit_chain_supported() -> bool {
2069 #[cfg(feature = "gpu")]
2070 {
2071 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2072 }
2073 #[allow(unreachable_code)]
2074 false
2075}
2076
2077pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2080 #[cfg(feature = "gpu")]
2081 {
2082 if matches!(backend(), Backend::Wgpu) {
2083 return crate::gpu_wgpu::dit_state_fetch(_x);
2084 }
2085 }
2086 false
2087}
2088
2089#[allow(unused_variables)]
2093#[allow(unused_variables, clippy::too_many_arguments)]
2097pub fn dit_qkv(
2098 model: &Arc<CmfModel>,
2099 wq: usize,
2100 wk: usize,
2101 wv: usize,
2102 xs: &[f32],
2103 b: usize,
2104 hidden: usize,
2105 qrows: usize,
2106 kvrows: usize,
2107 q_out: &mut [f32],
2108 k_out: &mut [f32],
2109 v_out: &mut [f32],
2110) -> bool {
2111 match backend() {
2112 #[cfg(feature = "gpu")]
2113 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2114 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2115 ),
2116 #[allow(unreachable_patterns)]
2117 _ => false,
2118 }
2119}
2120
2121pub struct QwenImageAttentionArgs<'a> {
2129 pub image: &'a [f32],
2130 pub text: &'a [f32],
2131 pub image_tokens: usize,
2132 pub text_tokens: usize,
2133 pub heads: usize,
2134 pub head_dim: usize,
2135 pub image_q: usize,
2136 pub image_k: usize,
2137 pub image_v: usize,
2138 pub text_q: usize,
2139 pub text_k: usize,
2140 pub text_v: usize,
2141 pub image_out: usize,
2142 pub text_out: usize,
2143 pub image_q_norm: &'a [f32],
2144 pub image_k_norm: &'a [f32],
2145 pub text_q_norm: &'a [f32],
2146 pub text_k_norm: &'a [f32],
2147 pub image_cos: &'a [f32],
2148 pub image_sin: &'a [f32],
2149 pub text_cos: &'a [f32],
2150 pub text_sin: &'a [f32],
2151 pub image_q_bias: &'a [f32],
2152 pub image_k_bias: &'a [f32],
2153 pub image_v_bias: &'a [f32],
2154 pub text_q_bias: &'a [f32],
2155 pub text_k_bias: &'a [f32],
2156 pub text_v_bias: &'a [f32],
2157 pub image_out_bias: &'a [f32],
2158 pub text_out_bias: &'a [f32],
2159 pub image_proj: &'a mut [f32],
2160 pub text_proj: &'a mut [f32],
2161}
2162
2163#[allow(clippy::too_many_fields)]
2168pub struct QwenImageChainBlock<'a> {
2169 pub image_mod: &'a [f32],
2170 pub text_mod: &'a [f32],
2171 pub image_q: usize,
2172 pub image_k: usize,
2173 pub image_v: usize,
2174 pub text_q: usize,
2175 pub text_k: usize,
2176 pub text_v: usize,
2177 pub image_out: usize,
2178 pub text_out: usize,
2179 pub image_q_norm: &'a [f32],
2180 pub image_k_norm: &'a [f32],
2181 pub text_q_norm: &'a [f32],
2182 pub text_k_norm: &'a [f32],
2183 pub image_q_bias: &'a [f32],
2184 pub image_k_bias: &'a [f32],
2185 pub image_v_bias: &'a [f32],
2186 pub text_q_bias: &'a [f32],
2187 pub text_k_bias: &'a [f32],
2188 pub text_v_bias: &'a [f32],
2189 pub image_out_bias: &'a [f32],
2190 pub text_out_bias: &'a [f32],
2191 pub image_attn_gate: &'a [f32],
2192 pub text_attn_gate: &'a [f32],
2193 pub image_mlp_in: usize,
2194 pub image_mlp_out: usize,
2195 pub text_mlp_in: usize,
2196 pub text_mlp_out: usize,
2197 pub image_mlp_in_bias: &'a [f32],
2198 pub image_mlp_out_bias: &'a [f32],
2199 pub text_mlp_in_bias: &'a [f32],
2200 pub text_mlp_out_bias: &'a [f32],
2201}
2202
2203#[allow(clippy::too_many_fields)]
2209pub struct QwenImageBlockArgs<'a> {
2210 pub image: &'a mut [f32],
2213 pub text: &'a mut [f32],
2214 pub image_norm: &'a [f32],
2215 pub text_norm: &'a [f32],
2216 pub image_tokens: usize,
2217 pub text_tokens: usize,
2218 pub heads: usize,
2219 pub head_dim: usize,
2220 pub image_cos: &'a [f32],
2221 pub image_sin: &'a [f32],
2222 pub text_cos: &'a [f32],
2223 pub text_sin: &'a [f32],
2224 pub image_q: usize,
2225 pub image_k: usize,
2226 pub image_v: usize,
2227 pub text_q: usize,
2228 pub text_k: usize,
2229 pub text_v: usize,
2230 pub image_out: usize,
2231 pub text_out: usize,
2232 pub image_q_norm: &'a [f32],
2233 pub image_k_norm: &'a [f32],
2234 pub text_q_norm: &'a [f32],
2235 pub text_k_norm: &'a [f32],
2236 pub image_q_bias: &'a [f32],
2237 pub image_k_bias: &'a [f32],
2238 pub image_v_bias: &'a [f32],
2239 pub text_q_bias: &'a [f32],
2240 pub text_k_bias: &'a [f32],
2241 pub text_v_bias: &'a [f32],
2242 pub image_out_bias: &'a [f32],
2243 pub text_out_bias: &'a [f32],
2244 pub image_attn_gate: &'a [f32],
2245 pub text_attn_gate: &'a [f32],
2246 pub image_mlp_in: usize,
2247 pub image_mlp_out: usize,
2248 pub text_mlp_in: usize,
2249 pub text_mlp_out: usize,
2250 pub image_mlp_in_bias: &'a [f32],
2251 pub image_mlp_out_bias: &'a [f32],
2252 pub text_mlp_in_bias: &'a [f32],
2253 pub text_mlp_out_bias: &'a [f32],
2254 pub image_mlp_mod: &'a [f32],
2255 pub text_mlp_mod: &'a [f32],
2256 pub image_mlp_gate: &'a [f32],
2257 pub text_mlp_gate: &'a [f32],
2258}
2259
2260pub struct QwenImageChainArgs<'a> {
2265 pub image: &'a mut [f32],
2266 pub text: &'a mut [f32],
2267 pub image_tokens: usize,
2268 pub text_tokens: usize,
2269 pub heads: usize,
2270 pub head_dim: usize,
2271 pub image_cos: &'a [f32],
2272 pub image_sin: &'a [f32],
2273 pub text_cos: &'a [f32],
2274 pub text_sin: &'a [f32],
2275 pub blocks: &'a [QwenImageChainBlock<'a>],
2276}
2277
2278#[allow(unused_variables)]
2279pub fn qwen_image_attention(
2280 model: &Arc<CmfModel>,
2281 args: &mut QwenImageAttentionArgs<'_>,
2282) -> bool {
2283 match backend() {
2284 #[cfg(feature = "gpu")]
2285 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2286 #[allow(unreachable_patterns)]
2287 _ => false,
2288 }
2289}
2290
2291#[allow(unused_variables)]
2292pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2293 match backend() {
2294 #[cfg(feature = "gpu")]
2295 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2296 #[allow(unreachable_patterns)]
2297 _ => false,
2298 }
2299}
2300
2301#[allow(unused_variables)]
2305pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2306 match backend() {
2307 #[cfg(feature = "gpu")]
2308 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2309 #[allow(unreachable_patterns)]
2310 _ => false,
2311 }
2312}
2313
2314#[allow(unused_variables, clippy::too_many_arguments)]
2321pub fn qwen_image_mlp_inplace(
2322 model: &Arc<CmfModel>,
2323 w_in: usize,
2324 w_out: usize,
2325 data: &mut [f32],
2326 batch: usize,
2327 hidden: usize,
2328 inter: usize,
2329 bias_in: &[f32],
2330 bias_out: &[f32],
2331 modulation: &[f32],
2332 gate: &[f32],
2333) -> bool {
2334 match backend() {
2335 #[cfg(feature = "gpu")]
2336 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2337 model,
2338 w_in,
2339 w_out,
2340 data,
2341 batch,
2342 hidden,
2343 inter,
2344 bias_in,
2345 bias_out,
2346 modulation,
2347 gate,
2348 ),
2349 #[allow(unreachable_patterns)]
2350 _ => false,
2351 }
2352}
2353
2354pub fn fused_dit_block_available() -> bool {
2358 #[cfg(target_os = "macos")]
2359 {
2360 matches!(backend(), Backend::Metal) && fused_block_trusted()
2361 }
2362 #[cfg(not(target_os = "macos"))]
2363 {
2364 false
2365 }
2366}
2367
2368pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2369 dit_block_seg(model, a, &[a.n], x)
2370}
2371
2372pub fn dit_block_seg(
2376 model: &Arc<CmfModel>,
2377 a: &DitBlockArgs,
2378 segs: &[usize],
2379 x: &mut [f32],
2380) -> bool {
2381 match backend() {
2382 #[cfg(target_os = "macos")]
2383 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2384 #[cfg(feature = "gpu")]
2391 Backend::Wgpu
2392 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2393 Some("0") => false,
2394 Some(_) => true,
2395 None => crate::gpu_wgpu::discrete_active(),
2396 } =>
2397 {
2398 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2399 }
2400 #[allow(unreachable_patterns)]
2401 _ => false,
2402 }
2403}
2404
2405pub struct VaeResnetArgs<'a> {
2409 pub groups: usize,
2410 pub ic: usize,
2411 pub oc: usize,
2412 pub h: usize,
2413 pub w: usize,
2414 pub n1w: &'a [f32],
2415 pub n1b: &'a [f32],
2416 pub c1w: &'a [f32],
2417 pub c1b: &'a [f32],
2418 pub c1k: usize,
2419 pub n2w: &'a [f32],
2420 pub n2b: &'a [f32],
2421 pub c2w: &'a [f32],
2422 pub c2b: &'a [f32],
2423 pub c2k: usize,
2424 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2425}
2426
2427#[allow(unused_variables)]
2430pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2431 match backend() {
2432 #[cfg(target_os = "macos")]
2433 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2434 _ => false,
2435 }
2436}
2437
2438#[allow(unused_variables, clippy::too_many_arguments)]
2441pub fn vae_upsample_conv(
2442 w: &[f32],
2443 bias: &[f32],
2444 x: &[f32],
2445 ic: usize,
2446 oc: usize,
2447 h: usize,
2448 w_img: usize,
2449 k: usize,
2450 out: &mut [f32],
2451) -> bool {
2452 match backend() {
2453 #[cfg(target_os = "macos")]
2454 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2455 #[cfg(feature = "gpu")]
2456 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2457 #[allow(unreachable_patterns)]
2458 _ => false,
2459 }
2460}
2461
2462#[allow(unused_variables, clippy::too_many_arguments)]
2465pub fn vae_conv2d(
2466 w: &[f32],
2467 bias: &[f32],
2468 x: &[f32],
2469 ic: usize,
2470 oc: usize,
2471 h: usize,
2472 w_img: usize,
2473 k: usize,
2474 out: &mut [f32],
2475) -> bool {
2476 match backend() {
2477 #[cfg(target_os = "macos")]
2478 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2479 #[cfg(feature = "gpu")]
2480 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2481 #[allow(unreachable_patterns)]
2482 _ => false,
2483 }
2484}
2485
2486#[allow(unused_variables, clippy::too_many_arguments)]
2490#[allow(unused_variables)]
2494#[allow(clippy::too_many_arguments)]
2495#[allow(clippy::too_many_arguments, unused_variables)]
2498pub fn dit_qkv_attention(
2499 model: &Arc<CmfModel>,
2500 qkv_idx: usize,
2501 xn: &[f32],
2502 n: usize,
2503 hidden: usize,
2504 nh: usize,
2505 hd: usize,
2506 scale: f32,
2507 nr: (&[f32], &[f32], &[f32], f32),
2508 out: &mut [f32],
2509) -> bool {
2510 match backend() {
2511 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2512 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2513 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2514 ),
2515 #[allow(unreachable_patterns)]
2516 _ => false,
2517 }
2518}
2519
2520#[allow(clippy::too_many_arguments)]
2523pub fn dit_qkv_attn_out(
2524 model: &Arc<CmfModel>,
2525 qkv_idx: usize,
2526 out_idx: usize,
2527 xn: &[f32],
2528 n: usize,
2529 hidden: usize,
2530 nh: usize,
2531 hd: usize,
2532 scale: f32,
2533 nr: (&[f32], &[f32], &[f32], f32),
2534 proj: &mut [f32],
2535) -> bool {
2536 match backend() {
2537 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2538 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2539 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2540 ),
2541 #[allow(unreachable_patterns)]
2542 _ => false,
2543 }
2544}
2545
2546#[allow(clippy::too_many_arguments)]
2548pub fn vae_qkv_attn_out(
2549 model: &Arc<CmfModel>,
2550 qkv_idx: usize,
2551 out_idx: usize,
2552 xn: &[f32],
2553 n: usize,
2554 dim: usize,
2555 nh: usize,
2556 hd: usize,
2557 scale: f32,
2558 angles: &[f32],
2559 eps: f32,
2560 qkv_bias: &[f32],
2561 proj: &mut [f32],
2562) -> bool {
2563 match backend() {
2564 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2565 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2566 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2567 ),
2568 #[allow(unreachable_patterns)]
2569 _ => false,
2570 }
2571}
2572
2573#[allow(clippy::too_many_arguments)]
2574pub fn vae_attention_packed(
2575 qkv: &[f32],
2576 nh: usize,
2577 n: usize,
2578 hd: usize,
2579 scale: f32,
2580 angles: &[f32],
2581 eps: f32,
2582 out: &mut [f32],
2583) -> bool {
2584 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2585}
2586
2587#[allow(clippy::too_many_arguments)]
2588pub fn vae_attention_packed_layout(
2589 qkv: &[f32],
2590 nh: usize,
2591 n: usize,
2592 hd: usize,
2593 scale: f32,
2594 angles: &[f32],
2595 eps: f32,
2596 out: &mut [f32],
2597 layout: u32,
2598) -> bool {
2599 match backend() {
2600 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2601 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2602 qkv, nh, n, hd, scale, angles, eps, out, layout,
2603 ),
2604 #[allow(unreachable_patterns)]
2605 _ => false,
2606 }
2607}
2608
2609#[allow(clippy::too_many_arguments)]
2610pub fn dit_split_only(
2611 qkv: &[f32],
2612 nh: usize,
2613 n: usize,
2614 hd: usize,
2615 layout: u32,
2616 norm: Option<(&[f32], f32)>,
2617 out_q: &mut [f32],
2618) -> bool {
2619 match backend() {
2620 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2621 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2622 #[allow(unreachable_patterns)]
2623 _ => false,
2624 }
2625}
2626
2627pub fn gemm_nt_f32_transient(
2635 x: &[f32],
2636 w: &[f32],
2637 y: &mut [f32],
2638 n: usize,
2639 k: usize,
2640 m: usize,
2641) -> bool {
2642 match backend() {
2643 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2644 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2645 #[allow(unreachable_patterns)]
2646 _ => false,
2647 }
2648}
2649
2650pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2651 match backend() {
2652 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2653 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2654 #[allow(unreachable_patterns)]
2655 _ => false,
2656 }
2657}
2658
2659#[allow(clippy::too_many_arguments)]
2662pub fn music3_ffn(
2663 model: &std::sync::Arc<CmfModel>,
2664 idx_in: usize,
2665 idx_out: usize,
2666 h: &[f32],
2667 bias_in: &[f32],
2668 n: usize,
2669 hs: usize,
2670 inter: usize,
2671 out: &mut [f32],
2672) -> bool {
2673 match backend() {
2674 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2675 Backend::Wgpu => {
2676 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2677 }
2678 #[allow(unreachable_patterns)]
2679 _ => false,
2680 }
2681}
2682
2683#[allow(clippy::too_many_arguments)]
2687pub fn conv1d_gemm(
2688 x: &[f32],
2689 w: &[f32],
2690 ic: usize,
2691 oc: usize,
2692 n: usize,
2693 k: usize,
2694 pad: usize,
2695 dil: usize,
2696 out_n: usize,
2697 yt: &mut [f32],
2698) -> bool {
2699 match backend() {
2700 #[cfg(target_os = "macos")]
2701 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2702 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2703 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2704 #[allow(unreachable_patterns)]
2705 _ => false,
2706 }
2707}
2708
2709#[allow(clippy::too_many_arguments)]
2711pub fn vae_conv2d_coop(
2712 w: &[f32],
2713 bias: Option<&[f32]>,
2714 x: &[f32],
2715 ic: usize,
2716 oc: usize,
2717 h: usize,
2718 wi: usize,
2719 k: usize,
2720 out: &mut [f32],
2721) -> bool {
2722 match backend() {
2723 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2724 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2725 #[allow(unreachable_patterns)]
2726 _ => false,
2727 }
2728}
2729
2730pub fn dit_attention_packed(
2731 qkv: &[f32],
2732 nh: usize,
2733 n: usize,
2734 hd: usize,
2735 scale: f32,
2736 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2739 out: &mut [f32],
2740) -> bool {
2741 match backend() {
2742 #[cfg(feature = "gpu")]
2749 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2750 #[allow(unreachable_patterns)]
2751 _ => false,
2752 }
2753}
2754
2755pub fn dit_attention_packed_available() -> bool {
2763 #[allow(unreachable_patterns)]
2764 match backend() {
2765 #[cfg(feature = "gpu")]
2766 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2767 _ => false,
2768 }
2769}
2770
2771pub fn dit_attention(
2772 qh: &[f32],
2773 kh: &[f32],
2774 vh: &[f32],
2775 nh: usize,
2776 nkv: usize,
2777 n: usize,
2778 hd: usize,
2779 scale: f32,
2780 out: &mut [f32],
2781) -> bool {
2782 match backend() {
2783 #[cfg(target_os = "macos")]
2784 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2785 #[cfg(feature = "gpu")]
2786 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2787 #[allow(unreachable_patterns)]
2788 _ => false,
2789 }
2790}
2791
2792#[allow(unused_variables)]
2797pub fn q4tp_matmat(
2798 model: &Arc<CmfModel>,
2799 idx: usize,
2800 xs: &[f32],
2801 b: usize,
2802 rows: usize,
2803 cols: usize,
2804 out: &mut [f32],
2805) -> bool {
2806 match backend() {
2807 #[cfg(target_os = "macos")]
2808 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2809 #[cfg(feature = "gpu")]
2810 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2811 #[allow(unreachable_patterns)]
2812 _ => false,
2813 }
2814}
2815
2816pub fn q2tp_matmat(
2819 model: &Arc<CmfModel>,
2820 idx: usize,
2821 xs: &[f32],
2822 b: usize,
2823 rows: usize,
2824 cols: usize,
2825 out: &mut [f32],
2826) -> bool {
2827 match backend() {
2828 #[cfg(target_os = "macos")]
2829 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2830 #[cfg(feature = "gpu")]
2831 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2832 #[allow(unreachable_patterns)]
2833 _ => false,
2834 }
2835}
2836
2837pub fn q2tp_affine_matmat(
2840 model: &Arc<CmfModel>,
2841 idx: usize,
2842 xs: &[f32],
2843 b: usize,
2844 rows: usize,
2845 cols: usize,
2846 out: &mut [f32],
2847) -> bool {
2848 match backend() {
2849 #[cfg(target_os = "macos")]
2850 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2851 #[cfg(feature = "gpu")]
2852 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2853 #[allow(unreachable_patterns)]
2854 _ => false,
2855 }
2856}
2857
2858pub fn q2tp_matvec(
2860 model: &Arc<CmfModel>,
2861 idx: usize,
2862 xs: &[f32],
2863 rows: usize,
2864 cols: usize,
2865 out: &mut [f32],
2866) -> bool {
2867 match backend() {
2868 #[cfg(target_os = "macos")]
2869 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
2870 #[cfg(feature = "gpu")]
2871 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
2872 #[allow(unreachable_patterns)]
2873 _ => false,
2874 }
2875}
2876
2877pub fn q2tp_affine_matvec(
2881 model: &Arc<CmfModel>,
2882 idx: usize,
2883 xs: &[f32],
2884 rows: usize,
2885 cols: usize,
2886 out: &mut [f32],
2887) -> bool {
2888 match backend() {
2889 #[cfg(target_os = "macos")]
2890 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2891 #[cfg(feature = "gpu")]
2892 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2893 #[allow(unreachable_patterns)]
2894 _ => false,
2895 }
2896}
2897
2898pub fn q4tp_matvec(
2903 model: &Arc<CmfModel>,
2904 idx: usize,
2905 xs: &[f32],
2906 rows: usize,
2907 cols: usize,
2908 out: &mut [f32],
2909) -> bool {
2910 match backend() {
2911 #[cfg(target_os = "macos")]
2912 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2913 #[cfg(feature = "gpu")]
2914 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2915 #[allow(unreachable_patterns)]
2916 _ => false,
2917 }
2918}
2919
2920pub fn q4t_matvec(
2926 model: &Arc<CmfModel>,
2927 idx: usize,
2928 xs: &[f32],
2929 rows: usize,
2930 cols: usize,
2931 out: &mut [f32],
2932) -> bool {
2933 match backend() {
2934 #[cfg(target_os = "macos")]
2935 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2936 #[allow(unreachable_patterns)]
2937 _ => false,
2938 }
2939}
2940
2941pub fn q4t_matmat(
2942 model: &Arc<CmfModel>,
2943 idx: usize,
2944 xs: &[f32],
2945 b: usize,
2946 rows: usize,
2947 cols: usize,
2948 out: &mut [f32],
2949) -> bool {
2950 match backend() {
2951 #[cfg(target_os = "macos")]
2952 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2953 #[cfg(feature = "gpu")]
2954 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2955 #[allow(unreachable_patterns)]
2956 _ => false,
2957 }
2958}
2959
2960#[cfg(target_os = "macos")]
2962pub use crate::gpu_metal::{
2963 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2964 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2965};
2966
2967#[cfg(target_os = "macos")]
2969pub fn gdn_block(
2970 model: &Arc<CmfModel>,
2971 layers: &[GdnGpuLayer],
2972 states: &mut [&mut [f32]],
2973 cfg: &GdnGpuCfg,
2974 h: &mut [f32],
2975) -> bool {
2976 match backend() {
2977 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2978 _ => false,
2979 }
2980}
2981
2982#[allow(unused_variables)]
2984pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2985 match backend() {
2986 #[cfg(target_os = "macos")]
2987 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2988 #[cfg(feature = "gpu")]
2989 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2990 Backend::None => false,
2991 }
2992}
2993
2994#[allow(unused_variables)]
2996pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2997 match backend() {
2998 #[cfg(target_os = "macos")]
2999 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
3000 #[cfg(feature = "gpu")]
3001 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
3002 Backend::None => false,
3003 }
3004}
3005
3006static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
3022static 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)];
3026
3027const GRAPH_RACE_SAMPLES: u32 = 4;
3029
3030static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
3040
3041pub fn graph_mark_unsupported() {
3046 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
3047 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
3048 }
3049}
3050
3051pub fn graph_unsupported() -> bool {
3052 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
3053}
3054
3055pub fn graph_unsupported_reset() {
3057 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
3058}
3059
3060pub fn graph_race_begin_generation() {
3061 #[cfg(feature = "gpu")]
3066 {
3067 static FLUSHED: std::sync::Once = std::sync::Once::new();
3079 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3080 if FIRST.swap(false, Ordering::Relaxed) {
3081 } else {
3083 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3084 }
3085 }
3086 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3087 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3088 return;
3089 }
3090 let (gn, cn) = (
3091 GRAPH_N[1].load(Ordering::Relaxed),
3092 GRAPH_N[0].load(Ordering::Relaxed),
3093 );
3094 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3095 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3096 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3097 let verdict = if g_avg < c_avg { 1 } else { 2 };
3098 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3099 tracing::info!(
3100 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3101 g_avg as f64 / 1e6,
3102 c_avg as f64 / 1e6,
3103 if verdict == 1 { "graph" } else { "normal path" }
3104 );
3105 return;
3106 }
3107 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3108 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3109}
3110
3111pub fn graph_race_use_graph(trusted: bool) -> bool {
3115 if trusted {
3116 return true;
3117 }
3118 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3119 1 => true,
3120 2 => false,
3121 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3122 }
3123}
3124
3125pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3130 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3131 return false;
3132 }
3133 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3134 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3135 if !first || cn == 0 {
3136 return false;
3137 }
3138 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3139 let ns = dur.as_nanos() as u64;
3140 if ns > 1_000_000_000 && ns > 4 * c_avg {
3141 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3142 tracing::info!(
3143 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3144 ns as f64 / 1e6,
3145 c_avg as f64 / 1e6
3146 );
3147 return true;
3148 }
3149 false
3150}
3151
3152pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3156 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3157 return;
3158 }
3159 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3160 if tok == 0 {
3161 return;
3162 }
3163 let i = used_graph as usize;
3164 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3165 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3166}
3167
3168pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3178 #[inline]
3179 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3180 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3181 for c in chunks.chunks_exact(8) {
3182 h ^= u64::from_le_bytes(c.try_into().unwrap());
3183 h = h.wrapping_mul(0x100_0000_01b3);
3184 }
3185 for &b in tail {
3186 h ^= b as u64;
3187 h = h.wrapping_mul(0x100_0000_01b3);
3188 }
3189 h
3190 }
3191 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3192 if data.len() <= 4096 {
3193 return fnv(h, data);
3194 }
3195 let step = (data.len() - 64) / 63;
3196 for i in 0..64 {
3197 h = fnv(h, &data[i * step..i * step + 64]);
3198 }
3199 h
3200}
3201
3202pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3205 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3206 fp_bytes(bytes)
3207}
3208
3209#[cfg(test)]
3210mod fp_tests {
3211 use super::fp_bytes;
3212
3213 #[test]
3218 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3219 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3221 let h0 = fp_bytes(&base);
3222 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3223 let mut dense = base.clone();
3226 for b in dense.iter_mut() {
3227 *b = b.wrapping_add(1);
3228 }
3229 assert_ne!(
3230 h0,
3231 fp_bytes(&dense),
3232 "a fully different tensor slipped through"
3233 );
3234 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3237 let mut small = vec![3u8; 4096];
3240 let hs = fp_bytes(&small);
3241 small[2048] ^= 1;
3242 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3243 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3245 let v = vec![9u8; n];
3246 let _ = fp_bytes(&v); }
3248 }
3249}
3250
3251pub fn bake_release() {
3255 #[cfg(feature = "gpu")]
3256 crate::gpu_wgpu::bake_release();
3257}
3258
3259pub fn bake_precision_strict(on: bool) {
3263 #[cfg(feature = "gpu")]
3264 crate::gpu_wgpu::bake_precision_strict(on);
3265 #[cfg(not(feature = "gpu"))]
3266 let _ = on;
3267}
3268
3269pub fn hostprof_encode_done(t0: std::time::Instant) {
3275 use std::sync::atomic::{AtomicU64, Ordering};
3276 static ENC: AtomicU64 = AtomicU64::new(0);
3277 static N: AtomicU64 = AtomicU64::new(0);
3278 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3279 return;
3280 }
3281 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3282 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3283 if n % 100 == 0 {
3284 eprintln!(
3285 "hostprof: encode {:.2} ms/token over {n} tokens",
3286 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3287 );
3288 }
3289}
3290
3291pub fn hostprof_total(t0: std::time::Instant) {
3292 use std::sync::atomic::{AtomicU64, Ordering};
3293 static TOT: AtomicU64 = AtomicU64::new(0);
3294 static N: AtomicU64 = AtomicU64::new(0);
3295 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3296 return;
3297 }
3298 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3299 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3300 if n % 100 == 0 {
3301 eprintln!(
3302 "hostprof: total {:.2} ms/token over {n} tokens",
3303 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3304 );
3305 }
3306}
3307
3308pub fn stageprof(stage: u32, dt: std::time::Duration) {
3312 use std::sync::atomic::{AtomicU64, Ordering};
3313 static NS: [AtomicU64; 4] = [
3314 AtomicU64::new(0),
3315 AtomicU64::new(0),
3316 AtomicU64::new(0),
3317 AtomicU64::new(0),
3318 ];
3319 static N: AtomicU64 = AtomicU64::new(0);
3320 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3321 return;
3322 }
3323 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3324 if stage == 1 {
3325 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3326 if n % 200 == 0 {
3327 eprintln!(
3328 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3329 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3330 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3331 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3332 );
3333 }
3334 }
3335}
3336
3337pub fn weight_bytes_dispatched() -> u64 {
3340 let mut total = 0u64;
3341 #[cfg(target_os = "macos")]
3342 {
3343 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3344 }
3345 #[cfg(feature = "gpu")]
3346 {
3347 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3348 }
3349 total
3350}
3351
3352pub fn weight_bytes_by() -> [u64; 6] {
3355 #[cfg(target_os = "macos")]
3356 {
3357 let mut o = [0u64; 6];
3358 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3359 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3360 }
3361 return o;
3362 }
3363 #[allow(unreachable_code)]
3364 [0; 6]
3365}
3366
3367#[cfg(test)]
3368mod probe_warmup_tests {
3369 use super::*;
3370 use std::time::Duration;
3371
3372 fn ms(v: f64) -> Duration {
3373 Duration::from_nanos((v * 1e6) as u64)
3374 }
3375
3376 #[test]
3381 fn one_cold_first_sample_does_not_lose_the_class() {
3382 let p = Probe::new();
3383 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3385 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3386 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3387 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3388 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3389 assert_eq!(
3390 p.state.load(Ordering::Relaxed),
3391 1,
3392 "the device is 3x faster once warm and must win"
3393 );
3394 }
3395
3396 #[test]
3400 fn the_warmup_is_spent_once_and_never_underflows() {
3401 let p = Probe::new();
3402 for _ in 0..8 {
3403 probe_record_into(&p, "matmat", None, true, ms(10.0));
3404 }
3405 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3406 assert_eq!(
3407 p.gpu_n.load(Ordering::Relaxed),
3408 7,
3409 "one sample burned, the rest counted"
3410 );
3411 }
3412
3413 #[test]
3419 fn a_class_whose_device_always_declines_settles_on_the_host() {
3420 let _probe_guard = probe_test_guard();
3421 let c = OpClass::MatmatWide;
3425 let p = &PROBES[c as usize];
3426 p.state.store(0, Ordering::Relaxed);
3427 p.declines.store(0, Ordering::Relaxed);
3428 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3429 probe_note_decline(c);
3430 }
3431 assert_eq!(
3432 p.state.load(Ordering::Relaxed),
3433 0,
3434 "one short of the limit is still a question, not an answer"
3435 );
3436 probe_note_decline(c);
3437 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3438 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3439 p.state.store(0, Ordering::Relaxed);
3440 p.declines.store(0, Ordering::Relaxed);
3441 }
3442
3443 #[test]
3446 fn a_slow_device_still_loses_after_the_warmup() {
3447 let p = Probe::new();
3448 for _ in 0..4 {
3449 probe_record_into(&p, "matvec", None, true, ms(40.0));
3450 }
3451 for _ in 0..4 {
3452 probe_record_into(&p, "matvec", None, false, ms(2.0));
3453 }
3454 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3455 }
3456}
3457
3458pub(crate) struct ImageStageGuard {
3461 #[cfg(target_os = "macos")]
3462 metal: Option<crate::gpu_metal::ImageStageGuard>,
3463 #[cfg(feature = "gpu")]
3464 wgpu: crate::gpu_wgpu::ImageStageGuard,
3465}
3466
3467pub(crate) fn image_stage_scope() -> ImageStageGuard {
3468 ImageStageGuard {
3469 #[cfg(target_os = "macos")]
3470 metal: if matches!(backend(), Backend::Metal) {
3471 Some(crate::gpu_metal::image_stage_scope())
3472 } else {
3473 None
3474 },
3475 #[cfg(feature = "gpu")]
3476 wgpu: crate::gpu_wgpu::image_stage_scope(),
3477 }
3478}
3479
3480impl ImageStageGuard {
3481 pub(crate) fn track_model(&mut self, uid: u64) {
3482 #[cfg(target_os = "macos")]
3483 if let Some(metal) = &mut self.metal {
3484 metal.track_model(uid);
3485 }
3486 #[cfg(feature = "gpu")]
3487 self.wgpu.track_model(uid);
3488 #[cfg(not(target_os = "macos"))]
3489 let _ = uid;
3490 }
3491}