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
296#[derive(Clone, Copy)]
308pub enum OpClass {
309 Ffn = 0,
311 Matvec = 1,
313 Matmat = 2,
315 Batch = 3,
317 MatmatWide = 4,
323 MatvecHead = 5,
330 GemmNt = 6,
337}
338
339pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
343 if rows * cols >= 67_108_864 {
344 OpClass::MatvecHead
345 } else {
346 OpClass::Matvec
347 }
348}
349
350pub enum ProbeArm {
352 Gpu,
354 CpuTimed,
356 Cpu,
358}
359
360const PROBE_SAMPLES: u32 = 6;
362
363const PROBE_DECLINE_LIMIT: u32 = 16;
367
368const PROBE_WARMUP: u32 = 1;
370
371struct Probe {
372 state: AtomicU8,
374 flip: AtomicU32,
375 gpu_ns: AtomicU64,
376 gpu_n: AtomicU32,
377 declines: AtomicU32,
387 gpu_burn: AtomicU32,
400 cpu_ns: AtomicU64,
401 cpu_n: AtomicU32,
402 gpu_min: AtomicU64,
409 cpu_min: AtomicU64,
410}
411
412impl Probe {
413 const fn new() -> Self {
414 Self {
415 state: AtomicU8::new(0),
416 flip: AtomicU32::new(0),
417 gpu_ns: AtomicU64::new(0),
418 gpu_n: AtomicU32::new(0),
419 declines: AtomicU32::new(0),
420 gpu_burn: AtomicU32::new(PROBE_WARMUP),
421 cpu_ns: AtomicU64::new(0),
422 cpu_n: AtomicU32::new(0),
423 gpu_min: AtomicU64::new(u64::MAX),
424 cpu_min: AtomicU64::new(u64::MAX),
425 }
426 }
427}
428
429static PROBES: [Probe; 7] = [
430 Probe::new(),
431 Probe::new(),
432 Probe::new(),
433 Probe::new(),
434 Probe::new(),
435 Probe::new(),
436 Probe::new(),
437];
438
439static TRUST_GPU: AtomicBool = AtomicBool::new(false);
446
447pub fn trust_gpu() -> GpuTrust {
449 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
450 GpuTrust(was)
451}
452
453pub struct GpuTrust(bool);
454
455impl Drop for GpuTrust {
456 fn drop(&mut self) {
457 TRUST_GPU.store(self.0, Ordering::Relaxed);
458 }
459}
460
461fn probe_on_for(c: OpClass) -> bool {
462 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
468 return false;
469 }
470 probe_on()
471}
472
473fn probe_on() -> bool {
474 static ON: OnceLock<bool> = OnceLock::new();
475 *ON.get_or_init(|| {
476 std::env::var("CMF_GPU_PROBE")
477 .map(|v| v != "0" && v != "off")
478 .unwrap_or(true)
479 })
480}
481
482pub fn q1_force() -> bool {
487 #[cfg(target_os = "macos")]
488 {
489 backend() == Backend::Metal
490 }
491 #[cfg(not(target_os = "macos"))]
492 {
493 false
494 }
495}
496
497pub fn fused_block_trusted() -> bool {
516 #[cfg(target_os = "macos")]
517 if backend() == Backend::Metal {
518 return true;
519 }
520 wgpu_graph_default()
521}
522
523pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
535 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
536 {
537 return crate::gpu_wgpu::weight_is_resident(model, idx);
538 }
539 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
540 {
541 let _ = (model, idx);
542 true
543 }
544}
545
546pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
547 if !weights_resident && probe_deciding(c) {
548 return ProbeArm::Gpu;
549 }
550 probe_arm(c)
551}
552
553pub fn probe_arm(c: OpClass) -> ProbeArm {
554 PROBE_COLD.with(|f| f.set(false));
559 if !probe_on_for(c) {
560 return ProbeArm::Gpu;
561 }
562 probe_cache_load();
563 let p = &PROBES[c as usize];
564 match p.state.load(Ordering::Relaxed) {
565 1 => ProbeArm::Gpu,
566 2 => ProbeArm::Cpu,
567 _ => {
568 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
569 ProbeArm::Gpu
570 } else {
571 ProbeArm::CpuTimed
572 }
573 }
574 }
575}
576
577pub fn probe_note_decline(c: OpClass) {
581 let p = &PROBES[c as usize];
582 if p.state.load(Ordering::Relaxed) != 0 {
583 return;
584 }
585 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
586 if n >= PROBE_DECLINE_LIMIT
587 && p.state
588 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
589 .is_ok()
590 {
591 tracing::info!(
592 "gpu probe [{}]: device declined {n} times → cpu",
593 CLASS_NAMES[c as usize]
594 );
595 }
596}
597
598pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
601 probe_record_into(
602 &PROBES[c as usize],
603 CLASS_NAMES[c as usize],
604 Some(c),
605 gpu,
606 dur,
607 )
608}
609
610fn probe_record_into(
613 p: &Probe,
614 class_name: &str,
615 cache: Option<OpClass>,
616 gpu: bool,
617 dur: std::time::Duration,
618) {
619 if p.state.load(Ordering::Relaxed) != 0 {
620 return;
621 }
622 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
623 return; }
625 if gpu {
626 let left = p.gpu_burn.load(Ordering::Relaxed);
630 if left > 0 {
631 p.gpu_burn.store(left - 1, Ordering::Relaxed);
632 return; }
634 }
635 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
636 if gpu {
637 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
638 p.gpu_n.fetch_add(1, Ordering::Relaxed);
639 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
640 } else {
641 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
642 p.cpu_n.fetch_add(1, Ordering::Relaxed);
643 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
644 }
645 let (gn, cn) = (
646 p.gpu_n.load(Ordering::Relaxed),
647 p.cpu_n.load(Ordering::Relaxed),
648 );
649 if gn >= 2 && cn >= 2 {
650 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
654 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
655 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
665 return;
666 }
667 let winner = if g <= cp { 1 } else { 2 };
668 if p.state
669 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
670 .is_ok()
671 {
672 tracing::info!(
673 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
674 class_name,
675 g / 1e6,
676 cp / 1e6,
677 if winner == 1 { "gpu" } else { "cpu" },
678 );
679 if let Some(c) = cache {
680 probe_cache_store(c, winner);
681 }
682 }
683 }
684}
685
686pub fn probe_deciding(c: OpClass) -> bool {
689 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
690}
691
692#[allow(unused_variables)]
702pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
703 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
704 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
705 let resident = match backend() {
706 #[cfg(target_os = "macos")]
707 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
708 #[cfg(feature = "gpu")]
709 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
710 Backend::None => false,
711 };
712 if !resident && may_upload {
713 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
714 }
715 resident
716}
717
718#[cfg(test)]
720pub(crate) fn probe_reset() {
721 for p in &PROBES {
722 p.state.store(0, Ordering::Relaxed);
723 p.flip.store(0, Ordering::Relaxed);
724 p.gpu_ns.store(0, Ordering::Relaxed);
725 p.gpu_n.store(0, Ordering::Relaxed);
726 p.cpu_ns.store(0, Ordering::Relaxed);
727 p.cpu_n.store(0, Ordering::Relaxed);
728 }
729}
730
731#[cfg(test)]
732mod probe_tests {
733 use super::*;
734 use std::time::Duration;
735
736 #[test]
739 fn probe_alternates_discards_cold_and_decides() {
740 probe_reset();
741 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
743 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
744
745 probe_note_cold();
749 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
750 for _ in 0..PROBE_SAMPLES {
751 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
752 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
753 }
754 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
755
756 for _ in 0..PROBE_SAMPLES {
758 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
759 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
760 }
761 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
762
763 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
765 CPU_ONLY.with(|c| assert!(!c.get()));
766 cpu_scope(|| {
767 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
768 CPU_ONLY.with(|c| assert!(c.get()));
769 });
770 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
771 CPU_ONLY.with(|c| assert!(!c.get()));
772 probe_reset();
773 }
774
775 #[test]
776 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
777 let mine = probe_cache_key_named("gemm-nt");
789 let state = || {
790 PROBES[OpClass::GemmNt as usize]
791 .state
792 .load(Ordering::Relaxed)
793 };
794
795 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
797 assert_eq!(state(), 0);
798 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
800 assert_ne!(older, mine);
801 probe_cache_adopt(&format!("{older}\tgpu\n"));
802 assert_eq!(state(), 0);
803 probe_cache_adopt(&format!("{mine}\tcpu\n"));
805 assert_eq!(state(), 2);
806
807 PROBES[OpClass::GemmNt as usize]
808 .state
809 .store(0, Ordering::Relaxed);
810 }
811}
812
813pub const GPU_MIN_ROWS: usize = 65_536;
816
817pub fn min_rows() -> usize {
824 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
825 .ok()
826 .and_then(|v| v.parse().ok())
827 {
828 return v;
829 }
830 if discrete() { 4096 } else { GPU_MIN_ROWS }
831}
832
833pub fn discrete() -> bool {
835 match backend() {
836 #[cfg(feature = "gpu")]
837 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
838 #[cfg(target_os = "macos")]
839 Backend::Metal => false, Backend::None => false,
841 }
842}
843
844pub struct MoeJob<'a> {
848 pub gate: (usize, usize, usize, &'a [f32]),
849 pub up: (usize, usize, usize, &'a [f32]),
850 pub down: (usize, usize, usize, &'a [f32]),
851 pub xs_gate: Vec<f32>,
852 pub xs_up: Vec<f32>,
853 pub down_col: &'a [f32],
854 pub w: f32,
855 pub q1: bool,
858 pub q4t: bool,
861 pub q4tp: bool,
865 pub gu_q2: bool,
869 pub swiglu_limit: f32,
874}
875
876pub struct BatchJob<'a> {
878 pub idx: usize,
879 pub rows: usize,
880 pub cols: usize,
881 pub row_scale: &'a [f32],
882 pub xs: Vec<f32>,
883 pub layout: BatchLayout,
887}
888
889#[derive(Clone, Copy, PartialEq, Eq, Debug)]
892pub enum BatchLayout {
893 Q8,
894 Q1,
895 Q4t,
896 Q4tp,
897}
898
899#[derive(Clone, Copy, PartialEq, Eq)]
900enum Backend {
901 None,
902 #[cfg(target_os = "macos")]
903 Metal,
904 #[cfg(feature = "gpu")]
905 Wgpu,
906}
907
908fn backend() -> Backend {
909 #[cfg(feature = "gpu")]
910 if crate::gpu_wgpu::selected() {
911 return if crate::gpu_wgpu::enabled() {
912 Backend::Wgpu
913 } else {
914 Backend::None
915 };
916 }
917 #[cfg(target_os = "macos")]
918 if crate::gpu_metal::enabled() {
919 return Backend::Metal;
920 }
921 Backend::None
922}
923
924pub fn backend_available() -> bool {
930 #[cfg(target_os = "macos")]
931 {
932 true
934 }
935 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
936 {
937 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
938 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
939 }
940 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
941 {
942 false
943 }
944}
945
946static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
952
953pub fn pause_gpu() -> GpuPause {
955 GPU_PAUSED.store(true, Ordering::Relaxed);
956 GpuPause(())
957}
958
959pub struct GpuPause(());
960
961impl Drop for GpuPause {
962 fn drop(&mut self) {
963 GPU_PAUSED.store(false, Ordering::Relaxed);
964 }
965}
966
967pub fn enabled() -> bool {
968 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
969}
970
971pub fn wgpu_active() -> bool {
985 #[cfg(feature = "gpu")]
986 {
987 matches!(backend(), Backend::Wgpu)
988 }
989 #[cfg(not(feature = "gpu"))]
990 {
991 false
992 }
993}
994
995pub fn default_device() -> usize {
1002 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1003 *D.get_or_init(|| {
1004 std::env::var("CMF_GPU_ADAPTER")
1005 .ok()
1006 .and_then(|v| v.trim().parse::<usize>().ok())
1007 .unwrap_or(0)
1008 })
1009}
1010
1011thread_local! {
1012 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1013}
1014
1015pub fn current_device() -> usize {
1017 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1018}
1019
1020pub fn set_current_device(i: usize) {
1024 CUR_DEV.with(|c| c.set(Some(i)));
1025}
1026
1027pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1029 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1030 let r = f();
1031 CUR_DEV.with(|c| c.set(prev));
1032 r
1033}
1034
1035pub fn device_count() -> usize {
1038 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1039 {
1040 return crate::gpu_wgpu::adapter_count();
1041 }
1042 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1043 {
1044 usize::from(backend_available())
1045 }
1046}
1047
1048pub fn vram_budget() -> u64 {
1052 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1053 {
1054 return crate::gpu_wgpu::device_vram_budget();
1055 }
1056 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1057 {
1058 if backend_available() { u64::MAX } else { 0 }
1059 }
1060}
1061
1062pub fn resident_bytes() -> u64 {
1066 #[cfg(feature = "gpu")]
1067 {
1068 if backend() == Backend::Wgpu {
1069 return crate::gpu_wgpu::resident_bytes();
1070 }
1071 }
1072 0
1073}
1074
1075pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1079 #[cfg(feature = "gpu")]
1080 {
1081 if backend() == Backend::Wgpu {
1082 return crate::gpu_wgpu::o1_device_stats(kv_id);
1083 }
1084 }
1085 let _ = kv_id;
1086 (0, 0)
1087}
1088
1089pub fn upload_bytes() -> u64 {
1093 #[cfg(feature = "gpu")]
1094 {
1095 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1096 }
1097 #[cfg(not(feature = "gpu"))]
1098 0
1099}
1100
1101pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1105 #[cfg(feature = "gpu")]
1106 {
1107 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1108 }
1109 let _ = (block, rounds);
1110 None
1111}
1112
1113#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1125pub enum GraphPhase {
1126 Prefill,
1127 Decode,
1128}
1129
1130pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1138 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1139 Some("0") => false,
1140 Some("prefill") => phase == GraphPhase::Prefill,
1141 Some(_) => true,
1142 None => {
1143 if wgpu_graph_default() {
1144 return true;
1145 }
1146 let _ = phase;
1151 false
1152 }
1153 }
1154}
1155
1156pub fn wgpu_graph_default() -> bool {
1157 #[cfg(feature = "gpu")]
1158 {
1159 matches!(backend(), Backend::Wgpu)
1165 && (crate::gpu_wgpu::discrete_active()
1166 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1167 }
1168 #[cfg(not(feature = "gpu"))]
1169 {
1170 false
1171 }
1172}
1173
1174#[allow(clippy::too_many_arguments, unused_variables)]
1176pub fn q8_matvec_range(
1177 model: &Arc<CmfModel>,
1178 idx: usize,
1179 row0: usize,
1180 row_scale: &[f32],
1181 xs: &[f32],
1182 rows: usize,
1183 cols: usize,
1184 out: &mut [f32],
1185) -> bool {
1186 match backend() {
1187 #[cfg(target_os = "macos")]
1188 Backend::Metal => {
1189 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1190 }
1191 #[cfg(feature = "gpu")]
1192 Backend::Wgpu => {
1193 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1194 }
1195 Backend::None => false,
1196 }
1197}
1198
1199#[allow(clippy::too_many_arguments, unused_variables)]
1202#[allow(clippy::too_many_arguments)]
1206pub fn q8_matmat_2f(
1207 model: &Arc<CmfModel>,
1208 idx: usize,
1209 row_scale: &[f32],
1210 col_field: &[f32],
1211 xs: &[f32],
1212 b: usize,
1213 rows: usize,
1214 cols: usize,
1215 out: &mut [f32],
1216) -> bool {
1217 #[allow(unreachable_patterns)]
1218 match backend() {
1219 #[cfg(feature = "gpu")]
1220 Backend::Wgpu => {
1221 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1222 }
1223 _ => false,
1224 }
1225}
1226
1227pub fn q8_matmat(
1228 model: &Arc<CmfModel>,
1229 idx: usize,
1230 row_scale: &[f32],
1231 pre: &[f32],
1232 b: usize,
1233 rows: usize,
1234 cols: usize,
1235 out: &mut [f32],
1236) -> bool {
1237 match backend() {
1238 #[cfg(target_os = "macos")]
1239 Backend::Metal => {
1240 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1241 }
1242 #[cfg(feature = "gpu")]
1243 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1244 Backend::None => false,
1245 }
1246}
1247
1248#[allow(unused_variables)]
1251pub fn q1_matvec(
1252 model: &Arc<CmfModel>,
1253 idx: usize,
1254 xs: &[f32],
1255 rows: usize,
1256 cols: usize,
1257 out: &mut [f32],
1258) -> bool {
1259 match backend() {
1260 #[cfg(target_os = "macos")]
1261 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1262 #[cfg(feature = "gpu")]
1263 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1264 Backend::None => false,
1265 }
1266}
1267
1268#[allow(clippy::too_many_arguments)]
1272pub fn attn_dropin(
1273 model: &Arc<CmfModel>,
1274 kv_id: u64,
1275 layer: usize,
1276 normed: &[f32],
1277 wq_idx: usize,
1278 wk_idx: usize,
1279 wv_idx: usize,
1280 wo_idx: usize,
1281 q_norm: Option<&[f32]>,
1282 k_norm: Option<&[f32]>,
1283 invf: &[f32],
1284 nh: usize,
1285 nkv: usize,
1286 hd: usize,
1287 rd: usize,
1288 hidden: usize,
1289 pos: usize,
1290 cap: usize,
1291 gemma: bool,
1292 eps: f32,
1293 cpu_k: &[Vec<f32>],
1294 cpu_v: &[Vec<f32>],
1295 out: &mut [f32],
1296) -> bool {
1297 match backend() {
1298 #[cfg(feature = "gpu")]
1299 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1300 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1301 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1302 ),
1303 #[allow(unused_variables)]
1304 _ => false,
1305 }
1306}
1307
1308pub struct GraphW<'a> {
1312 pub idx: usize,
1313 pub kind: u8,
1314 pub row_scale: &'a [f32],
1315 pub data: &'a [f32],
1316}
1317
1318pub enum GraphAttn<'a> {
1321 Full {
1322 wq: GraphW<'a>,
1323 wk: GraphW<'a>,
1324 wv: GraphW<'a>,
1325 wo: GraphW<'a>,
1326 q_norm: Option<&'a [f32]>,
1327 k_norm: Option<&'a [f32]>,
1328 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1330 output_gate: bool,
1333 cpu_k: &'a [Vec<f32>],
1334 cpu_v: &'a [Vec<f32>],
1335 },
1336 Gdn {
1337 qkv: GraphW<'a>,
1338 z: GraphW<'a>,
1339 a: GraphW<'a>,
1340 b: GraphW<'a>,
1341 out: GraphW<'a>,
1342 conv1d: &'a [f32],
1343 a_log: &'a [f32],
1344 dt_bias: &'a [f32],
1345 norm: &'a [f32],
1346 nv: usize,
1347 nk: usize,
1348 dk: usize,
1349 dv: usize,
1350 kk: usize,
1351 cpu_state: &'a [f32],
1356 },
1357 ShortConv {
1364 inp: GraphW<'a>,
1366 out: GraphW<'a>,
1368 taps: &'a [f32],
1371 kernel: usize,
1372 cpu_state: &'a [f32],
1376 },
1377}
1378
1379pub struct GraphLayer<'a> {
1381 pub input_norm: &'a [f32],
1382 pub attn: GraphAttn<'a>,
1383 pub post_norm: &'a [f32],
1384 pub ffn: GraphFfn<'a>,
1385}
1386
1387pub enum GraphFfn<'a> {
1392 Dense {
1393 gate: GraphW<'a>,
1394 up: GraphW<'a>,
1395 down: GraphW<'a>,
1396 },
1397 Moe {
1398 router: GraphW<'a>,
1400 shared_gate: GraphW<'a>,
1402 experts: Vec<(usize, usize, usize)>,
1406 n_exp: usize,
1408 top_k: usize,
1409 inter: usize,
1410 norm_topk: bool,
1411 q4tp: bool,
1417 gu_q2: bool,
1421 sigmoid: bool,
1425 bias: Option<&'a [f32]>,
1428 has_shared: bool,
1432 },
1433}
1434
1435#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1438pub enum TokenGraphOutcome {
1439 Declined,
1441 Completed,
1443 Failed,
1445}
1446
1447#[allow(clippy::too_many_arguments)]
1452pub fn forward_token_graph(
1453 model: &Arc<CmfModel>,
1454 kv_id: u64,
1455 layers: &[GraphLayer],
1456 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1459 o1_epoch: u64,
1460 invf: &[f32],
1461 h: &mut [f32],
1462 nh: usize,
1463 nkv: usize,
1464 hd: usize,
1465 attn_scale: f32,
1466 rd: usize,
1467 hidden: usize,
1468 inter: usize,
1469 position: usize,
1470 cap: usize,
1471 gemma: bool,
1472 eps: f32,
1473 lm_head: Option<(&GraphW, usize)>,
1474 final_norm: &[f32],
1475 logits: &mut Vec<f32>,
1476 loop_norm_at: &[usize],
1477 steps: usize,
1478 embed: Option<(&GraphW, usize, f32)>,
1479 ids_out: Option<&mut Vec<u32>>,
1480 layers_run: Option<&mut usize>,
1483 layer_base: usize,
1487 hidden_too: bool,
1489) -> TokenGraphOutcome {
1490 match backend() {
1491 #[cfg(feature = "gpu")]
1492 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1493 model,
1494 kv_id,
1495 layers,
1496 o1,
1497 o1_epoch,
1498 invf,
1499 h,
1500 nh,
1501 nkv,
1502 hd,
1503 attn_scale,
1504 rd,
1505 hidden,
1506 inter,
1507 position,
1508 cap,
1509 gemma,
1510 eps,
1511 lm_head,
1512 final_norm,
1513 logits,
1514 loop_norm_at,
1515 steps,
1516 embed,
1517 ids_out,
1518 layers_run,
1519 layer_base,
1520 hidden_too,
1521 ),
1522 #[allow(unused_variables)]
1523 _ => {
1524 let _ = (
1525 attn_scale,
1526 lm_head,
1527 final_norm,
1528 logits,
1529 loop_norm_at,
1530 layers_run,
1531 layer_base,
1532 hidden_too,
1533 );
1534 TokenGraphOutcome::Declined
1535 }
1536 }
1537}
1538
1539#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1543pub enum BatchGraphOutcome {
1544 Declined,
1547 Completed,
1549 Failed,
1553}
1554
1555pub struct SpecTail<'a> {
1556 pub lm: GraphW<'a>,
1557 pub lm_rows: usize,
1558 pub final_norm: &'a [f32],
1559 pub logits_out: &'a mut Vec<f32>,
1560}
1561
1562#[allow(clippy::too_many_arguments)]
1566pub fn forward_batch_graph(
1567 model: &Arc<CmfModel>,
1568 kv_id: u64,
1569 layers: &[GraphLayer],
1570 invf: &[f32],
1571 h: &mut [f32],
1572 nh: usize,
1573 nkv: usize,
1574 hd: usize,
1575 rd: usize,
1576 hidden: usize,
1577 inter: usize,
1578 positions: &[usize],
1579 cap: usize,
1580 gemma: bool,
1581 eps: f32,
1582 attn_scale: f32,
1583 k: usize,
1584 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1587 o1_epoch: u64,
1588 spec: Option<SpecTail<'_>>,
1589) -> BatchGraphOutcome {
1590 match backend() {
1591 #[cfg(feature = "gpu")]
1592 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1593 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1594 eps, attn_scale, k, o1, o1_epoch, spec,
1595 ),
1596 #[allow(unreachable_patterns)]
1597 _ => {
1598 let _ = (o1, o1_epoch, spec);
1599 BatchGraphOutcome::Declined
1600 }
1601 }
1602}
1603
1604pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1609 #[cfg(feature = "gpu")]
1610 if backend() == Backend::Wgpu {
1611 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1612 }
1613 #[allow(unreachable_code)]
1614 {
1615 let _ = (kv_id, slot, base_pos, expected_layers);
1616 false
1617 }
1618}
1619
1620pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1626 #[cfg(feature = "gpu")]
1627 if backend() == Backend::Wgpu {
1628 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1629 }
1630 #[cfg(target_os = "macos")]
1631 if backend() == Backend::Metal {
1632 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1633 return true;
1634 }
1635 false
1636}
1637
1638pub fn graph_kv_reset(_kv_id: u64) {
1640 #[cfg(feature = "gpu")]
1641 if backend() == Backend::Wgpu {
1642 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1643 }
1644}
1645
1646pub fn q1t_matvec(
1650 model: &Arc<CmfModel>,
1651 idx: usize,
1652 xs: &[f32],
1653 rows: usize,
1654 cols: usize,
1655 out: &mut [f32],
1656) -> bool {
1657 match backend() {
1658 #[cfg(target_os = "macos")]
1659 Backend::Metal => {
1660 if metal_q1t_enabled() {
1661 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1662 } else {
1663 false
1664 }
1665 }
1666 #[cfg(feature = "gpu")]
1667 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1668 Backend::None => false,
1669 }
1670}
1671
1672#[allow(unused_variables)]
1675pub fn q4b_matvec(
1676 model: &Arc<CmfModel>,
1677 idx: usize,
1678 xs: &[f32],
1679 rows: usize,
1680 cols: usize,
1681 out: &mut [f32],
1682) -> bool {
1683 match backend() {
1684 #[cfg(target_os = "macos")]
1685 Backend::Metal => false,
1686 #[cfg(feature = "gpu")]
1687 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1688 Backend::None => false,
1689 }
1690}
1691
1692pub fn q1t_matmat(
1695 model: &Arc<CmfModel>,
1696 idx: usize,
1697 xs: &[f32],
1698 b: usize,
1699 rows: usize,
1700 cols: usize,
1701 out: &mut [f32],
1702) -> bool {
1703 match backend() {
1704 #[cfg(target_os = "macos")]
1705 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1709 #[cfg(feature = "gpu")]
1710 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1711 Backend::None => false,
1712 }
1713}
1714
1715#[cfg(target_os = "macos")]
1719pub(crate) fn metal_q1t_enabled() -> bool {
1720 std::env::var("CMF_METAL_Q1T")
1721 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1722 .unwrap_or(true)
1723}
1724
1725pub fn q1_matmat(
1727 model: &Arc<CmfModel>,
1728 idx: usize,
1729 xs: &[f32],
1730 b: usize,
1731 rows: usize,
1732 cols: usize,
1733 out: &mut [f32],
1734) -> bool {
1735 match backend() {
1736 #[cfg(feature = "gpu")]
1737 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1738 #[allow(unused_variables)]
1739 _ => false,
1740 }
1741}
1742
1743static MM_KILL: AtomicBool = AtomicBool::new(false);
1748pub(crate) fn mm_killed() -> bool {
1749 MM_KILL.load(Ordering::Relaxed)
1750}
1751pub(crate) fn mm_kill() {
1752 MM_KILL.store(true, Ordering::Relaxed);
1753}
1754
1755static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1762const MM_STRIKES_TO_KILL: u32 = 3;
1763static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1770
1771pub fn mm_kill_arm(on: bool) {
1774 MM_ARMED.store(on, Ordering::Relaxed);
1775 if on {
1776 MM_STRIKES.store(0, Ordering::Relaxed);
1777 }
1778}
1779
1780pub(crate) fn mm_budget_check(
1787 what: &str,
1788 el: std::time::Duration,
1789 budget: std::time::Duration,
1790 exempt: bool,
1791) {
1792 if el <= budget {
1793 MM_STRIKES.store(0, Ordering::Relaxed);
1794 return;
1795 }
1796 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1797 return;
1798 }
1799 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1800 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1801 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1802 if !on {
1803 tracing::info!(
1804 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1805 );
1806 return;
1807 }
1808 if n >= MM_STRIKES_TO_KILL {
1809 tracing::warn!(
1810 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1811 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1812 );
1813 mm_kill();
1814 } else {
1815 tracing::info!(
1816 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1817 );
1818 }
1819}
1820
1821#[allow(unused_variables, clippy::too_many_arguments)]
1826pub fn chunk_attend(
1827 q: &[f32],
1828 k: &[&[f32]],
1829 v: &[&[f32]],
1830 b: usize,
1831 s0: usize,
1832 nh: usize,
1833 nkv: usize,
1834 hd: usize,
1835 scale: f32,
1836 out: &mut [f32],
1837) -> bool {
1838 match backend() {
1839 #[cfg(feature = "gpu")]
1840 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1841 #[allow(unreachable_patterns)]
1842 _ => false,
1843 }
1844}
1845
1846#[allow(unused_variables, clippy::too_many_arguments)]
1850pub fn q4t_qkv(
1851 model: &Arc<CmfModel>,
1852 wq: usize,
1853 wk: usize,
1854 wv: usize,
1855 xs: &[f32],
1856 b: usize,
1857 cols: usize,
1858 rq: usize,
1859 rk: usize,
1860 rv: usize,
1861 out: &mut [f32],
1862) -> bool {
1863 match backend() {
1864 #[cfg(feature = "gpu")]
1865 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1866 #[allow(unreachable_patterns)]
1867 _ => false,
1868 }
1869}
1870
1871#[allow(unused_variables, clippy::too_many_arguments)]
1873#[allow(clippy::too_many_arguments, unused_variables)]
1877pub fn q4tp_ffn_packed(
1878 model: &Arc<CmfModel>,
1879 w1: usize,
1880 w2: usize,
1881 xs: &[f32],
1882 b: usize,
1883 hidden: usize,
1884 inter: usize,
1885 bias: Option<&[f32]>,
1886 out: &mut [f32],
1887) -> bool {
1888 match backend() {
1889 #[cfg(feature = "gpu")]
1890 Backend::Wgpu => {
1891 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1892 }
1893 #[allow(unreachable_patterns)]
1894 _ => false,
1895 }
1896}
1897
1898pub fn q4tp_ffn(
1899 model: &Arc<CmfModel>,
1900 w1: usize,
1901 w3: usize,
1902 w2: usize,
1903 xs: &[f32],
1904 b: usize,
1905 hidden: usize,
1906 inter: usize,
1907 out: &mut [f32],
1908) -> bool {
1909 match backend() {
1910 #[cfg(target_os = "macos")]
1911 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1912 #[cfg(feature = "gpu")]
1913 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1914 #[allow(unreachable_patterns)]
1915 _ => false,
1916 }
1917}
1918
1919#[allow(clippy::too_many_arguments, unused_variables)]
1924pub fn q4tp_gelu_ffn(
1925 model: &Arc<CmfModel>,
1926 w_in: usize,
1927 w_out: usize,
1928 xs: &[f32],
1929 b: usize,
1930 hidden: usize,
1931 inter: usize,
1932 bias_in: &[f32],
1933 bias_out: &[f32],
1934 out: &mut [f32],
1935) -> bool {
1936 match backend() {
1937 #[cfg(feature = "gpu")]
1938 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
1939 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
1940 ),
1941 #[allow(unreachable_patterns)]
1942 _ => false,
1943 }
1944}
1945
1946pub fn q4t_ffn(
1947 model: &Arc<CmfModel>,
1948 w1: usize,
1949 w3: usize,
1950 w2: usize,
1951 xs: &[f32],
1952 b: usize,
1953 hidden: usize,
1954 inter: usize,
1955 out: &mut [f32],
1956) -> bool {
1957 match backend() {
1958 #[cfg(target_os = "macos")]
1959 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1960 #[cfg(feature = "gpu")]
1961 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1962 #[allow(unreachable_patterns)]
1963 _ => false,
1964 }
1965}
1966
1967pub struct DitBlockArgs<'a> {
1972 pub n: usize,
1973 pub hidden: usize,
1974 pub inter: usize,
1975 pub nh: usize,
1976 pub nkv: usize,
1977 pub hd: usize,
1978 pub eps: f32,
1979 pub rope_cos: &'a [f32],
1980 pub rope_sin: &'a [f32],
1981 pub norm1: &'a [f32],
1982 pub norm2: &'a [f32],
1983 pub ffn_norm1: &'a [f32],
1984 pub ffn_norm2: &'a [f32],
1985 pub norm_q: &'a [f32],
1986 pub norm_k: &'a [f32],
1987 pub s_msa: &'a [f32],
1988 pub gate_msa: &'a [f32],
1989 pub s_mlp: &'a [f32],
1990 pub gate_mlp: &'a [f32],
1991 pub wq: usize,
1992 pub wk: usize,
1993 pub wv: usize,
1994 pub wo: usize,
1995 pub w1: usize,
1996 pub w3: usize,
1997 pub w2: usize,
1998 pub q4tp: bool,
2002 pub resident_in: bool,
2005 pub resident_out: bool,
2009}
2010
2011pub fn dit_chain_supported() -> bool {
2015 #[cfg(feature = "gpu")]
2016 {
2017 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2018 }
2019 #[allow(unreachable_code)]
2020 false
2021}
2022
2023pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2026 #[cfg(feature = "gpu")]
2027 {
2028 if matches!(backend(), Backend::Wgpu) {
2029 return crate::gpu_wgpu::dit_state_fetch(_x);
2030 }
2031 }
2032 false
2033}
2034
2035#[allow(unused_variables)]
2039#[allow(unused_variables, clippy::too_many_arguments)]
2043pub fn dit_qkv(
2044 model: &Arc<CmfModel>,
2045 wq: usize,
2046 wk: usize,
2047 wv: usize,
2048 xs: &[f32],
2049 b: usize,
2050 hidden: usize,
2051 qrows: usize,
2052 kvrows: usize,
2053 q_out: &mut [f32],
2054 k_out: &mut [f32],
2055 v_out: &mut [f32],
2056) -> bool {
2057 match backend() {
2058 #[cfg(feature = "gpu")]
2059 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2060 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2061 ),
2062 #[allow(unreachable_patterns)]
2063 _ => false,
2064 }
2065}
2066
2067pub struct QwenImageAttentionArgs<'a> {
2075 pub image: &'a [f32],
2076 pub text: &'a [f32],
2077 pub image_tokens: usize,
2078 pub text_tokens: usize,
2079 pub heads: usize,
2080 pub head_dim: usize,
2081 pub image_q: usize,
2082 pub image_k: usize,
2083 pub image_v: usize,
2084 pub text_q: usize,
2085 pub text_k: usize,
2086 pub text_v: usize,
2087 pub image_out: usize,
2088 pub text_out: usize,
2089 pub image_q_norm: &'a [f32],
2090 pub image_k_norm: &'a [f32],
2091 pub text_q_norm: &'a [f32],
2092 pub text_k_norm: &'a [f32],
2093 pub image_cos: &'a [f32],
2094 pub image_sin: &'a [f32],
2095 pub text_cos: &'a [f32],
2096 pub text_sin: &'a [f32],
2097 pub image_q_bias: &'a [f32],
2098 pub image_k_bias: &'a [f32],
2099 pub image_v_bias: &'a [f32],
2100 pub text_q_bias: &'a [f32],
2101 pub text_k_bias: &'a [f32],
2102 pub text_v_bias: &'a [f32],
2103 pub image_out_bias: &'a [f32],
2104 pub text_out_bias: &'a [f32],
2105 pub image_proj: &'a mut [f32],
2106 pub text_proj: &'a mut [f32],
2107}
2108
2109#[allow(clippy::too_many_fields)]
2114pub struct QwenImageChainBlock<'a> {
2115 pub image_mod: &'a [f32],
2116 pub text_mod: &'a [f32],
2117 pub image_q: usize,
2118 pub image_k: usize,
2119 pub image_v: usize,
2120 pub text_q: usize,
2121 pub text_k: usize,
2122 pub text_v: usize,
2123 pub image_out: usize,
2124 pub text_out: usize,
2125 pub image_q_norm: &'a [f32],
2126 pub image_k_norm: &'a [f32],
2127 pub text_q_norm: &'a [f32],
2128 pub text_k_norm: &'a [f32],
2129 pub image_q_bias: &'a [f32],
2130 pub image_k_bias: &'a [f32],
2131 pub image_v_bias: &'a [f32],
2132 pub text_q_bias: &'a [f32],
2133 pub text_k_bias: &'a [f32],
2134 pub text_v_bias: &'a [f32],
2135 pub image_out_bias: &'a [f32],
2136 pub text_out_bias: &'a [f32],
2137 pub image_attn_gate: &'a [f32],
2138 pub text_attn_gate: &'a [f32],
2139 pub image_mlp_in: usize,
2140 pub image_mlp_out: usize,
2141 pub text_mlp_in: usize,
2142 pub text_mlp_out: usize,
2143 pub image_mlp_in_bias: &'a [f32],
2144 pub image_mlp_out_bias: &'a [f32],
2145 pub text_mlp_in_bias: &'a [f32],
2146 pub text_mlp_out_bias: &'a [f32],
2147}
2148
2149#[allow(clippy::too_many_fields)]
2155pub struct QwenImageBlockArgs<'a> {
2156 pub image: &'a mut [f32],
2159 pub text: &'a mut [f32],
2160 pub image_norm: &'a [f32],
2161 pub text_norm: &'a [f32],
2162 pub image_tokens: usize,
2163 pub text_tokens: usize,
2164 pub heads: usize,
2165 pub head_dim: usize,
2166 pub image_cos: &'a [f32],
2167 pub image_sin: &'a [f32],
2168 pub text_cos: &'a [f32],
2169 pub text_sin: &'a [f32],
2170 pub image_q: usize,
2171 pub image_k: usize,
2172 pub image_v: usize,
2173 pub text_q: usize,
2174 pub text_k: usize,
2175 pub text_v: usize,
2176 pub image_out: usize,
2177 pub text_out: usize,
2178 pub image_q_norm: &'a [f32],
2179 pub image_k_norm: &'a [f32],
2180 pub text_q_norm: &'a [f32],
2181 pub text_k_norm: &'a [f32],
2182 pub image_q_bias: &'a [f32],
2183 pub image_k_bias: &'a [f32],
2184 pub image_v_bias: &'a [f32],
2185 pub text_q_bias: &'a [f32],
2186 pub text_k_bias: &'a [f32],
2187 pub text_v_bias: &'a [f32],
2188 pub image_out_bias: &'a [f32],
2189 pub text_out_bias: &'a [f32],
2190 pub image_attn_gate: &'a [f32],
2191 pub text_attn_gate: &'a [f32],
2192 pub image_mlp_in: usize,
2193 pub image_mlp_out: usize,
2194 pub text_mlp_in: usize,
2195 pub text_mlp_out: usize,
2196 pub image_mlp_in_bias: &'a [f32],
2197 pub image_mlp_out_bias: &'a [f32],
2198 pub text_mlp_in_bias: &'a [f32],
2199 pub text_mlp_out_bias: &'a [f32],
2200 pub image_mlp_mod: &'a [f32],
2201 pub text_mlp_mod: &'a [f32],
2202 pub image_mlp_gate: &'a [f32],
2203 pub text_mlp_gate: &'a [f32],
2204}
2205
2206pub struct QwenImageChainArgs<'a> {
2211 pub image: &'a mut [f32],
2212 pub text: &'a mut [f32],
2213 pub image_tokens: usize,
2214 pub text_tokens: usize,
2215 pub heads: usize,
2216 pub head_dim: usize,
2217 pub image_cos: &'a [f32],
2218 pub image_sin: &'a [f32],
2219 pub text_cos: &'a [f32],
2220 pub text_sin: &'a [f32],
2221 pub blocks: &'a [QwenImageChainBlock<'a>],
2222}
2223
2224#[allow(unused_variables)]
2225pub fn qwen_image_attention(
2226 model: &Arc<CmfModel>,
2227 args: &mut QwenImageAttentionArgs<'_>,
2228) -> bool {
2229 match backend() {
2230 #[cfg(feature = "gpu")]
2231 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2232 #[allow(unreachable_patterns)]
2233 _ => false,
2234 }
2235}
2236
2237#[allow(unused_variables)]
2238pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2239 match backend() {
2240 #[cfg(feature = "gpu")]
2241 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2242 #[allow(unreachable_patterns)]
2243 _ => false,
2244 }
2245}
2246
2247#[allow(unused_variables)]
2251pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2252 match backend() {
2253 #[cfg(feature = "gpu")]
2254 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2255 #[allow(unreachable_patterns)]
2256 _ => false,
2257 }
2258}
2259
2260#[allow(unused_variables, clippy::too_many_arguments)]
2267pub fn qwen_image_mlp_inplace(
2268 model: &Arc<CmfModel>,
2269 w_in: usize,
2270 w_out: usize,
2271 data: &mut [f32],
2272 batch: usize,
2273 hidden: usize,
2274 inter: usize,
2275 bias_in: &[f32],
2276 bias_out: &[f32],
2277 modulation: &[f32],
2278 gate: &[f32],
2279) -> bool {
2280 match backend() {
2281 #[cfg(feature = "gpu")]
2282 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2283 model,
2284 w_in,
2285 w_out,
2286 data,
2287 batch,
2288 hidden,
2289 inter,
2290 bias_in,
2291 bias_out,
2292 modulation,
2293 gate,
2294 ),
2295 #[allow(unreachable_patterns)]
2296 _ => false,
2297 }
2298}
2299
2300pub fn fused_dit_block_available() -> bool {
2304 #[cfg(target_os = "macos")]
2305 {
2306 matches!(backend(), Backend::Metal) && fused_block_trusted()
2307 }
2308 #[cfg(not(target_os = "macos"))]
2309 {
2310 false
2311 }
2312}
2313
2314pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2315 dit_block_seg(model, a, &[a.n], x)
2316}
2317
2318pub fn dit_block_seg(
2322 model: &Arc<CmfModel>,
2323 a: &DitBlockArgs,
2324 segs: &[usize],
2325 x: &mut [f32],
2326) -> bool {
2327 match backend() {
2328 #[cfg(target_os = "macos")]
2329 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2330 #[cfg(feature = "gpu")]
2337 Backend::Wgpu
2338 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2339 Some("0") => false,
2340 Some(_) => true,
2341 None => crate::gpu_wgpu::discrete_active(),
2342 } =>
2343 {
2344 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2345 }
2346 #[allow(unreachable_patterns)]
2347 _ => false,
2348 }
2349}
2350
2351pub struct VaeResnetArgs<'a> {
2355 pub groups: usize,
2356 pub ic: usize,
2357 pub oc: usize,
2358 pub h: usize,
2359 pub w: usize,
2360 pub n1w: &'a [f32],
2361 pub n1b: &'a [f32],
2362 pub c1w: &'a [f32],
2363 pub c1b: &'a [f32],
2364 pub c1k: usize,
2365 pub n2w: &'a [f32],
2366 pub n2b: &'a [f32],
2367 pub c2w: &'a [f32],
2368 pub c2b: &'a [f32],
2369 pub c2k: usize,
2370 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2371}
2372
2373#[allow(unused_variables)]
2376pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2377 match backend() {
2378 #[cfg(target_os = "macos")]
2379 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2380 _ => false,
2381 }
2382}
2383
2384#[allow(unused_variables, clippy::too_many_arguments)]
2387pub fn vae_upsample_conv(
2388 w: &[f32],
2389 bias: &[f32],
2390 x: &[f32],
2391 ic: usize,
2392 oc: usize,
2393 h: usize,
2394 w_img: usize,
2395 k: usize,
2396 out: &mut [f32],
2397) -> bool {
2398 match backend() {
2399 #[cfg(target_os = "macos")]
2400 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2401 #[cfg(feature = "gpu")]
2402 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2403 #[allow(unreachable_patterns)]
2404 _ => false,
2405 }
2406}
2407
2408#[allow(unused_variables, clippy::too_many_arguments)]
2411pub fn vae_conv2d(
2412 w: &[f32],
2413 bias: &[f32],
2414 x: &[f32],
2415 ic: usize,
2416 oc: usize,
2417 h: usize,
2418 w_img: usize,
2419 k: usize,
2420 out: &mut [f32],
2421) -> bool {
2422 match backend() {
2423 #[cfg(target_os = "macos")]
2424 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2425 #[cfg(feature = "gpu")]
2426 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2427 #[allow(unreachable_patterns)]
2428 _ => false,
2429 }
2430}
2431
2432#[allow(unused_variables, clippy::too_many_arguments)]
2436#[allow(unused_variables)]
2440#[allow(clippy::too_many_arguments)]
2441#[allow(clippy::too_many_arguments, unused_variables)]
2444pub fn dit_qkv_attention(
2445 model: &Arc<CmfModel>,
2446 qkv_idx: usize,
2447 xn: &[f32],
2448 n: usize,
2449 hidden: usize,
2450 nh: usize,
2451 hd: usize,
2452 scale: f32,
2453 nr: (&[f32], &[f32], &[f32], f32),
2454 out: &mut [f32],
2455) -> bool {
2456 match backend() {
2457 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2458 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2459 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2460 ),
2461 #[allow(unreachable_patterns)]
2462 _ => false,
2463 }
2464}
2465
2466#[allow(clippy::too_many_arguments)]
2469pub fn dit_qkv_attn_out(
2470 model: &Arc<CmfModel>,
2471 qkv_idx: usize,
2472 out_idx: usize,
2473 xn: &[f32],
2474 n: usize,
2475 hidden: usize,
2476 nh: usize,
2477 hd: usize,
2478 scale: f32,
2479 nr: (&[f32], &[f32], &[f32], f32),
2480 proj: &mut [f32],
2481) -> bool {
2482 match backend() {
2483 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2484 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2485 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2486 ),
2487 #[allow(unreachable_patterns)]
2488 _ => false,
2489 }
2490}
2491
2492#[allow(clippy::too_many_arguments)]
2494pub fn vae_qkv_attn_out(
2495 model: &Arc<CmfModel>,
2496 qkv_idx: usize,
2497 out_idx: usize,
2498 xn: &[f32],
2499 n: usize,
2500 dim: usize,
2501 nh: usize,
2502 hd: usize,
2503 scale: f32,
2504 angles: &[f32],
2505 eps: f32,
2506 qkv_bias: &[f32],
2507 proj: &mut [f32],
2508) -> bool {
2509 match backend() {
2510 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2511 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2512 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2513 ),
2514 #[allow(unreachable_patterns)]
2515 _ => false,
2516 }
2517}
2518
2519#[allow(clippy::too_many_arguments)]
2520pub fn vae_attention_packed(
2521 qkv: &[f32],
2522 nh: usize,
2523 n: usize,
2524 hd: usize,
2525 scale: f32,
2526 angles: &[f32],
2527 eps: f32,
2528 out: &mut [f32],
2529) -> bool {
2530 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2531}
2532
2533#[allow(clippy::too_many_arguments)]
2534pub fn vae_attention_packed_layout(
2535 qkv: &[f32],
2536 nh: usize,
2537 n: usize,
2538 hd: usize,
2539 scale: f32,
2540 angles: &[f32],
2541 eps: f32,
2542 out: &mut [f32],
2543 layout: u32,
2544) -> bool {
2545 match backend() {
2546 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2547 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2548 qkv, nh, n, hd, scale, angles, eps, out, layout,
2549 ),
2550 #[allow(unreachable_patterns)]
2551 _ => false,
2552 }
2553}
2554
2555#[allow(clippy::too_many_arguments)]
2556pub fn dit_split_only(
2557 qkv: &[f32],
2558 nh: usize,
2559 n: usize,
2560 hd: usize,
2561 layout: u32,
2562 norm: Option<(&[f32], f32)>,
2563 out_q: &mut [f32],
2564) -> bool {
2565 match backend() {
2566 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2567 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2568 #[allow(unreachable_patterns)]
2569 _ => false,
2570 }
2571}
2572
2573pub fn gemm_nt_f32_transient(
2581 x: &[f32],
2582 w: &[f32],
2583 y: &mut [f32],
2584 n: usize,
2585 k: usize,
2586 m: usize,
2587) -> bool {
2588 match backend() {
2589 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2590 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2591 #[allow(unreachable_patterns)]
2592 _ => false,
2593 }
2594}
2595
2596pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2597 match backend() {
2598 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2599 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2600 #[allow(unreachable_patterns)]
2601 _ => false,
2602 }
2603}
2604
2605#[allow(clippy::too_many_arguments)]
2608pub fn music3_ffn(
2609 model: &std::sync::Arc<CmfModel>,
2610 idx_in: usize,
2611 idx_out: usize,
2612 h: &[f32],
2613 bias_in: &[f32],
2614 n: usize,
2615 hs: usize,
2616 inter: usize,
2617 out: &mut [f32],
2618) -> bool {
2619 match backend() {
2620 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2621 Backend::Wgpu => {
2622 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2623 }
2624 #[allow(unreachable_patterns)]
2625 _ => false,
2626 }
2627}
2628
2629#[allow(clippy::too_many_arguments)]
2633pub fn conv1d_gemm(
2634 x: &[f32],
2635 w: &[f32],
2636 ic: usize,
2637 oc: usize,
2638 n: usize,
2639 k: usize,
2640 pad: usize,
2641 dil: usize,
2642 out_n: usize,
2643 yt: &mut [f32],
2644) -> bool {
2645 match backend() {
2646 #[cfg(target_os = "macos")]
2647 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2648 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2649 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2650 #[allow(unreachable_patterns)]
2651 _ => false,
2652 }
2653}
2654
2655#[allow(clippy::too_many_arguments)]
2657pub fn vae_conv2d_coop(
2658 w: &[f32],
2659 bias: Option<&[f32]>,
2660 x: &[f32],
2661 ic: usize,
2662 oc: usize,
2663 h: usize,
2664 wi: usize,
2665 k: usize,
2666 out: &mut [f32],
2667) -> bool {
2668 match backend() {
2669 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2670 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2671 #[allow(unreachable_patterns)]
2672 _ => false,
2673 }
2674}
2675
2676pub fn dit_attention_packed(
2677 qkv: &[f32],
2678 nh: usize,
2679 n: usize,
2680 hd: usize,
2681 scale: f32,
2682 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2685 out: &mut [f32],
2686) -> bool {
2687 match backend() {
2688 #[cfg(feature = "gpu")]
2695 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2696 #[allow(unreachable_patterns)]
2697 _ => false,
2698 }
2699}
2700
2701pub fn dit_attention_packed_available() -> bool {
2709 #[allow(unreachable_patterns)]
2710 match backend() {
2711 #[cfg(feature = "gpu")]
2712 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2713 _ => false,
2714 }
2715}
2716
2717pub fn dit_attention(
2718 qh: &[f32],
2719 kh: &[f32],
2720 vh: &[f32],
2721 nh: usize,
2722 nkv: usize,
2723 n: usize,
2724 hd: usize,
2725 scale: f32,
2726 out: &mut [f32],
2727) -> bool {
2728 match backend() {
2729 #[cfg(target_os = "macos")]
2730 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2731 #[cfg(feature = "gpu")]
2732 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2733 #[allow(unreachable_patterns)]
2734 _ => false,
2735 }
2736}
2737
2738#[allow(unused_variables)]
2743pub fn q4tp_matmat(
2744 model: &Arc<CmfModel>,
2745 idx: usize,
2746 xs: &[f32],
2747 b: usize,
2748 rows: usize,
2749 cols: usize,
2750 out: &mut [f32],
2751) -> bool {
2752 match backend() {
2753 #[cfg(target_os = "macos")]
2754 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2755 #[cfg(feature = "gpu")]
2756 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2757 #[allow(unreachable_patterns)]
2758 _ => false,
2759 }
2760}
2761
2762pub fn q2tp_matmat(
2765 model: &Arc<CmfModel>,
2766 idx: usize,
2767 xs: &[f32],
2768 b: usize,
2769 rows: usize,
2770 cols: usize,
2771 out: &mut [f32],
2772) -> bool {
2773 match backend() {
2774 #[cfg(feature = "gpu")]
2775 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2776 #[allow(unreachable_patterns)]
2777 _ => false,
2778 }
2779}
2780
2781pub fn q4tp_matvec(
2786 model: &Arc<CmfModel>,
2787 idx: usize,
2788 xs: &[f32],
2789 rows: usize,
2790 cols: usize,
2791 out: &mut [f32],
2792) -> bool {
2793 match backend() {
2794 #[cfg(target_os = "macos")]
2795 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2796 #[cfg(feature = "gpu")]
2797 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2798 #[allow(unreachable_patterns)]
2799 _ => false,
2800 }
2801}
2802
2803pub fn q4t_matvec(
2809 model: &Arc<CmfModel>,
2810 idx: usize,
2811 xs: &[f32],
2812 rows: usize,
2813 cols: usize,
2814 out: &mut [f32],
2815) -> bool {
2816 match backend() {
2817 #[cfg(target_os = "macos")]
2818 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2819 #[allow(unreachable_patterns)]
2820 _ => false,
2821 }
2822}
2823
2824pub fn q4t_matmat(
2825 model: &Arc<CmfModel>,
2826 idx: usize,
2827 xs: &[f32],
2828 b: usize,
2829 rows: usize,
2830 cols: usize,
2831 out: &mut [f32],
2832) -> bool {
2833 match backend() {
2834 #[cfg(target_os = "macos")]
2835 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2836 #[cfg(feature = "gpu")]
2837 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2838 #[allow(unreachable_patterns)]
2839 _ => false,
2840 }
2841}
2842
2843#[cfg(target_os = "macos")]
2845pub use crate::gpu_metal::{
2846 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2847 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2848};
2849
2850#[cfg(target_os = "macos")]
2852pub fn gdn_block(
2853 model: &Arc<CmfModel>,
2854 layers: &[GdnGpuLayer],
2855 states: &mut [&mut [f32]],
2856 cfg: &GdnGpuCfg,
2857 h: &mut [f32],
2858) -> bool {
2859 match backend() {
2860 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2861 _ => false,
2862 }
2863}
2864
2865#[allow(unused_variables)]
2867pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2868 match backend() {
2869 #[cfg(target_os = "macos")]
2870 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2871 #[cfg(feature = "gpu")]
2872 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2873 Backend::None => false,
2874 }
2875}
2876
2877#[allow(unused_variables)]
2879pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2880 match backend() {
2881 #[cfg(target_os = "macos")]
2882 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2883 #[cfg(feature = "gpu")]
2884 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2885 Backend::None => false,
2886 }
2887}
2888
2889static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2905static 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)];
2909
2910const GRAPH_RACE_SAMPLES: u32 = 4;
2912
2913static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2923
2924pub fn graph_mark_unsupported() {
2929 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2930 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2931 }
2932}
2933
2934pub fn graph_unsupported() -> bool {
2935 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2936}
2937
2938pub fn graph_unsupported_reset() {
2940 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2941}
2942
2943pub fn graph_race_begin_generation() {
2944 #[cfg(feature = "gpu")]
2949 {
2950 static FLUSHED: std::sync::Once = std::sync::Once::new();
2962 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2963 if FIRST.swap(false, Ordering::Relaxed) {
2964 } else {
2966 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2967 }
2968 }
2969 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2970 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2971 return;
2972 }
2973 let (gn, cn) = (
2974 GRAPH_N[1].load(Ordering::Relaxed),
2975 GRAPH_N[0].load(Ordering::Relaxed),
2976 );
2977 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2978 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2979 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2980 let verdict = if g_avg < c_avg { 1 } else { 2 };
2981 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2982 tracing::info!(
2983 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2984 g_avg as f64 / 1e6,
2985 c_avg as f64 / 1e6,
2986 if verdict == 1 { "graph" } else { "normal path" }
2987 );
2988 return;
2989 }
2990 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2991 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2992}
2993
2994pub fn graph_race_use_graph(trusted: bool) -> bool {
2998 if trusted {
2999 return true;
3000 }
3001 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3002 1 => true,
3003 2 => false,
3004 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3005 }
3006}
3007
3008pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3013 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3014 return false;
3015 }
3016 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3017 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3018 if !first || cn == 0 {
3019 return false;
3020 }
3021 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3022 let ns = dur.as_nanos() as u64;
3023 if ns > 1_000_000_000 && ns > 4 * c_avg {
3024 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3025 tracing::info!(
3026 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3027 ns as f64 / 1e6,
3028 c_avg as f64 / 1e6
3029 );
3030 return true;
3031 }
3032 false
3033}
3034
3035pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3039 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3040 return;
3041 }
3042 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3043 if tok == 0 {
3044 return;
3045 }
3046 let i = used_graph as usize;
3047 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3048 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3049}
3050
3051pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3061 #[inline]
3062 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3063 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3064 for c in chunks.chunks_exact(8) {
3065 h ^= u64::from_le_bytes(c.try_into().unwrap());
3066 h = h.wrapping_mul(0x100_0000_01b3);
3067 }
3068 for &b in tail {
3069 h ^= b as u64;
3070 h = h.wrapping_mul(0x100_0000_01b3);
3071 }
3072 h
3073 }
3074 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3075 if data.len() <= 4096 {
3076 return fnv(h, data);
3077 }
3078 let step = (data.len() - 64) / 63;
3079 for i in 0..64 {
3080 h = fnv(h, &data[i * step..i * step + 64]);
3081 }
3082 h
3083}
3084
3085pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3088 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3089 fp_bytes(bytes)
3090}
3091
3092#[cfg(test)]
3093mod fp_tests {
3094 use super::fp_bytes;
3095
3096 #[test]
3101 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3102 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3104 let h0 = fp_bytes(&base);
3105 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3106 let mut dense = base.clone();
3109 for b in dense.iter_mut() {
3110 *b = b.wrapping_add(1);
3111 }
3112 assert_ne!(
3113 h0,
3114 fp_bytes(&dense),
3115 "a fully different tensor slipped through"
3116 );
3117 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3120 let mut small = vec![3u8; 4096];
3123 let hs = fp_bytes(&small);
3124 small[2048] ^= 1;
3125 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3126 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3128 let v = vec![9u8; n];
3129 let _ = fp_bytes(&v); }
3131 }
3132}
3133
3134pub fn bake_release() {
3138 #[cfg(feature = "gpu")]
3139 crate::gpu_wgpu::bake_release();
3140}
3141
3142pub fn bake_precision_strict(on: bool) {
3146 #[cfg(feature = "gpu")]
3147 crate::gpu_wgpu::bake_precision_strict(on);
3148 #[cfg(not(feature = "gpu"))]
3149 let _ = on;
3150}
3151
3152pub fn hostprof_encode_done(t0: std::time::Instant) {
3158 use std::sync::atomic::{AtomicU64, Ordering};
3159 static ENC: AtomicU64 = AtomicU64::new(0);
3160 static N: AtomicU64 = AtomicU64::new(0);
3161 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3162 return;
3163 }
3164 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3165 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3166 if n % 100 == 0 {
3167 eprintln!(
3168 "hostprof: encode {:.2} ms/token over {n} tokens",
3169 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3170 );
3171 }
3172}
3173
3174pub fn hostprof_total(t0: std::time::Instant) {
3175 use std::sync::atomic::{AtomicU64, Ordering};
3176 static TOT: AtomicU64 = AtomicU64::new(0);
3177 static N: AtomicU64 = AtomicU64::new(0);
3178 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3179 return;
3180 }
3181 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3182 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3183 if n % 100 == 0 {
3184 eprintln!(
3185 "hostprof: total {:.2} ms/token over {n} tokens",
3186 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3187 );
3188 }
3189}
3190
3191pub fn stageprof(stage: u32, dt: std::time::Duration) {
3195 use std::sync::atomic::{AtomicU64, Ordering};
3196 static NS: [AtomicU64; 4] = [
3197 AtomicU64::new(0),
3198 AtomicU64::new(0),
3199 AtomicU64::new(0),
3200 AtomicU64::new(0),
3201 ];
3202 static N: AtomicU64 = AtomicU64::new(0);
3203 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3204 return;
3205 }
3206 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3207 if stage == 1 {
3208 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3209 if n % 200 == 0 {
3210 eprintln!(
3211 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3212 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3213 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3214 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3215 );
3216 }
3217 }
3218}
3219
3220pub fn weight_bytes_dispatched() -> u64 {
3223 let mut total = 0u64;
3224 #[cfg(target_os = "macos")]
3225 {
3226 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3227 }
3228 #[cfg(feature = "gpu")]
3229 {
3230 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3231 }
3232 total
3233}
3234
3235pub fn weight_bytes_by() -> [u64; 6] {
3238 #[cfg(target_os = "macos")]
3239 {
3240 let mut o = [0u64; 6];
3241 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3242 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3243 }
3244 return o;
3245 }
3246 #[allow(unreachable_code)]
3247 [0; 6]
3248}
3249
3250#[cfg(test)]
3251mod probe_warmup_tests {
3252 use super::*;
3253 use std::time::Duration;
3254
3255 fn ms(v: f64) -> Duration {
3256 Duration::from_nanos((v * 1e6) as u64)
3257 }
3258
3259 #[test]
3264 fn one_cold_first_sample_does_not_lose_the_class() {
3265 let p = Probe::new();
3266 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3268 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3269 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3270 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3271 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3272 assert_eq!(
3273 p.state.load(Ordering::Relaxed),
3274 1,
3275 "the device is 3x faster once warm and must win"
3276 );
3277 }
3278
3279 #[test]
3283 fn the_warmup_is_spent_once_and_never_underflows() {
3284 let p = Probe::new();
3285 for _ in 0..8 {
3286 probe_record_into(&p, "matmat", None, true, ms(10.0));
3287 }
3288 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3289 assert_eq!(
3290 p.gpu_n.load(Ordering::Relaxed),
3291 7,
3292 "one sample burned, the rest counted"
3293 );
3294 }
3295
3296 #[test]
3302 fn a_class_whose_device_always_declines_settles_on_the_host() {
3303 let c = OpClass::MatmatWide;
3307 let p = &PROBES[c as usize];
3308 p.state.store(0, Ordering::Relaxed);
3309 p.declines.store(0, Ordering::Relaxed);
3310 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3311 probe_note_decline(c);
3312 }
3313 assert_eq!(
3314 p.state.load(Ordering::Relaxed),
3315 0,
3316 "one short of the limit is still a question, not an answer"
3317 );
3318 probe_note_decline(c);
3319 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3320 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3321 p.state.store(0, Ordering::Relaxed);
3322 p.declines.store(0, Ordering::Relaxed);
3323 }
3324
3325 #[test]
3328 fn a_slow_device_still_loses_after_the_warmup() {
3329 let p = Probe::new();
3330 for _ in 0..4 {
3331 probe_record_into(&p, "matvec", None, true, ms(40.0));
3332 }
3333 for _ in 0..4 {
3334 probe_record_into(&p, "matvec", None, false, ms(2.0));
3335 }
3336 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3337 }
3338}
3339
3340pub(crate) struct ImageStageGuard {
3343 #[cfg(target_os = "macos")]
3344 metal: Option<crate::gpu_metal::ImageStageGuard>,
3345 #[cfg(feature = "gpu")]
3346 wgpu: crate::gpu_wgpu::ImageStageGuard,
3347}
3348
3349pub(crate) fn image_stage_scope() -> ImageStageGuard {
3350 ImageStageGuard {
3351 #[cfg(target_os = "macos")]
3352 metal: if matches!(backend(), Backend::Metal) {
3353 Some(crate::gpu_metal::image_stage_scope())
3354 } else {
3355 None
3356 },
3357 #[cfg(feature = "gpu")]
3358 wgpu: crate::gpu_wgpu::image_stage_scope(),
3359 }
3360}
3361
3362impl ImageStageGuard {
3363 pub(crate) fn track_model(&mut self, uid: u64) {
3364 #[cfg(target_os = "macos")]
3365 if let Some(metal) = &mut self.metal {
3366 metal.track_model(uid);
3367 }
3368 #[cfg(feature = "gpu")]
3369 self.wgpu.track_model(uid);
3370 #[cfg(not(target_os = "macos"))]
3371 let _ = uid;
3372 }
3373}