1use cortiq_core::CmfModel;
14use std::cell::Cell;
15use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock};
17
18thread_local! {
19 static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
23 static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
28 static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
32}
33
34pub struct CpuScopeGuard(bool);
40
41impl Drop for CpuScopeGuard {
42 fn drop(&mut self) {
43 CPU_ONLY.with(|c| c.set(self.0));
44 }
45}
46
47pub fn enter_cpu_scope() -> CpuScopeGuard {
48 let previous = CPU_ONLY.with(|c| c.replace(true));
49 CpuScopeGuard(previous)
50}
51
52pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
54 let _restore = enter_cpu_scope();
55 f()
56}
57
58pub fn probe_set_device(label: &str) {
63 let _ = DEVICE_LABEL.set(label.to_string());
64}
65
66fn device_label() -> &'static str {
67 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
68}
69
70static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
71
72static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
81
82pub fn set_cache_dir(dir: std::path::PathBuf) {
84 let _ = CACHE_DIR.set(dir);
85}
86
87pub fn cache_dir_pub() -> std::path::PathBuf {
89 cache_dir()
90}
91
92fn cache_dir() -> std::path::PathBuf {
93 if let Some(d) = CACHE_DIR.get() {
94 return d.clone();
95 }
96 match std::env::var_os("TMPDIR") {
97 Some(t) => std::path::PathBuf::from(t),
98 None => std::env::temp_dir(),
99 }
100}
101
102fn probe_cache_path() -> Option<std::path::PathBuf> {
105 match std::env::var("CMF_PROBE_CACHE") {
106 Ok(v) if v == "0" => None,
107 Ok(v) => Some(std::path::PathBuf::from(v)),
108 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
109 }
110}
111
112fn probe_cache_key_named(class: &str) -> String {
116 format!(
117 "{}\t{}\t{}",
118 env!("CARGO_PKG_VERSION"),
119 device_label(),
120 class
121 )
122}
123
124const CLASS_NAMES: [&str; 7] = [
125 "ffn",
126 "matvec",
127 "matmat",
128 "qkv-batch",
129 "matmat-wide",
130 "lm-head",
131 "gemm-nt",
132];
133
134fn probe_cache_load() {
143 static ONCE: std::sync::Once = std::sync::Once::new();
144 ONCE.call_once(|| {
145 let Some(path) = probe_cache_path() else {
146 return;
147 };
148 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
153 return;
154 }
155 let Ok(text) = std::fs::read_to_string(&path) else {
156 return;
157 };
158 probe_cache_adopt(&text);
159 });
160}
161
162fn probe_cache_adopt(text: &str) {
166 for line in text.lines() {
167 let Some((key, verdict)) = line.rsplit_once('\t') else {
168 continue;
169 };
170 let winner = match verdict.trim() {
171 "gpu" => 1u8,
172 "cpu" => 2u8,
173 _ => continue,
174 };
175 for (i, name) in CLASS_NAMES.iter().enumerate() {
176 if probe_cache_key_named(name) == key {
177 let _ = PROBES[i].state.compare_exchange(
178 0,
179 winner,
180 Ordering::Relaxed,
181 Ordering::Relaxed,
182 );
183 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
184 }
185 }
186 }
187}
188
189fn probe_cache_store(c: OpClass, winner: u8) {
192 let Some(path) = probe_cache_path() else {
193 return;
194 };
195 let line = format!(
196 "{}\t{}\n",
197 probe_cache_key_named(CLASS_NAMES[c as usize]),
198 if winner == 1 { "gpu" } else { "cpu" }
199 );
200 use std::io::Write;
201 if let Ok(mut f) = std::fs::OpenOptions::new()
202 .create(true)
203 .append(true)
204 .open(&path)
205 {
206 let _ = f.write_all(line.as_bytes());
207 }
208}
209
210pub fn cold_epoch() -> u64 {
216 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
217}
218static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
219
220pub(crate) fn probe_note_cold() {
221 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
222 PROBE_COLD.with(|c| c.set(true));
223}
224
225pub(crate) fn probe_was_cold() -> bool {
229 PROBE_COLD.with(|c| c.get())
230}
231
232pub fn set_layer(l: i64) {
234 CUR_LAYER.with(|c| c.set(l));
235}
236
237pub fn cur_layer() -> i64 {
239 CUR_LAYER.with(|c| c.get())
240}
241
242pub fn automatic_layer_prefix(
245 model: &Arc<CmfModel>,
246 num_layers: usize,
247 physical_layers: usize,
248) -> Option<usize> {
249 match backend() {
250 #[cfg(feature = "gpu")]
251 Backend::Wgpu => {
252 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
253 }
254 _ => None,
255 }
256}
257
258fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
261 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
262 R.get_or_init(|| {
263 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
264 let mut v = Vec::new();
265 for part in s.split(',') {
266 let part = part.trim();
267 match part.split_once('-') {
268 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
269 None => {
270 let x: i64 = part.parse().ok()?;
271 v.push((x, x));
272 }
273 }
274 }
275 Some(v)
276 })
277}
278
279fn layer_allowed() -> bool {
280 match layer_ranges() {
281 None => true,
282 Some(ranges) => {
283 let cur = CUR_LAYER.with(|c| c.get());
284 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
285 }
286 }
287}
288
289pub fn enabled_here() -> bool {
293 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
294}
295
296pub fn q2tp_gpu_opt_in() -> bool {
302 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
303}
304
305#[derive(Clone, Copy)]
317pub enum OpClass {
318 Ffn = 0,
320 Matvec = 1,
322 Matmat = 2,
324 Batch = 3,
326 MatmatWide = 4,
332 MatvecHead = 5,
339 GemmNt = 6,
346}
347
348pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
352 if rows * cols >= 67_108_864 {
353 OpClass::MatvecHead
354 } else {
355 OpClass::Matvec
356 }
357}
358
359pub enum ProbeArm {
361 Gpu,
363 CpuTimed,
365 Cpu,
367}
368
369const PROBE_SAMPLES: u32 = 6;
371
372const PROBE_DECLINE_LIMIT: u32 = 16;
376
377const PROBE_WARMUP: u32 = 1;
379
380struct Probe {
381 state: AtomicU8,
383 flip: AtomicU32,
384 gpu_ns: AtomicU64,
385 gpu_n: AtomicU32,
386 declines: AtomicU32,
396 gpu_burn: AtomicU32,
409 cpu_ns: AtomicU64,
410 cpu_n: AtomicU32,
411 gpu_min: AtomicU64,
418 cpu_min: AtomicU64,
419}
420
421impl Probe {
422 const fn new() -> Self {
423 Self {
424 state: AtomicU8::new(0),
425 flip: AtomicU32::new(0),
426 gpu_ns: AtomicU64::new(0),
427 gpu_n: AtomicU32::new(0),
428 declines: AtomicU32::new(0),
429 gpu_burn: AtomicU32::new(PROBE_WARMUP),
430 cpu_ns: AtomicU64::new(0),
431 cpu_n: AtomicU32::new(0),
432 gpu_min: AtomicU64::new(u64::MAX),
433 cpu_min: AtomicU64::new(u64::MAX),
434 }
435 }
436}
437
438static PROBES: [Probe; 7] = [
439 Probe::new(),
440 Probe::new(),
441 Probe::new(),
442 Probe::new(),
443 Probe::new(),
444 Probe::new(),
445 Probe::new(),
446];
447
448static TRUST_GPU: AtomicBool = AtomicBool::new(false);
455
456pub fn trust_gpu() -> GpuTrust {
458 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
459 GpuTrust(was)
460}
461
462pub struct GpuTrust(bool);
463
464impl Drop for GpuTrust {
465 fn drop(&mut self) {
466 TRUST_GPU.store(self.0, Ordering::Relaxed);
467 }
468}
469
470fn probe_on_for(c: OpClass) -> bool {
471 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
477 return false;
478 }
479 probe_on()
480}
481
482fn probe_on() -> bool {
483 static ON: OnceLock<bool> = OnceLock::new();
484 *ON.get_or_init(|| {
485 std::env::var("CMF_GPU_PROBE")
486 .map(|v| v != "0" && v != "off")
487 .unwrap_or(true)
488 })
489}
490
491pub fn q1_force() -> bool {
496 #[cfg(target_os = "macos")]
497 {
498 backend() == Backend::Metal
499 }
500 #[cfg(not(target_os = "macos"))]
501 {
502 false
503 }
504}
505
506pub fn fused_block_trusted() -> bool {
525 #[cfg(target_os = "macos")]
526 if backend() == Backend::Metal {
527 return true;
528 }
529 wgpu_graph_default()
530}
531
532pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
544 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
545 {
546 return crate::gpu_wgpu::weight_is_resident(model, idx);
547 }
548 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
549 {
550 let _ = (model, idx);
551 true
552 }
553}
554
555pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
556 if !weights_resident && probe_deciding(c) {
557 return ProbeArm::Gpu;
558 }
559 probe_arm(c)
560}
561
562pub fn probe_arm(c: OpClass) -> ProbeArm {
563 PROBE_COLD.with(|f| f.set(false));
568 if !probe_on_for(c) {
569 return ProbeArm::Gpu;
570 }
571 probe_cache_load();
572 let p = &PROBES[c as usize];
573 match p.state.load(Ordering::Relaxed) {
574 1 => ProbeArm::Gpu,
575 2 => ProbeArm::Cpu,
576 _ => {
577 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
578 ProbeArm::Gpu
579 } else {
580 ProbeArm::CpuTimed
581 }
582 }
583 }
584}
585
586pub fn probe_note_decline(c: OpClass) {
590 let p = &PROBES[c as usize];
591 if p.state.load(Ordering::Relaxed) != 0 {
592 return;
593 }
594 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
595 if n >= PROBE_DECLINE_LIMIT
596 && p.state
597 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
598 .is_ok()
599 {
600 tracing::info!(
601 "gpu probe [{}]: device declined {n} times → cpu",
602 CLASS_NAMES[c as usize]
603 );
604 }
605}
606
607pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
610 probe_record_into(
611 &PROBES[c as usize],
612 CLASS_NAMES[c as usize],
613 Some(c),
614 gpu,
615 dur,
616 )
617}
618
619fn probe_record_into(
622 p: &Probe,
623 class_name: &str,
624 cache: Option<OpClass>,
625 gpu: bool,
626 dur: std::time::Duration,
627) {
628 if p.state.load(Ordering::Relaxed) != 0 {
629 return;
630 }
631 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
632 return; }
634 if gpu {
635 let left = p.gpu_burn.load(Ordering::Relaxed);
639 if left > 0 {
640 p.gpu_burn.store(left - 1, Ordering::Relaxed);
641 return; }
643 }
644 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
645 if gpu {
646 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
647 p.gpu_n.fetch_add(1, Ordering::Relaxed);
648 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
649 } else {
650 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
651 p.cpu_n.fetch_add(1, Ordering::Relaxed);
652 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
653 }
654 let (gn, cn) = (
655 p.gpu_n.load(Ordering::Relaxed),
656 p.cpu_n.load(Ordering::Relaxed),
657 );
658 if gn >= 2 && cn >= 2 {
659 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
663 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
664 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
674 return;
675 }
676 let winner = if g <= cp { 1 } else { 2 };
677 if p.state
678 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
679 .is_ok()
680 {
681 tracing::info!(
682 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
683 class_name,
684 g / 1e6,
685 cp / 1e6,
686 if winner == 1 { "gpu" } else { "cpu" },
687 );
688 if let Some(c) = cache {
689 probe_cache_store(c, winner);
690 }
691 }
692 }
693}
694
695pub fn probe_deciding(c: OpClass) -> bool {
698 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
699}
700
701#[allow(unused_variables)]
711pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
712 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
713 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
714 let resident = match backend() {
715 #[cfg(target_os = "macos")]
716 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
717 #[cfg(feature = "gpu")]
718 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
719 Backend::None => false,
720 };
721 if !resident && may_upload {
722 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
723 }
724 resident
725}
726
727#[cfg(test)]
729pub(crate) fn probe_reset() {
730 for p in &PROBES {
731 p.state.store(0, Ordering::Relaxed);
732 p.flip.store(0, Ordering::Relaxed);
733 p.gpu_ns.store(0, Ordering::Relaxed);
734 p.gpu_n.store(0, Ordering::Relaxed);
735 p.cpu_ns.store(0, Ordering::Relaxed);
736 p.cpu_n.store(0, Ordering::Relaxed);
737 }
738}
739
740#[cfg(test)]
741mod probe_tests {
742 use super::*;
743 use std::time::Duration;
744
745 #[test]
748 fn probe_alternates_discards_cold_and_decides() {
749 probe_reset();
750 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
752 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
753
754 probe_note_cold();
758 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
759 for _ in 0..PROBE_SAMPLES {
760 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
761 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
762 }
763 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
764
765 for _ in 0..PROBE_SAMPLES {
767 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
768 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
769 }
770 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
771
772 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
774 CPU_ONLY.with(|c| assert!(!c.get()));
775 cpu_scope(|| {
776 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
777 CPU_ONLY.with(|c| assert!(c.get()));
778 });
779 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
780 CPU_ONLY.with(|c| assert!(!c.get()));
781 probe_reset();
782 }
783
784 #[test]
785 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
786 let mine = probe_cache_key_named("gemm-nt");
798 let state = || {
799 PROBES[OpClass::GemmNt as usize]
800 .state
801 .load(Ordering::Relaxed)
802 };
803
804 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
806 assert_eq!(state(), 0);
807 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
809 assert_ne!(older, mine);
810 probe_cache_adopt(&format!("{older}\tgpu\n"));
811 assert_eq!(state(), 0);
812 probe_cache_adopt(&format!("{mine}\tcpu\n"));
814 assert_eq!(state(), 2);
815
816 PROBES[OpClass::GemmNt as usize]
817 .state
818 .store(0, Ordering::Relaxed);
819 }
820}
821
822pub const GPU_MIN_ROWS: usize = 65_536;
825
826pub fn min_rows() -> usize {
833 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
834 .ok()
835 .and_then(|v| v.parse().ok())
836 {
837 return v;
838 }
839 if discrete() { 4096 } else { GPU_MIN_ROWS }
840}
841
842pub fn discrete() -> bool {
844 match backend() {
845 #[cfg(feature = "gpu")]
846 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
847 #[cfg(target_os = "macos")]
848 Backend::Metal => false, Backend::None => false,
850 }
851}
852
853pub struct MoeJob<'a> {
857 pub gate: (usize, usize, usize, &'a [f32]),
858 pub up: (usize, usize, usize, &'a [f32]),
859 pub down: (usize, usize, usize, &'a [f32]),
860 pub xs_gate: Vec<f32>,
861 pub xs_up: Vec<f32>,
862 pub down_col: &'a [f32],
863 pub w: f32,
864 pub q1: bool,
867 pub q4t: bool,
870 pub q4tp: bool,
874 pub gu_q2: bool,
878 pub swiglu_limit: f32,
883}
884
885pub struct BatchJob<'a> {
887 pub idx: usize,
888 pub rows: usize,
889 pub cols: usize,
890 pub row_scale: &'a [f32],
891 pub xs: Vec<f32>,
892 pub layout: BatchLayout,
896}
897
898#[derive(Clone, Copy, PartialEq, Eq, Debug)]
901pub enum BatchLayout {
902 Q8,
903 Q1,
904 Q4t,
905 Q4tp,
906}
907
908#[derive(Clone, Copy, PartialEq, Eq)]
909enum Backend {
910 None,
911 #[cfg(target_os = "macos")]
912 Metal,
913 #[cfg(feature = "gpu")]
914 Wgpu,
915}
916
917fn backend() -> Backend {
918 #[cfg(feature = "gpu")]
919 if crate::gpu_wgpu::selected() {
920 return if crate::gpu_wgpu::enabled() {
921 Backend::Wgpu
922 } else {
923 Backend::None
924 };
925 }
926 #[cfg(target_os = "macos")]
927 if crate::gpu_metal::enabled() {
928 return Backend::Metal;
929 }
930 Backend::None
931}
932
933pub fn backend_available() -> bool {
939 #[cfg(target_os = "macos")]
940 {
941 true
943 }
944 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
945 {
946 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
947 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
948 }
949 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
950 {
951 false
952 }
953}
954
955static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
961
962pub fn pause_gpu() -> GpuPause {
964 GPU_PAUSED.store(true, Ordering::Relaxed);
965 GpuPause(())
966}
967
968pub struct GpuPause(());
969
970impl Drop for GpuPause {
971 fn drop(&mut self) {
972 GPU_PAUSED.store(false, Ordering::Relaxed);
973 }
974}
975
976pub fn enabled() -> bool {
977 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
978}
979
980pub fn wgpu_active() -> bool {
994 #[cfg(feature = "gpu")]
995 {
996 matches!(backend(), Backend::Wgpu)
997 }
998 #[cfg(not(feature = "gpu"))]
999 {
1000 false
1001 }
1002}
1003
1004pub fn default_device() -> usize {
1011 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1012 *D.get_or_init(|| {
1013 std::env::var("CMF_GPU_ADAPTER")
1014 .ok()
1015 .and_then(|v| v.trim().parse::<usize>().ok())
1016 .unwrap_or(0)
1017 })
1018}
1019
1020thread_local! {
1021 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1022}
1023
1024pub fn current_device() -> usize {
1026 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1027}
1028
1029pub fn set_current_device(i: usize) {
1033 CUR_DEV.with(|c| c.set(Some(i)));
1034}
1035
1036pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1038 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1039 let r = f();
1040 CUR_DEV.with(|c| c.set(prev));
1041 r
1042}
1043
1044pub fn device_count() -> usize {
1047 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1048 {
1049 return crate::gpu_wgpu::adapter_count();
1050 }
1051 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1052 {
1053 usize::from(backend_available())
1054 }
1055}
1056
1057pub fn vram_budget() -> u64 {
1061 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1062 {
1063 return crate::gpu_wgpu::device_vram_budget();
1064 }
1065 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1066 {
1067 if backend_available() { u64::MAX } else { 0 }
1068 }
1069}
1070
1071pub fn resident_bytes() -> u64 {
1075 #[cfg(feature = "gpu")]
1076 {
1077 if backend() == Backend::Wgpu {
1078 return crate::gpu_wgpu::resident_bytes();
1079 }
1080 }
1081 0
1082}
1083
1084pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1088 #[cfg(feature = "gpu")]
1089 {
1090 if backend() == Backend::Wgpu {
1091 return crate::gpu_wgpu::o1_device_stats(kv_id);
1092 }
1093 }
1094 let _ = kv_id;
1095 (0, 0)
1096}
1097
1098pub fn upload_bytes() -> u64 {
1102 #[cfg(feature = "gpu")]
1103 {
1104 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1105 }
1106 #[cfg(not(feature = "gpu"))]
1107 0
1108}
1109
1110pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1114 #[cfg(feature = "gpu")]
1115 {
1116 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1117 }
1118 let _ = (block, rounds);
1119 None
1120}
1121
1122#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1134pub enum GraphPhase {
1135 Prefill,
1136 Decode,
1137}
1138
1139pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1147 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1148 Some("0") => false,
1149 Some("prefill") => phase == GraphPhase::Prefill,
1150 Some(_) => true,
1151 None => {
1152 if wgpu_graph_default() {
1153 return true;
1154 }
1155 let _ = phase;
1160 false
1161 }
1162 }
1163}
1164
1165pub fn wgpu_graph_default() -> bool {
1166 #[cfg(feature = "gpu")]
1167 {
1168 matches!(backend(), Backend::Wgpu)
1174 && (crate::gpu_wgpu::discrete_active()
1175 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1176 }
1177 #[cfg(not(feature = "gpu"))]
1178 {
1179 false
1180 }
1181}
1182
1183#[allow(clippy::too_many_arguments, unused_variables)]
1185pub fn q8_matvec_range(
1186 model: &Arc<CmfModel>,
1187 idx: usize,
1188 row0: usize,
1189 row_scale: &[f32],
1190 xs: &[f32],
1191 rows: usize,
1192 cols: usize,
1193 out: &mut [f32],
1194) -> bool {
1195 match backend() {
1196 #[cfg(target_os = "macos")]
1197 Backend::Metal => {
1198 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1199 }
1200 #[cfg(feature = "gpu")]
1201 Backend::Wgpu => {
1202 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1203 }
1204 Backend::None => false,
1205 }
1206}
1207
1208#[allow(clippy::too_many_arguments, unused_variables)]
1211#[allow(clippy::too_many_arguments)]
1215pub fn q8_matmat_2f(
1216 model: &Arc<CmfModel>,
1217 idx: usize,
1218 row_scale: &[f32],
1219 col_field: &[f32],
1220 xs: &[f32],
1221 b: usize,
1222 rows: usize,
1223 cols: usize,
1224 out: &mut [f32],
1225) -> bool {
1226 #[allow(unreachable_patterns)]
1227 match backend() {
1228 #[cfg(feature = "gpu")]
1229 Backend::Wgpu => {
1230 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1231 }
1232 _ => false,
1233 }
1234}
1235
1236pub fn q8_matmat(
1237 model: &Arc<CmfModel>,
1238 idx: usize,
1239 row_scale: &[f32],
1240 pre: &[f32],
1241 b: usize,
1242 rows: usize,
1243 cols: usize,
1244 out: &mut [f32],
1245) -> bool {
1246 match backend() {
1247 #[cfg(target_os = "macos")]
1248 Backend::Metal => {
1249 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1250 }
1251 #[cfg(feature = "gpu")]
1252 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1253 Backend::None => false,
1254 }
1255}
1256
1257#[allow(unused_variables)]
1260pub fn q1_matvec(
1261 model: &Arc<CmfModel>,
1262 idx: usize,
1263 xs: &[f32],
1264 rows: usize,
1265 cols: usize,
1266 out: &mut [f32],
1267) -> bool {
1268 match backend() {
1269 #[cfg(target_os = "macos")]
1270 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1271 #[cfg(feature = "gpu")]
1272 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1273 Backend::None => false,
1274 }
1275}
1276
1277#[allow(clippy::too_many_arguments)]
1281pub fn attn_dropin(
1282 model: &Arc<CmfModel>,
1283 kv_id: u64,
1284 layer: usize,
1285 normed: &[f32],
1286 wq_idx: usize,
1287 wk_idx: usize,
1288 wv_idx: usize,
1289 wo_idx: usize,
1290 q_norm: Option<&[f32]>,
1291 k_norm: Option<&[f32]>,
1292 invf: &[f32],
1293 nh: usize,
1294 nkv: usize,
1295 hd: usize,
1296 rd: usize,
1297 hidden: usize,
1298 pos: usize,
1299 cap: usize,
1300 gemma: bool,
1301 eps: f32,
1302 cpu_k: &[Vec<f32>],
1303 cpu_v: &[Vec<f32>],
1304 out: &mut [f32],
1305) -> bool {
1306 match backend() {
1307 #[cfg(feature = "gpu")]
1308 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1309 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1310 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1311 ),
1312 #[allow(unused_variables)]
1313 _ => false,
1314 }
1315}
1316
1317#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1321pub enum GraphPrismOp {
1322 None,
1323 Forward,
1324 InverseEmbedding,
1325}
1326
1327pub struct GraphW<'a> {
1331 pub idx: usize,
1332 pub kind: u8,
1333 pub row_scale: &'a [f32],
1334 pub data: &'a [f32],
1335 pub prism: GraphPrismOp,
1336 pub affine: bool,
1337}
1338
1339pub enum GraphAttn<'a> {
1342 Full {
1343 wq: GraphW<'a>,
1344 wk: GraphW<'a>,
1345 wv: GraphW<'a>,
1346 wo: GraphW<'a>,
1347 q_norm: Option<&'a [f32]>,
1348 k_norm: Option<&'a [f32]>,
1349 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1351 output_gate: bool,
1354 cpu_k: &'a [Vec<f32>],
1355 cpu_v: &'a [Vec<f32>],
1356 },
1357 Gdn {
1358 qkv: GraphW<'a>,
1359 z: GraphW<'a>,
1360 a: GraphW<'a>,
1361 b: GraphW<'a>,
1362 out: GraphW<'a>,
1363 conv1d: &'a [f32],
1364 a_log: &'a [f32],
1365 dt_bias: &'a [f32],
1366 norm: &'a [f32],
1367 nv: usize,
1368 nk: usize,
1369 dk: usize,
1370 dv: usize,
1371 kk: usize,
1372 cpu_state: &'a [f32],
1377 },
1378 ShortConv {
1385 inp: GraphW<'a>,
1387 out: GraphW<'a>,
1389 taps: &'a [f32],
1392 kernel: usize,
1393 cpu_state: &'a [f32],
1397 },
1398}
1399
1400pub struct GraphLayer<'a> {
1402 pub input_norm: &'a [f32],
1403 pub attn: GraphAttn<'a>,
1404 pub post_norm: &'a [f32],
1405 pub ffn: GraphFfn<'a>,
1406}
1407
1408pub enum GraphFfn<'a> {
1413 Dense {
1414 gate: GraphW<'a>,
1415 up: GraphW<'a>,
1416 down: GraphW<'a>,
1417 },
1418 Moe {
1419 router: GraphW<'a>,
1421 shared_gate: GraphW<'a>,
1423 experts: Vec<(usize, usize, usize)>,
1427 n_exp: usize,
1429 top_k: usize,
1430 inter: usize,
1431 norm_topk: bool,
1432 q4tp: bool,
1438 gu_q2: bool,
1442 sigmoid: bool,
1446 bias: Option<&'a [f32]>,
1449 has_shared: bool,
1453 },
1454}
1455
1456#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1459pub enum TokenGraphOutcome {
1460 Declined,
1462 Completed,
1464 Failed,
1466}
1467
1468#[allow(clippy::too_many_arguments)]
1473pub fn forward_token_graph(
1474 model: &Arc<CmfModel>,
1475 kv_id: u64,
1476 layers: &[GraphLayer],
1477 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1480 o1_epoch: u64,
1481 invf: &[f32],
1482 h: &mut [f32],
1483 nh: usize,
1484 nkv: usize,
1485 hd: usize,
1486 attn_scale: f32,
1487 rd: usize,
1488 hidden: usize,
1489 inter: usize,
1490 position: usize,
1491 cap: usize,
1492 gemma: bool,
1493 eps: f32,
1494 lm_head: Option<(&GraphW, usize)>,
1495 final_norm: &[f32],
1496 logits: &mut Vec<f32>,
1497 loop_norm_at: &[usize],
1498 steps: usize,
1499 embed: Option<(&GraphW, usize, f32)>,
1500 ids_out: Option<&mut Vec<u32>>,
1501 layers_run: Option<&mut usize>,
1504 layer_base: usize,
1508 hidden_too: bool,
1510) -> TokenGraphOutcome {
1511 match backend() {
1512 #[cfg(feature = "gpu")]
1513 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1514 model,
1515 kv_id,
1516 layers,
1517 o1,
1518 o1_epoch,
1519 invf,
1520 h,
1521 nh,
1522 nkv,
1523 hd,
1524 attn_scale,
1525 rd,
1526 hidden,
1527 inter,
1528 position,
1529 cap,
1530 gemma,
1531 eps,
1532 lm_head,
1533 final_norm,
1534 logits,
1535 loop_norm_at,
1536 steps,
1537 embed,
1538 ids_out,
1539 layers_run,
1540 layer_base,
1541 hidden_too,
1542 ),
1543 #[allow(unused_variables)]
1544 _ => {
1545 let _ = (
1546 attn_scale,
1547 lm_head,
1548 final_norm,
1549 logits,
1550 loop_norm_at,
1551 layers_run,
1552 layer_base,
1553 hidden_too,
1554 );
1555 TokenGraphOutcome::Declined
1556 }
1557 }
1558}
1559
1560#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1564pub enum BatchGraphOutcome {
1565 Declined,
1568 Completed,
1570 Failed,
1574}
1575
1576pub struct SpecTail<'a> {
1577 pub lm: GraphW<'a>,
1578 pub lm_rows: usize,
1579 pub final_norm: &'a [f32],
1580 pub logits_out: &'a mut Vec<f32>,
1581}
1582
1583#[allow(clippy::too_many_arguments)]
1587pub fn forward_batch_graph(
1588 model: &Arc<CmfModel>,
1589 kv_id: u64,
1590 layers: &[GraphLayer],
1591 invf: &[f32],
1592 h: &mut [f32],
1593 nh: usize,
1594 nkv: usize,
1595 hd: usize,
1596 rd: usize,
1597 hidden: usize,
1598 inter: usize,
1599 positions: &[usize],
1600 cap: usize,
1601 gemma: bool,
1602 eps: f32,
1603 attn_scale: f32,
1604 k: usize,
1605 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1608 o1_epoch: u64,
1609 spec: Option<SpecTail<'_>>,
1610) -> BatchGraphOutcome {
1611 match backend() {
1612 #[cfg(feature = "gpu")]
1613 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1614 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1615 eps, attn_scale, k, o1, o1_epoch, spec,
1616 ),
1617 #[allow(unreachable_patterns)]
1618 _ => {
1619 let _ = (o1, o1_epoch, spec);
1620 BatchGraphOutcome::Declined
1621 }
1622 }
1623}
1624
1625pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1630 #[cfg(feature = "gpu")]
1631 if backend() == Backend::Wgpu {
1632 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1633 }
1634 #[allow(unreachable_code)]
1635 {
1636 let _ = (kv_id, slot, base_pos, expected_layers);
1637 false
1638 }
1639}
1640
1641pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1647 #[cfg(feature = "gpu")]
1648 if backend() == Backend::Wgpu {
1649 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1650 }
1651 #[cfg(target_os = "macos")]
1652 if backend() == Backend::Metal {
1653 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1654 return true;
1655 }
1656 false
1657}
1658
1659pub fn graph_kv_reset(_kv_id: u64) {
1661 #[cfg(feature = "gpu")]
1662 if backend() == Backend::Wgpu {
1663 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1664 }
1665}
1666
1667pub fn q1t_matvec(
1671 model: &Arc<CmfModel>,
1672 idx: usize,
1673 xs: &[f32],
1674 rows: usize,
1675 cols: usize,
1676 out: &mut [f32],
1677) -> bool {
1678 match backend() {
1679 #[cfg(target_os = "macos")]
1680 Backend::Metal => {
1681 if metal_q1t_enabled() {
1682 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1683 } else {
1684 false
1685 }
1686 }
1687 #[cfg(feature = "gpu")]
1688 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1689 Backend::None => false,
1690 }
1691}
1692
1693#[allow(unused_variables)]
1696pub fn q4b_matvec(
1697 model: &Arc<CmfModel>,
1698 idx: usize,
1699 xs: &[f32],
1700 rows: usize,
1701 cols: usize,
1702 out: &mut [f32],
1703) -> bool {
1704 match backend() {
1705 #[cfg(target_os = "macos")]
1706 Backend::Metal => false,
1707 #[cfg(feature = "gpu")]
1708 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1709 Backend::None => false,
1710 }
1711}
1712
1713pub fn q1t_matmat(
1716 model: &Arc<CmfModel>,
1717 idx: usize,
1718 xs: &[f32],
1719 b: usize,
1720 rows: usize,
1721 cols: usize,
1722 out: &mut [f32],
1723) -> bool {
1724 match backend() {
1725 #[cfg(target_os = "macos")]
1726 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1730 #[cfg(feature = "gpu")]
1731 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1732 Backend::None => false,
1733 }
1734}
1735
1736#[cfg(target_os = "macos")]
1740pub(crate) fn metal_q1t_enabled() -> bool {
1741 std::env::var("CMF_METAL_Q1T")
1742 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1743 .unwrap_or(true)
1744}
1745
1746pub fn q1_matmat(
1748 model: &Arc<CmfModel>,
1749 idx: usize,
1750 xs: &[f32],
1751 b: usize,
1752 rows: usize,
1753 cols: usize,
1754 out: &mut [f32],
1755) -> bool {
1756 match backend() {
1757 #[cfg(feature = "gpu")]
1758 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1759 #[allow(unused_variables)]
1760 _ => false,
1761 }
1762}
1763
1764static MM_KILL: AtomicBool = AtomicBool::new(false);
1769pub(crate) fn mm_killed() -> bool {
1770 MM_KILL.load(Ordering::Relaxed)
1771}
1772pub(crate) fn mm_kill() {
1773 MM_KILL.store(true, Ordering::Relaxed);
1774}
1775
1776static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1783const MM_STRIKES_TO_KILL: u32 = 3;
1784static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1791
1792pub fn mm_kill_arm(on: bool) {
1795 MM_ARMED.store(on, Ordering::Relaxed);
1796 if on {
1797 MM_STRIKES.store(0, Ordering::Relaxed);
1798 }
1799}
1800
1801pub(crate) fn mm_budget_check(
1808 what: &str,
1809 el: std::time::Duration,
1810 budget: std::time::Duration,
1811 exempt: bool,
1812) {
1813 if el <= budget {
1814 MM_STRIKES.store(0, Ordering::Relaxed);
1815 return;
1816 }
1817 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1818 return;
1819 }
1820 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1821 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1822 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1823 if !on {
1824 tracing::info!(
1825 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1826 );
1827 return;
1828 }
1829 if n >= MM_STRIKES_TO_KILL {
1830 tracing::warn!(
1831 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1832 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1833 );
1834 mm_kill();
1835 } else {
1836 tracing::info!(
1837 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1838 );
1839 }
1840}
1841
1842#[allow(unused_variables, clippy::too_many_arguments)]
1847pub fn chunk_attend(
1848 q: &[f32],
1849 k: &[&[f32]],
1850 v: &[&[f32]],
1851 b: usize,
1852 s0: usize,
1853 nh: usize,
1854 nkv: usize,
1855 hd: usize,
1856 scale: f32,
1857 out: &mut [f32],
1858) -> bool {
1859 match backend() {
1860 #[cfg(feature = "gpu")]
1861 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1862 #[allow(unreachable_patterns)]
1863 _ => false,
1864 }
1865}
1866
1867#[allow(unused_variables, clippy::too_many_arguments)]
1871pub fn q4t_qkv(
1872 model: &Arc<CmfModel>,
1873 wq: usize,
1874 wk: usize,
1875 wv: usize,
1876 xs: &[f32],
1877 b: usize,
1878 cols: usize,
1879 rq: usize,
1880 rk: usize,
1881 rv: usize,
1882 out: &mut [f32],
1883) -> bool {
1884 match backend() {
1885 #[cfg(feature = "gpu")]
1886 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1887 #[allow(unreachable_patterns)]
1888 _ => false,
1889 }
1890}
1891
1892#[allow(unused_variables, clippy::too_many_arguments)]
1894#[allow(clippy::too_many_arguments, unused_variables)]
1898pub fn q4tp_ffn_packed(
1899 model: &Arc<CmfModel>,
1900 w1: usize,
1901 w2: usize,
1902 xs: &[f32],
1903 b: usize,
1904 hidden: usize,
1905 inter: usize,
1906 bias: Option<&[f32]>,
1907 out: &mut [f32],
1908) -> bool {
1909 match backend() {
1910 #[cfg(feature = "gpu")]
1911 Backend::Wgpu => {
1912 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1913 }
1914 #[allow(unreachable_patterns)]
1915 _ => false,
1916 }
1917}
1918
1919pub fn q4tp_ffn(
1920 model: &Arc<CmfModel>,
1921 w1: usize,
1922 w3: usize,
1923 w2: usize,
1924 xs: &[f32],
1925 b: usize,
1926 hidden: usize,
1927 inter: usize,
1928 out: &mut [f32],
1929) -> bool {
1930 match backend() {
1931 #[cfg(target_os = "macos")]
1932 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1933 #[cfg(feature = "gpu")]
1934 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1935 #[allow(unreachable_patterns)]
1936 _ => false,
1937 }
1938}
1939
1940#[allow(clippy::too_many_arguments, unused_variables)]
1945pub fn q4tp_gelu_ffn(
1946 model: &Arc<CmfModel>,
1947 w_in: usize,
1948 w_out: usize,
1949 xs: &[f32],
1950 b: usize,
1951 hidden: usize,
1952 inter: usize,
1953 bias_in: &[f32],
1954 bias_out: &[f32],
1955 out: &mut [f32],
1956) -> bool {
1957 match backend() {
1958 #[cfg(feature = "gpu")]
1959 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
1960 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
1961 ),
1962 #[allow(unreachable_patterns)]
1963 _ => false,
1964 }
1965}
1966
1967pub fn q4t_ffn(
1968 model: &Arc<CmfModel>,
1969 w1: usize,
1970 w3: usize,
1971 w2: usize,
1972 xs: &[f32],
1973 b: usize,
1974 hidden: usize,
1975 inter: usize,
1976 out: &mut [f32],
1977) -> bool {
1978 match backend() {
1979 #[cfg(target_os = "macos")]
1980 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1981 #[cfg(feature = "gpu")]
1982 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1983 #[allow(unreachable_patterns)]
1984 _ => false,
1985 }
1986}
1987
1988pub struct DitBlockArgs<'a> {
1993 pub n: usize,
1994 pub hidden: usize,
1995 pub inter: usize,
1996 pub nh: usize,
1997 pub nkv: usize,
1998 pub hd: usize,
1999 pub eps: f32,
2000 pub rope_cos: &'a [f32],
2001 pub rope_sin: &'a [f32],
2002 pub norm1: &'a [f32],
2003 pub norm2: &'a [f32],
2004 pub ffn_norm1: &'a [f32],
2005 pub ffn_norm2: &'a [f32],
2006 pub norm_q: &'a [f32],
2007 pub norm_k: &'a [f32],
2008 pub s_msa: &'a [f32],
2009 pub gate_msa: &'a [f32],
2010 pub s_mlp: &'a [f32],
2011 pub gate_mlp: &'a [f32],
2012 pub wq: usize,
2013 pub wk: usize,
2014 pub wv: usize,
2015 pub wo: usize,
2016 pub w1: usize,
2017 pub w3: usize,
2018 pub w2: usize,
2019 pub q4tp: bool,
2023 pub resident_in: bool,
2026 pub resident_out: bool,
2030}
2031
2032pub fn dit_chain_supported() -> bool {
2036 #[cfg(feature = "gpu")]
2037 {
2038 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2039 }
2040 #[allow(unreachable_code)]
2041 false
2042}
2043
2044pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2047 #[cfg(feature = "gpu")]
2048 {
2049 if matches!(backend(), Backend::Wgpu) {
2050 return crate::gpu_wgpu::dit_state_fetch(_x);
2051 }
2052 }
2053 false
2054}
2055
2056#[allow(unused_variables)]
2060#[allow(unused_variables, clippy::too_many_arguments)]
2064pub fn dit_qkv(
2065 model: &Arc<CmfModel>,
2066 wq: usize,
2067 wk: usize,
2068 wv: usize,
2069 xs: &[f32],
2070 b: usize,
2071 hidden: usize,
2072 qrows: usize,
2073 kvrows: usize,
2074 q_out: &mut [f32],
2075 k_out: &mut [f32],
2076 v_out: &mut [f32],
2077) -> bool {
2078 match backend() {
2079 #[cfg(feature = "gpu")]
2080 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2081 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2082 ),
2083 #[allow(unreachable_patterns)]
2084 _ => false,
2085 }
2086}
2087
2088pub struct QwenImageAttentionArgs<'a> {
2096 pub image: &'a [f32],
2097 pub text: &'a [f32],
2098 pub image_tokens: usize,
2099 pub text_tokens: usize,
2100 pub heads: usize,
2101 pub head_dim: usize,
2102 pub image_q: usize,
2103 pub image_k: usize,
2104 pub image_v: usize,
2105 pub text_q: usize,
2106 pub text_k: usize,
2107 pub text_v: usize,
2108 pub image_out: usize,
2109 pub text_out: usize,
2110 pub image_q_norm: &'a [f32],
2111 pub image_k_norm: &'a [f32],
2112 pub text_q_norm: &'a [f32],
2113 pub text_k_norm: &'a [f32],
2114 pub image_cos: &'a [f32],
2115 pub image_sin: &'a [f32],
2116 pub text_cos: &'a [f32],
2117 pub text_sin: &'a [f32],
2118 pub image_q_bias: &'a [f32],
2119 pub image_k_bias: &'a [f32],
2120 pub image_v_bias: &'a [f32],
2121 pub text_q_bias: &'a [f32],
2122 pub text_k_bias: &'a [f32],
2123 pub text_v_bias: &'a [f32],
2124 pub image_out_bias: &'a [f32],
2125 pub text_out_bias: &'a [f32],
2126 pub image_proj: &'a mut [f32],
2127 pub text_proj: &'a mut [f32],
2128}
2129
2130#[allow(clippy::too_many_fields)]
2135pub struct QwenImageChainBlock<'a> {
2136 pub image_mod: &'a [f32],
2137 pub text_mod: &'a [f32],
2138 pub image_q: usize,
2139 pub image_k: usize,
2140 pub image_v: usize,
2141 pub text_q: usize,
2142 pub text_k: usize,
2143 pub text_v: usize,
2144 pub image_out: usize,
2145 pub text_out: usize,
2146 pub image_q_norm: &'a [f32],
2147 pub image_k_norm: &'a [f32],
2148 pub text_q_norm: &'a [f32],
2149 pub text_k_norm: &'a [f32],
2150 pub image_q_bias: &'a [f32],
2151 pub image_k_bias: &'a [f32],
2152 pub image_v_bias: &'a [f32],
2153 pub text_q_bias: &'a [f32],
2154 pub text_k_bias: &'a [f32],
2155 pub text_v_bias: &'a [f32],
2156 pub image_out_bias: &'a [f32],
2157 pub text_out_bias: &'a [f32],
2158 pub image_attn_gate: &'a [f32],
2159 pub text_attn_gate: &'a [f32],
2160 pub image_mlp_in: usize,
2161 pub image_mlp_out: usize,
2162 pub text_mlp_in: usize,
2163 pub text_mlp_out: usize,
2164 pub image_mlp_in_bias: &'a [f32],
2165 pub image_mlp_out_bias: &'a [f32],
2166 pub text_mlp_in_bias: &'a [f32],
2167 pub text_mlp_out_bias: &'a [f32],
2168}
2169
2170#[allow(clippy::too_many_fields)]
2176pub struct QwenImageBlockArgs<'a> {
2177 pub image: &'a mut [f32],
2180 pub text: &'a mut [f32],
2181 pub image_norm: &'a [f32],
2182 pub text_norm: &'a [f32],
2183 pub image_tokens: usize,
2184 pub text_tokens: usize,
2185 pub heads: usize,
2186 pub head_dim: usize,
2187 pub image_cos: &'a [f32],
2188 pub image_sin: &'a [f32],
2189 pub text_cos: &'a [f32],
2190 pub text_sin: &'a [f32],
2191 pub image_q: usize,
2192 pub image_k: usize,
2193 pub image_v: usize,
2194 pub text_q: usize,
2195 pub text_k: usize,
2196 pub text_v: usize,
2197 pub image_out: usize,
2198 pub text_out: usize,
2199 pub image_q_norm: &'a [f32],
2200 pub image_k_norm: &'a [f32],
2201 pub text_q_norm: &'a [f32],
2202 pub text_k_norm: &'a [f32],
2203 pub image_q_bias: &'a [f32],
2204 pub image_k_bias: &'a [f32],
2205 pub image_v_bias: &'a [f32],
2206 pub text_q_bias: &'a [f32],
2207 pub text_k_bias: &'a [f32],
2208 pub text_v_bias: &'a [f32],
2209 pub image_out_bias: &'a [f32],
2210 pub text_out_bias: &'a [f32],
2211 pub image_attn_gate: &'a [f32],
2212 pub text_attn_gate: &'a [f32],
2213 pub image_mlp_in: usize,
2214 pub image_mlp_out: usize,
2215 pub text_mlp_in: usize,
2216 pub text_mlp_out: usize,
2217 pub image_mlp_in_bias: &'a [f32],
2218 pub image_mlp_out_bias: &'a [f32],
2219 pub text_mlp_in_bias: &'a [f32],
2220 pub text_mlp_out_bias: &'a [f32],
2221 pub image_mlp_mod: &'a [f32],
2222 pub text_mlp_mod: &'a [f32],
2223 pub image_mlp_gate: &'a [f32],
2224 pub text_mlp_gate: &'a [f32],
2225}
2226
2227pub struct QwenImageChainArgs<'a> {
2232 pub image: &'a mut [f32],
2233 pub text: &'a mut [f32],
2234 pub image_tokens: usize,
2235 pub text_tokens: usize,
2236 pub heads: usize,
2237 pub head_dim: usize,
2238 pub image_cos: &'a [f32],
2239 pub image_sin: &'a [f32],
2240 pub text_cos: &'a [f32],
2241 pub text_sin: &'a [f32],
2242 pub blocks: &'a [QwenImageChainBlock<'a>],
2243}
2244
2245#[allow(unused_variables)]
2246pub fn qwen_image_attention(
2247 model: &Arc<CmfModel>,
2248 args: &mut QwenImageAttentionArgs<'_>,
2249) -> bool {
2250 match backend() {
2251 #[cfg(feature = "gpu")]
2252 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2253 #[allow(unreachable_patterns)]
2254 _ => false,
2255 }
2256}
2257
2258#[allow(unused_variables)]
2259pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2260 match backend() {
2261 #[cfg(feature = "gpu")]
2262 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2263 #[allow(unreachable_patterns)]
2264 _ => false,
2265 }
2266}
2267
2268#[allow(unused_variables)]
2272pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2273 match backend() {
2274 #[cfg(feature = "gpu")]
2275 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2276 #[allow(unreachable_patterns)]
2277 _ => false,
2278 }
2279}
2280
2281#[allow(unused_variables, clippy::too_many_arguments)]
2288pub fn qwen_image_mlp_inplace(
2289 model: &Arc<CmfModel>,
2290 w_in: usize,
2291 w_out: usize,
2292 data: &mut [f32],
2293 batch: usize,
2294 hidden: usize,
2295 inter: usize,
2296 bias_in: &[f32],
2297 bias_out: &[f32],
2298 modulation: &[f32],
2299 gate: &[f32],
2300) -> bool {
2301 match backend() {
2302 #[cfg(feature = "gpu")]
2303 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2304 model,
2305 w_in,
2306 w_out,
2307 data,
2308 batch,
2309 hidden,
2310 inter,
2311 bias_in,
2312 bias_out,
2313 modulation,
2314 gate,
2315 ),
2316 #[allow(unreachable_patterns)]
2317 _ => false,
2318 }
2319}
2320
2321pub fn fused_dit_block_available() -> bool {
2325 #[cfg(target_os = "macos")]
2326 {
2327 matches!(backend(), Backend::Metal) && fused_block_trusted()
2328 }
2329 #[cfg(not(target_os = "macos"))]
2330 {
2331 false
2332 }
2333}
2334
2335pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2336 dit_block_seg(model, a, &[a.n], x)
2337}
2338
2339pub fn dit_block_seg(
2343 model: &Arc<CmfModel>,
2344 a: &DitBlockArgs,
2345 segs: &[usize],
2346 x: &mut [f32],
2347) -> bool {
2348 match backend() {
2349 #[cfg(target_os = "macos")]
2350 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2351 #[cfg(feature = "gpu")]
2358 Backend::Wgpu
2359 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2360 Some("0") => false,
2361 Some(_) => true,
2362 None => crate::gpu_wgpu::discrete_active(),
2363 } =>
2364 {
2365 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2366 }
2367 #[allow(unreachable_patterns)]
2368 _ => false,
2369 }
2370}
2371
2372pub struct VaeResnetArgs<'a> {
2376 pub groups: usize,
2377 pub ic: usize,
2378 pub oc: usize,
2379 pub h: usize,
2380 pub w: usize,
2381 pub n1w: &'a [f32],
2382 pub n1b: &'a [f32],
2383 pub c1w: &'a [f32],
2384 pub c1b: &'a [f32],
2385 pub c1k: usize,
2386 pub n2w: &'a [f32],
2387 pub n2b: &'a [f32],
2388 pub c2w: &'a [f32],
2389 pub c2b: &'a [f32],
2390 pub c2k: usize,
2391 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2392}
2393
2394#[allow(unused_variables)]
2397pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2398 match backend() {
2399 #[cfg(target_os = "macos")]
2400 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2401 _ => false,
2402 }
2403}
2404
2405#[allow(unused_variables, clippy::too_many_arguments)]
2408pub fn vae_upsample_conv(
2409 w: &[f32],
2410 bias: &[f32],
2411 x: &[f32],
2412 ic: usize,
2413 oc: usize,
2414 h: usize,
2415 w_img: usize,
2416 k: usize,
2417 out: &mut [f32],
2418) -> bool {
2419 match backend() {
2420 #[cfg(target_os = "macos")]
2421 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2422 #[cfg(feature = "gpu")]
2423 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2424 #[allow(unreachable_patterns)]
2425 _ => false,
2426 }
2427}
2428
2429#[allow(unused_variables, clippy::too_many_arguments)]
2432pub fn vae_conv2d(
2433 w: &[f32],
2434 bias: &[f32],
2435 x: &[f32],
2436 ic: usize,
2437 oc: usize,
2438 h: usize,
2439 w_img: usize,
2440 k: usize,
2441 out: &mut [f32],
2442) -> bool {
2443 match backend() {
2444 #[cfg(target_os = "macos")]
2445 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2446 #[cfg(feature = "gpu")]
2447 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2448 #[allow(unreachable_patterns)]
2449 _ => false,
2450 }
2451}
2452
2453#[allow(unused_variables, clippy::too_many_arguments)]
2457#[allow(unused_variables)]
2461#[allow(clippy::too_many_arguments)]
2462#[allow(clippy::too_many_arguments, unused_variables)]
2465pub fn dit_qkv_attention(
2466 model: &Arc<CmfModel>,
2467 qkv_idx: usize,
2468 xn: &[f32],
2469 n: usize,
2470 hidden: usize,
2471 nh: usize,
2472 hd: usize,
2473 scale: f32,
2474 nr: (&[f32], &[f32], &[f32], f32),
2475 out: &mut [f32],
2476) -> bool {
2477 match backend() {
2478 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2479 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2480 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2481 ),
2482 #[allow(unreachable_patterns)]
2483 _ => false,
2484 }
2485}
2486
2487#[allow(clippy::too_many_arguments)]
2490pub fn dit_qkv_attn_out(
2491 model: &Arc<CmfModel>,
2492 qkv_idx: usize,
2493 out_idx: usize,
2494 xn: &[f32],
2495 n: usize,
2496 hidden: usize,
2497 nh: usize,
2498 hd: usize,
2499 scale: f32,
2500 nr: (&[f32], &[f32], &[f32], f32),
2501 proj: &mut [f32],
2502) -> bool {
2503 match backend() {
2504 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2505 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2506 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2507 ),
2508 #[allow(unreachable_patterns)]
2509 _ => false,
2510 }
2511}
2512
2513#[allow(clippy::too_many_arguments)]
2515pub fn vae_qkv_attn_out(
2516 model: &Arc<CmfModel>,
2517 qkv_idx: usize,
2518 out_idx: usize,
2519 xn: &[f32],
2520 n: usize,
2521 dim: usize,
2522 nh: usize,
2523 hd: usize,
2524 scale: f32,
2525 angles: &[f32],
2526 eps: f32,
2527 qkv_bias: &[f32],
2528 proj: &mut [f32],
2529) -> bool {
2530 match backend() {
2531 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2532 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2533 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2534 ),
2535 #[allow(unreachable_patterns)]
2536 _ => false,
2537 }
2538}
2539
2540#[allow(clippy::too_many_arguments)]
2541pub fn vae_attention_packed(
2542 qkv: &[f32],
2543 nh: usize,
2544 n: usize,
2545 hd: usize,
2546 scale: f32,
2547 angles: &[f32],
2548 eps: f32,
2549 out: &mut [f32],
2550) -> bool {
2551 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2552}
2553
2554#[allow(clippy::too_many_arguments)]
2555pub fn vae_attention_packed_layout(
2556 qkv: &[f32],
2557 nh: usize,
2558 n: usize,
2559 hd: usize,
2560 scale: f32,
2561 angles: &[f32],
2562 eps: f32,
2563 out: &mut [f32],
2564 layout: u32,
2565) -> bool {
2566 match backend() {
2567 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2568 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2569 qkv, nh, n, hd, scale, angles, eps, out, layout,
2570 ),
2571 #[allow(unreachable_patterns)]
2572 _ => false,
2573 }
2574}
2575
2576#[allow(clippy::too_many_arguments)]
2577pub fn dit_split_only(
2578 qkv: &[f32],
2579 nh: usize,
2580 n: usize,
2581 hd: usize,
2582 layout: u32,
2583 norm: Option<(&[f32], f32)>,
2584 out_q: &mut [f32],
2585) -> bool {
2586 match backend() {
2587 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2588 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2589 #[allow(unreachable_patterns)]
2590 _ => false,
2591 }
2592}
2593
2594pub fn gemm_nt_f32_transient(
2602 x: &[f32],
2603 w: &[f32],
2604 y: &mut [f32],
2605 n: usize,
2606 k: usize,
2607 m: usize,
2608) -> bool {
2609 match backend() {
2610 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2611 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2612 #[allow(unreachable_patterns)]
2613 _ => false,
2614 }
2615}
2616
2617pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2618 match backend() {
2619 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2620 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2621 #[allow(unreachable_patterns)]
2622 _ => false,
2623 }
2624}
2625
2626#[allow(clippy::too_many_arguments)]
2629pub fn music3_ffn(
2630 model: &std::sync::Arc<CmfModel>,
2631 idx_in: usize,
2632 idx_out: usize,
2633 h: &[f32],
2634 bias_in: &[f32],
2635 n: usize,
2636 hs: usize,
2637 inter: usize,
2638 out: &mut [f32],
2639) -> bool {
2640 match backend() {
2641 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2642 Backend::Wgpu => {
2643 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2644 }
2645 #[allow(unreachable_patterns)]
2646 _ => false,
2647 }
2648}
2649
2650#[allow(clippy::too_many_arguments)]
2654pub fn conv1d_gemm(
2655 x: &[f32],
2656 w: &[f32],
2657 ic: usize,
2658 oc: usize,
2659 n: usize,
2660 k: usize,
2661 pad: usize,
2662 dil: usize,
2663 out_n: usize,
2664 yt: &mut [f32],
2665) -> bool {
2666 match backend() {
2667 #[cfg(target_os = "macos")]
2668 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2669 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2670 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2671 #[allow(unreachable_patterns)]
2672 _ => false,
2673 }
2674}
2675
2676#[allow(clippy::too_many_arguments)]
2678pub fn vae_conv2d_coop(
2679 w: &[f32],
2680 bias: Option<&[f32]>,
2681 x: &[f32],
2682 ic: usize,
2683 oc: usize,
2684 h: usize,
2685 wi: usize,
2686 k: usize,
2687 out: &mut [f32],
2688) -> bool {
2689 match backend() {
2690 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2691 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2692 #[allow(unreachable_patterns)]
2693 _ => false,
2694 }
2695}
2696
2697pub fn dit_attention_packed(
2698 qkv: &[f32],
2699 nh: usize,
2700 n: usize,
2701 hd: usize,
2702 scale: f32,
2703 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2706 out: &mut [f32],
2707) -> bool {
2708 match backend() {
2709 #[cfg(feature = "gpu")]
2716 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2717 #[allow(unreachable_patterns)]
2718 _ => false,
2719 }
2720}
2721
2722pub fn dit_attention_packed_available() -> bool {
2730 #[allow(unreachable_patterns)]
2731 match backend() {
2732 #[cfg(feature = "gpu")]
2733 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2734 _ => false,
2735 }
2736}
2737
2738pub fn dit_attention(
2739 qh: &[f32],
2740 kh: &[f32],
2741 vh: &[f32],
2742 nh: usize,
2743 nkv: usize,
2744 n: usize,
2745 hd: usize,
2746 scale: f32,
2747 out: &mut [f32],
2748) -> bool {
2749 match backend() {
2750 #[cfg(target_os = "macos")]
2751 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2752 #[cfg(feature = "gpu")]
2753 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2754 #[allow(unreachable_patterns)]
2755 _ => false,
2756 }
2757}
2758
2759#[allow(unused_variables)]
2764pub fn q4tp_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(target_os = "macos")]
2775 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2776 #[cfg(feature = "gpu")]
2777 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2778 #[allow(unreachable_patterns)]
2779 _ => false,
2780 }
2781}
2782
2783pub fn q2tp_matmat(
2786 model: &Arc<CmfModel>,
2787 idx: usize,
2788 xs: &[f32],
2789 b: usize,
2790 rows: usize,
2791 cols: usize,
2792 out: &mut [f32],
2793) -> bool {
2794 match backend() {
2795 #[cfg(target_os = "macos")]
2796 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2797 #[cfg(feature = "gpu")]
2798 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2799 #[allow(unreachable_patterns)]
2800 _ => false,
2801 }
2802}
2803
2804pub fn q2tp_affine_matmat(
2807 model: &Arc<CmfModel>,
2808 idx: usize,
2809 xs: &[f32],
2810 b: usize,
2811 rows: usize,
2812 cols: usize,
2813 out: &mut [f32],
2814) -> bool {
2815 match backend() {
2816 #[cfg(target_os = "macos")]
2817 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2818 #[cfg(feature = "gpu")]
2819 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2820 #[allow(unreachable_patterns)]
2821 _ => false,
2822 }
2823}
2824
2825pub fn q2tp_matvec(
2827 model: &Arc<CmfModel>,
2828 idx: usize,
2829 xs: &[f32],
2830 rows: usize,
2831 cols: usize,
2832 out: &mut [f32],
2833) -> bool {
2834 match backend() {
2835 #[cfg(target_os = "macos")]
2836 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
2837 #[cfg(feature = "gpu")]
2838 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
2839 #[allow(unreachable_patterns)]
2840 _ => false,
2841 }
2842}
2843
2844pub fn q2tp_affine_matvec(
2848 model: &Arc<CmfModel>,
2849 idx: usize,
2850 xs: &[f32],
2851 rows: usize,
2852 cols: usize,
2853 out: &mut [f32],
2854) -> bool {
2855 match backend() {
2856 #[cfg(target_os = "macos")]
2857 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2858 #[cfg(feature = "gpu")]
2859 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2860 #[allow(unreachable_patterns)]
2861 _ => false,
2862 }
2863}
2864
2865pub fn q4tp_matvec(
2870 model: &Arc<CmfModel>,
2871 idx: usize,
2872 xs: &[f32],
2873 rows: usize,
2874 cols: usize,
2875 out: &mut [f32],
2876) -> bool {
2877 match backend() {
2878 #[cfg(target_os = "macos")]
2879 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2880 #[cfg(feature = "gpu")]
2881 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2882 #[allow(unreachable_patterns)]
2883 _ => false,
2884 }
2885}
2886
2887pub fn q4t_matvec(
2893 model: &Arc<CmfModel>,
2894 idx: usize,
2895 xs: &[f32],
2896 rows: usize,
2897 cols: usize,
2898 out: &mut [f32],
2899) -> bool {
2900 match backend() {
2901 #[cfg(target_os = "macos")]
2902 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2903 #[allow(unreachable_patterns)]
2904 _ => false,
2905 }
2906}
2907
2908pub fn q4t_matmat(
2909 model: &Arc<CmfModel>,
2910 idx: usize,
2911 xs: &[f32],
2912 b: usize,
2913 rows: usize,
2914 cols: usize,
2915 out: &mut [f32],
2916) -> bool {
2917 match backend() {
2918 #[cfg(target_os = "macos")]
2919 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2920 #[cfg(feature = "gpu")]
2921 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2922 #[allow(unreachable_patterns)]
2923 _ => false,
2924 }
2925}
2926
2927#[cfg(target_os = "macos")]
2929pub use crate::gpu_metal::{
2930 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2931 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2932};
2933
2934#[cfg(target_os = "macos")]
2936pub fn gdn_block(
2937 model: &Arc<CmfModel>,
2938 layers: &[GdnGpuLayer],
2939 states: &mut [&mut [f32]],
2940 cfg: &GdnGpuCfg,
2941 h: &mut [f32],
2942) -> bool {
2943 match backend() {
2944 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2945 _ => false,
2946 }
2947}
2948
2949#[allow(unused_variables)]
2951pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2952 match backend() {
2953 #[cfg(target_os = "macos")]
2954 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2955 #[cfg(feature = "gpu")]
2956 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2957 Backend::None => false,
2958 }
2959}
2960
2961#[allow(unused_variables)]
2963pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2964 match backend() {
2965 #[cfg(target_os = "macos")]
2966 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2967 #[cfg(feature = "gpu")]
2968 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2969 Backend::None => false,
2970 }
2971}
2972
2973static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2989static 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)];
2993
2994const GRAPH_RACE_SAMPLES: u32 = 4;
2996
2997static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
3007
3008pub fn graph_mark_unsupported() {
3013 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
3014 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
3015 }
3016}
3017
3018pub fn graph_unsupported() -> bool {
3019 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
3020}
3021
3022pub fn graph_unsupported_reset() {
3024 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
3025}
3026
3027pub fn graph_race_begin_generation() {
3028 #[cfg(feature = "gpu")]
3033 {
3034 static FLUSHED: std::sync::Once = std::sync::Once::new();
3046 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3047 if FIRST.swap(false, Ordering::Relaxed) {
3048 } else {
3050 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3051 }
3052 }
3053 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3054 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3055 return;
3056 }
3057 let (gn, cn) = (
3058 GRAPH_N[1].load(Ordering::Relaxed),
3059 GRAPH_N[0].load(Ordering::Relaxed),
3060 );
3061 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3062 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3063 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3064 let verdict = if g_avg < c_avg { 1 } else { 2 };
3065 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3066 tracing::info!(
3067 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3068 g_avg as f64 / 1e6,
3069 c_avg as f64 / 1e6,
3070 if verdict == 1 { "graph" } else { "normal path" }
3071 );
3072 return;
3073 }
3074 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3075 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3076}
3077
3078pub fn graph_race_use_graph(trusted: bool) -> bool {
3082 if trusted {
3083 return true;
3084 }
3085 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3086 1 => true,
3087 2 => false,
3088 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3089 }
3090}
3091
3092pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3097 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3098 return false;
3099 }
3100 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3101 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3102 if !first || cn == 0 {
3103 return false;
3104 }
3105 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3106 let ns = dur.as_nanos() as u64;
3107 if ns > 1_000_000_000 && ns > 4 * c_avg {
3108 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3109 tracing::info!(
3110 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3111 ns as f64 / 1e6,
3112 c_avg as f64 / 1e6
3113 );
3114 return true;
3115 }
3116 false
3117}
3118
3119pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3123 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3124 return;
3125 }
3126 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3127 if tok == 0 {
3128 return;
3129 }
3130 let i = used_graph as usize;
3131 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3132 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3133}
3134
3135pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3145 #[inline]
3146 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3147 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3148 for c in chunks.chunks_exact(8) {
3149 h ^= u64::from_le_bytes(c.try_into().unwrap());
3150 h = h.wrapping_mul(0x100_0000_01b3);
3151 }
3152 for &b in tail {
3153 h ^= b as u64;
3154 h = h.wrapping_mul(0x100_0000_01b3);
3155 }
3156 h
3157 }
3158 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3159 if data.len() <= 4096 {
3160 return fnv(h, data);
3161 }
3162 let step = (data.len() - 64) / 63;
3163 for i in 0..64 {
3164 h = fnv(h, &data[i * step..i * step + 64]);
3165 }
3166 h
3167}
3168
3169pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3172 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3173 fp_bytes(bytes)
3174}
3175
3176#[cfg(test)]
3177mod fp_tests {
3178 use super::fp_bytes;
3179
3180 #[test]
3185 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3186 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3188 let h0 = fp_bytes(&base);
3189 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3190 let mut dense = base.clone();
3193 for b in dense.iter_mut() {
3194 *b = b.wrapping_add(1);
3195 }
3196 assert_ne!(
3197 h0,
3198 fp_bytes(&dense),
3199 "a fully different tensor slipped through"
3200 );
3201 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3204 let mut small = vec![3u8; 4096];
3207 let hs = fp_bytes(&small);
3208 small[2048] ^= 1;
3209 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3210 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3212 let v = vec![9u8; n];
3213 let _ = fp_bytes(&v); }
3215 }
3216}
3217
3218pub fn bake_release() {
3222 #[cfg(feature = "gpu")]
3223 crate::gpu_wgpu::bake_release();
3224}
3225
3226pub fn bake_precision_strict(on: bool) {
3230 #[cfg(feature = "gpu")]
3231 crate::gpu_wgpu::bake_precision_strict(on);
3232 #[cfg(not(feature = "gpu"))]
3233 let _ = on;
3234}
3235
3236pub fn hostprof_encode_done(t0: std::time::Instant) {
3242 use std::sync::atomic::{AtomicU64, Ordering};
3243 static ENC: AtomicU64 = AtomicU64::new(0);
3244 static N: AtomicU64 = AtomicU64::new(0);
3245 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3246 return;
3247 }
3248 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3249 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3250 if n % 100 == 0 {
3251 eprintln!(
3252 "hostprof: encode {:.2} ms/token over {n} tokens",
3253 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3254 );
3255 }
3256}
3257
3258pub fn hostprof_total(t0: std::time::Instant) {
3259 use std::sync::atomic::{AtomicU64, Ordering};
3260 static TOT: AtomicU64 = AtomicU64::new(0);
3261 static N: AtomicU64 = AtomicU64::new(0);
3262 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3263 return;
3264 }
3265 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3266 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3267 if n % 100 == 0 {
3268 eprintln!(
3269 "hostprof: total {:.2} ms/token over {n} tokens",
3270 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3271 );
3272 }
3273}
3274
3275pub fn stageprof(stage: u32, dt: std::time::Duration) {
3279 use std::sync::atomic::{AtomicU64, Ordering};
3280 static NS: [AtomicU64; 4] = [
3281 AtomicU64::new(0),
3282 AtomicU64::new(0),
3283 AtomicU64::new(0),
3284 AtomicU64::new(0),
3285 ];
3286 static N: AtomicU64 = AtomicU64::new(0);
3287 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3288 return;
3289 }
3290 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3291 if stage == 1 {
3292 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3293 if n % 200 == 0 {
3294 eprintln!(
3295 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3296 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3297 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3298 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3299 );
3300 }
3301 }
3302}
3303
3304pub fn weight_bytes_dispatched() -> u64 {
3307 let mut total = 0u64;
3308 #[cfg(target_os = "macos")]
3309 {
3310 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3311 }
3312 #[cfg(feature = "gpu")]
3313 {
3314 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3315 }
3316 total
3317}
3318
3319pub fn weight_bytes_by() -> [u64; 6] {
3322 #[cfg(target_os = "macos")]
3323 {
3324 let mut o = [0u64; 6];
3325 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3326 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3327 }
3328 return o;
3329 }
3330 #[allow(unreachable_code)]
3331 [0; 6]
3332}
3333
3334#[cfg(test)]
3335mod probe_warmup_tests {
3336 use super::*;
3337 use std::time::Duration;
3338
3339 fn ms(v: f64) -> Duration {
3340 Duration::from_nanos((v * 1e6) as u64)
3341 }
3342
3343 #[test]
3348 fn one_cold_first_sample_does_not_lose_the_class() {
3349 let p = Probe::new();
3350 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3352 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3353 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3354 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3355 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3356 assert_eq!(
3357 p.state.load(Ordering::Relaxed),
3358 1,
3359 "the device is 3x faster once warm and must win"
3360 );
3361 }
3362
3363 #[test]
3367 fn the_warmup_is_spent_once_and_never_underflows() {
3368 let p = Probe::new();
3369 for _ in 0..8 {
3370 probe_record_into(&p, "matmat", None, true, ms(10.0));
3371 }
3372 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3373 assert_eq!(
3374 p.gpu_n.load(Ordering::Relaxed),
3375 7,
3376 "one sample burned, the rest counted"
3377 );
3378 }
3379
3380 #[test]
3386 fn a_class_whose_device_always_declines_settles_on_the_host() {
3387 let c = OpClass::MatmatWide;
3391 let p = &PROBES[c as usize];
3392 p.state.store(0, Ordering::Relaxed);
3393 p.declines.store(0, Ordering::Relaxed);
3394 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3395 probe_note_decline(c);
3396 }
3397 assert_eq!(
3398 p.state.load(Ordering::Relaxed),
3399 0,
3400 "one short of the limit is still a question, not an answer"
3401 );
3402 probe_note_decline(c);
3403 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3404 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3405 p.state.store(0, Ordering::Relaxed);
3406 p.declines.store(0, Ordering::Relaxed);
3407 }
3408
3409 #[test]
3412 fn a_slow_device_still_loses_after_the_warmup() {
3413 let p = Probe::new();
3414 for _ in 0..4 {
3415 probe_record_into(&p, "matvec", None, true, ms(40.0));
3416 }
3417 for _ in 0..4 {
3418 probe_record_into(&p, "matvec", None, false, ms(2.0));
3419 }
3420 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3421 }
3422}
3423
3424pub(crate) struct ImageStageGuard {
3427 #[cfg(target_os = "macos")]
3428 metal: Option<crate::gpu_metal::ImageStageGuard>,
3429 #[cfg(feature = "gpu")]
3430 wgpu: crate::gpu_wgpu::ImageStageGuard,
3431}
3432
3433pub(crate) fn image_stage_scope() -> ImageStageGuard {
3434 ImageStageGuard {
3435 #[cfg(target_os = "macos")]
3436 metal: if matches!(backend(), Backend::Metal) {
3437 Some(crate::gpu_metal::image_stage_scope())
3438 } else {
3439 None
3440 },
3441 #[cfg(feature = "gpu")]
3442 wgpu: crate::gpu_wgpu::image_stage_scope(),
3443 }
3444}
3445
3446impl ImageStageGuard {
3447 pub(crate) fn track_model(&mut self, uid: u64) {
3448 #[cfg(target_os = "macos")]
3449 if let Some(metal) = &mut self.metal {
3450 metal.track_model(uid);
3451 }
3452 #[cfg(feature = "gpu")]
3453 self.wgpu.track_model(uid);
3454 #[cfg(not(target_os = "macos"))]
3455 let _ = uid;
3456 }
3457}