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 fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
36 struct Restore(bool);
37 impl Drop for Restore {
38 fn drop(&mut self) {
39 CPU_ONLY.with(|c| c.set(self.0));
40 }
41 }
42 let previous = CPU_ONLY.with(|c| c.replace(true));
43 let _restore = Restore(previous);
44 f()
45}
46
47pub fn probe_set_device(label: &str) {
52 let _ = DEVICE_LABEL.set(label.to_string());
53}
54
55fn device_label() -> &'static str {
56 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
57}
58
59static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
60
61static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
70
71pub fn set_cache_dir(dir: std::path::PathBuf) {
73 let _ = CACHE_DIR.set(dir);
74}
75
76pub fn cache_dir_pub() -> std::path::PathBuf {
78 cache_dir()
79}
80
81fn cache_dir() -> std::path::PathBuf {
82 if let Some(d) = CACHE_DIR.get() {
83 return d.clone();
84 }
85 match std::env::var_os("TMPDIR") {
86 Some(t) => std::path::PathBuf::from(t),
87 None => std::env::temp_dir(),
88 }
89}
90
91fn probe_cache_path() -> Option<std::path::PathBuf> {
94 match std::env::var("CMF_PROBE_CACHE") {
95 Ok(v) if v == "0" => None,
96 Ok(v) => Some(std::path::PathBuf::from(v)),
97 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
98 }
99}
100
101fn probe_cache_key_named(class: &str) -> String {
105 format!(
106 "{}\t{}\t{}",
107 env!("CARGO_PKG_VERSION"),
108 device_label(),
109 class
110 )
111}
112
113const CLASS_NAMES: [&str; 7] = [
114 "ffn",
115 "matvec",
116 "matmat",
117 "qkv-batch",
118 "matmat-wide",
119 "lm-head",
120 "gemm-nt",
121];
122
123fn probe_cache_load() {
132 static ONCE: std::sync::Once = std::sync::Once::new();
133 ONCE.call_once(|| {
134 let Some(path) = probe_cache_path() else {
135 return;
136 };
137 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
142 return;
143 }
144 let Ok(text) = std::fs::read_to_string(&path) else {
145 return;
146 };
147 probe_cache_adopt(&text);
148 });
149}
150
151fn probe_cache_adopt(text: &str) {
155 for line in text.lines() {
156 let Some((key, verdict)) = line.rsplit_once('\t') else {
157 continue;
158 };
159 let winner = match verdict.trim() {
160 "gpu" => 1u8,
161 "cpu" => 2u8,
162 _ => continue,
163 };
164 for (i, name) in CLASS_NAMES.iter().enumerate() {
165 if probe_cache_key_named(name) == key {
166 let _ = PROBES[i].state.compare_exchange(
167 0,
168 winner,
169 Ordering::Relaxed,
170 Ordering::Relaxed,
171 );
172 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
173 }
174 }
175 }
176}
177
178fn probe_cache_store(c: OpClass, winner: u8) {
181 let Some(path) = probe_cache_path() else {
182 return;
183 };
184 let line = format!(
185 "{}\t{}\n",
186 probe_cache_key_named(CLASS_NAMES[c as usize]),
187 if winner == 1 { "gpu" } else { "cpu" }
188 );
189 use std::io::Write;
190 if let Ok(mut f) = std::fs::OpenOptions::new()
191 .create(true)
192 .append(true)
193 .open(&path)
194 {
195 let _ = f.write_all(line.as_bytes());
196 }
197}
198
199pub fn cold_epoch() -> u64 {
205 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
206}
207static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
208
209pub(crate) fn probe_note_cold() {
210 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
211 PROBE_COLD.with(|c| c.set(true));
212}
213
214pub(crate) fn probe_was_cold() -> bool {
218 PROBE_COLD.with(|c| c.get())
219}
220
221pub fn set_layer(l: i64) {
223 CUR_LAYER.with(|c| c.set(l));
224}
225
226pub fn cur_layer() -> i64 {
228 CUR_LAYER.with(|c| c.get())
229}
230
231fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
234 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
235 R.get_or_init(|| {
236 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
237 let mut v = Vec::new();
238 for part in s.split(',') {
239 let part = part.trim();
240 match part.split_once('-') {
241 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
242 None => {
243 let x: i64 = part.parse().ok()?;
244 v.push((x, x));
245 }
246 }
247 }
248 Some(v)
249 })
250}
251
252fn layer_allowed() -> bool {
253 match layer_ranges() {
254 None => true,
255 Some(ranges) => {
256 let cur = CUR_LAYER.with(|c| c.get());
257 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
258 }
259 }
260}
261
262pub fn enabled_here() -> bool {
266 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
267}
268
269#[derive(Clone, Copy)]
281pub enum OpClass {
282 Ffn = 0,
284 Matvec = 1,
286 Matmat = 2,
288 Batch = 3,
290 MatmatWide = 4,
296 MatvecHead = 5,
303 GemmNt = 6,
310}
311
312pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
316 if rows * cols >= 67_108_864 {
317 OpClass::MatvecHead
318 } else {
319 OpClass::Matvec
320 }
321}
322
323pub enum ProbeArm {
325 Gpu,
327 CpuTimed,
329 Cpu,
331}
332
333const PROBE_SAMPLES: u32 = 6;
335
336const PROBE_DECLINE_LIMIT: u32 = 16;
340
341const PROBE_WARMUP: u32 = 1;
343
344struct Probe {
345 state: AtomicU8,
347 flip: AtomicU32,
348 gpu_ns: AtomicU64,
349 gpu_n: AtomicU32,
350 declines: AtomicU32,
360 gpu_burn: AtomicU32,
373 cpu_ns: AtomicU64,
374 cpu_n: AtomicU32,
375 gpu_min: AtomicU64,
382 cpu_min: AtomicU64,
383}
384
385impl Probe {
386 const fn new() -> Self {
387 Self {
388 state: AtomicU8::new(0),
389 flip: AtomicU32::new(0),
390 gpu_ns: AtomicU64::new(0),
391 gpu_n: AtomicU32::new(0),
392 declines: AtomicU32::new(0),
393 gpu_burn: AtomicU32::new(PROBE_WARMUP),
394 cpu_ns: AtomicU64::new(0),
395 cpu_n: AtomicU32::new(0),
396 gpu_min: AtomicU64::new(u64::MAX),
397 cpu_min: AtomicU64::new(u64::MAX),
398 }
399 }
400}
401
402static PROBES: [Probe; 7] = [
403 Probe::new(),
404 Probe::new(),
405 Probe::new(),
406 Probe::new(),
407 Probe::new(),
408 Probe::new(),
409 Probe::new(),
410];
411
412static TRUST_GPU: AtomicBool = AtomicBool::new(false);
419
420pub fn trust_gpu() -> GpuTrust {
422 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
423 GpuTrust(was)
424}
425
426pub struct GpuTrust(bool);
427
428impl Drop for GpuTrust {
429 fn drop(&mut self) {
430 TRUST_GPU.store(self.0, Ordering::Relaxed);
431 }
432}
433
434fn probe_on_for(c: OpClass) -> bool {
435 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
441 return false;
442 }
443 probe_on()
444}
445
446fn probe_on() -> bool {
447 static ON: OnceLock<bool> = OnceLock::new();
448 *ON.get_or_init(|| {
449 std::env::var("CMF_GPU_PROBE")
450 .map(|v| v != "0" && v != "off")
451 .unwrap_or(true)
452 })
453}
454
455pub fn q1_force() -> bool {
460 #[cfg(target_os = "macos")]
461 {
462 backend() == Backend::Metal
463 }
464 #[cfg(not(target_os = "macos"))]
465 {
466 false
467 }
468}
469
470pub fn fused_block_trusted() -> bool {
489 #[cfg(target_os = "macos")]
490 if backend() == Backend::Metal {
491 return true;
492 }
493 wgpu_graph_default()
494}
495
496pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
508 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
509 {
510 return crate::gpu_wgpu::weight_is_resident(model, idx);
511 }
512 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
513 {
514 let _ = (model, idx);
515 true
516 }
517}
518
519pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
520 if !weights_resident && probe_deciding(c) {
521 return ProbeArm::Gpu;
522 }
523 probe_arm(c)
524}
525
526pub fn probe_arm(c: OpClass) -> ProbeArm {
527 PROBE_COLD.with(|f| f.set(false));
532 if !probe_on_for(c) {
533 return ProbeArm::Gpu;
534 }
535 probe_cache_load();
536 let p = &PROBES[c as usize];
537 match p.state.load(Ordering::Relaxed) {
538 1 => ProbeArm::Gpu,
539 2 => ProbeArm::Cpu,
540 _ => {
541 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
542 ProbeArm::Gpu
543 } else {
544 ProbeArm::CpuTimed
545 }
546 }
547 }
548}
549
550pub fn probe_note_decline(c: OpClass) {
554 let p = &PROBES[c as usize];
555 if p.state.load(Ordering::Relaxed) != 0 {
556 return;
557 }
558 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
559 if n >= PROBE_DECLINE_LIMIT
560 && p.state
561 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
562 .is_ok()
563 {
564 tracing::info!(
565 "gpu probe [{}]: device declined {n} times → cpu",
566 CLASS_NAMES[c as usize]
567 );
568 }
569}
570
571pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
574 probe_record_into(&PROBES[c as usize], CLASS_NAMES[c as usize], Some(c), gpu, dur)
575}
576
577fn probe_record_into(
580 p: &Probe,
581 class_name: &str,
582 cache: Option<OpClass>,
583 gpu: bool,
584 dur: std::time::Duration,
585) {
586 if p.state.load(Ordering::Relaxed) != 0 {
587 return;
588 }
589 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
590 return; }
592 if gpu {
593 let left = p.gpu_burn.load(Ordering::Relaxed);
597 if left > 0 {
598 p.gpu_burn.store(left - 1, Ordering::Relaxed);
599 return; }
601 }
602 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
603 if gpu {
604 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
605 p.gpu_n.fetch_add(1, Ordering::Relaxed);
606 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
607 } else {
608 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
609 p.cpu_n.fetch_add(1, Ordering::Relaxed);
610 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
611 }
612 let (gn, cn) = (
613 p.gpu_n.load(Ordering::Relaxed),
614 p.cpu_n.load(Ordering::Relaxed),
615 );
616 if gn >= 2 && cn >= 2 {
617 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
621 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
622 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
632 return;
633 }
634 let winner = if g <= cp { 1 } else { 2 };
635 if p.state
636 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
637 .is_ok()
638 {
639 tracing::info!(
640 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
641 class_name,
642 g / 1e6,
643 cp / 1e6,
644 if winner == 1 { "gpu" } else { "cpu" },
645 );
646 if let Some(c) = cache {
647 probe_cache_store(c, winner);
648 }
649 }
650 }
651}
652
653pub fn probe_deciding(c: OpClass) -> bool {
656 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
657}
658
659#[allow(unused_variables)]
669pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
670 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
671 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
672 let resident = match backend() {
673 #[cfg(target_os = "macos")]
674 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
675 #[cfg(feature = "gpu")]
676 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
677 Backend::None => false,
678 };
679 if !resident && may_upload {
680 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
681 }
682 resident
683}
684
685#[cfg(test)]
687pub(crate) fn probe_reset() {
688 for p in &PROBES {
689 p.state.store(0, Ordering::Relaxed);
690 p.flip.store(0, Ordering::Relaxed);
691 p.gpu_ns.store(0, Ordering::Relaxed);
692 p.gpu_n.store(0, Ordering::Relaxed);
693 p.cpu_ns.store(0, Ordering::Relaxed);
694 p.cpu_n.store(0, Ordering::Relaxed);
695 }
696}
697
698#[cfg(test)]
699mod probe_tests {
700 use super::*;
701 use std::time::Duration;
702
703 #[test]
706 fn probe_alternates_discards_cold_and_decides() {
707 probe_reset();
708 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
710 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
711
712 probe_note_cold();
716 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
717 for _ in 0..PROBE_SAMPLES {
718 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
719 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
720 }
721 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
722
723 for _ in 0..PROBE_SAMPLES {
725 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
726 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
727 }
728 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
729
730 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
732 CPU_ONLY.with(|c| assert!(!c.get()));
733 cpu_scope(|| {
734 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
735 CPU_ONLY.with(|c| assert!(c.get()));
736 });
737 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
738 CPU_ONLY.with(|c| assert!(!c.get()));
739 probe_reset();
740 }
741
742 #[test]
743 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
744 let mine = probe_cache_key_named("gemm-nt");
756 let state = || {
757 PROBES[OpClass::GemmNt as usize]
758 .state
759 .load(Ordering::Relaxed)
760 };
761
762 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
764 assert_eq!(state(), 0);
765 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
767 assert_ne!(older, mine);
768 probe_cache_adopt(&format!("{older}\tgpu\n"));
769 assert_eq!(state(), 0);
770 probe_cache_adopt(&format!("{mine}\tcpu\n"));
772 assert_eq!(state(), 2);
773
774 PROBES[OpClass::GemmNt as usize]
775 .state
776 .store(0, Ordering::Relaxed);
777 }
778}
779
780pub const GPU_MIN_ROWS: usize = 65_536;
783
784pub fn min_rows() -> usize {
791 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
792 .ok()
793 .and_then(|v| v.parse().ok())
794 {
795 return v;
796 }
797 if discrete() { 4096 } else { GPU_MIN_ROWS }
798}
799
800pub fn discrete() -> bool {
802 match backend() {
803 #[cfg(feature = "gpu")]
804 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
805 #[cfg(target_os = "macos")]
806 Backend::Metal => false, Backend::None => false,
808 }
809}
810
811pub struct MoeJob<'a> {
815 pub gate: (usize, usize, usize, &'a [f32]),
816 pub up: (usize, usize, usize, &'a [f32]),
817 pub down: (usize, usize, usize, &'a [f32]),
818 pub xs_gate: Vec<f32>,
819 pub xs_up: Vec<f32>,
820 pub down_col: &'a [f32],
821 pub w: f32,
822 pub q1: bool,
825 pub q4t: bool,
828 pub q4tp: bool,
832 pub gu_q2: bool,
836 pub swiglu_limit: f32,
841}
842
843pub struct BatchJob<'a> {
845 pub idx: usize,
846 pub rows: usize,
847 pub cols: usize,
848 pub row_scale: &'a [f32],
849 pub xs: Vec<f32>,
850 pub layout: BatchLayout,
854}
855
856#[derive(Clone, Copy, PartialEq, Eq, Debug)]
859pub enum BatchLayout {
860 Q8,
861 Q1,
862 Q4t,
863 Q4tp,
864}
865
866#[derive(Clone, Copy, PartialEq, Eq)]
867enum Backend {
868 None,
869 #[cfg(target_os = "macos")]
870 Metal,
871 #[cfg(feature = "gpu")]
872 Wgpu,
873}
874
875fn backend() -> Backend {
876 #[cfg(feature = "gpu")]
877 if crate::gpu_wgpu::selected() {
878 return if crate::gpu_wgpu::enabled() {
879 Backend::Wgpu
880 } else {
881 Backend::None
882 };
883 }
884 #[cfg(target_os = "macos")]
885 if crate::gpu_metal::enabled() {
886 return Backend::Metal;
887 }
888 Backend::None
889}
890
891pub fn backend_available() -> bool {
897 #[cfg(target_os = "macos")]
898 {
899 true
901 }
902 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
903 {
904 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
905 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
906 }
907 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
908 {
909 false
910 }
911}
912
913static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
919
920pub fn pause_gpu() -> GpuPause {
922 GPU_PAUSED.store(true, Ordering::Relaxed);
923 GpuPause(())
924}
925
926pub struct GpuPause(());
927
928impl Drop for GpuPause {
929 fn drop(&mut self) {
930 GPU_PAUSED.store(false, Ordering::Relaxed);
931 }
932}
933
934pub fn enabled() -> bool {
935 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
936}
937
938pub fn wgpu_active() -> bool {
952 #[cfg(feature = "gpu")]
953 {
954 matches!(backend(), Backend::Wgpu)
955 }
956 #[cfg(not(feature = "gpu"))]
957 {
958 false
959 }
960}
961
962pub fn default_device() -> usize {
969 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
970 *D.get_or_init(|| {
971 std::env::var("CMF_GPU_ADAPTER")
972 .ok()
973 .and_then(|v| v.trim().parse::<usize>().ok())
974 .unwrap_or(0)
975 })
976}
977
978thread_local! {
979 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
980}
981
982pub fn current_device() -> usize {
984 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
985}
986
987pub fn set_current_device(i: usize) {
991 CUR_DEV.with(|c| c.set(Some(i)));
992}
993
994pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
996 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
997 let r = f();
998 CUR_DEV.with(|c| c.set(prev));
999 r
1000}
1001
1002pub fn device_count() -> usize {
1005 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1006 {
1007 return crate::gpu_wgpu::adapter_count();
1008 }
1009 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1010 {
1011 usize::from(backend_available())
1012 }
1013}
1014
1015pub fn vram_budget() -> u64 {
1019 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1020 {
1021 return crate::gpu_wgpu::device_vram_budget();
1022 }
1023 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1024 {
1025 if backend_available() { u64::MAX } else { 0 }
1026 }
1027}
1028
1029pub fn upload_bytes() -> u64 {
1033 #[cfg(feature = "gpu")]
1034 {
1035 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1036 }
1037 #[cfg(not(feature = "gpu"))]
1038 0
1039}
1040
1041#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1053pub enum GraphPhase {
1054 Prefill,
1055 Decode,
1056}
1057
1058pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1066 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1067 Some("0") => false,
1068 Some("prefill") => phase == GraphPhase::Prefill,
1069 Some(_) => true,
1070 None => {
1071 if wgpu_graph_default() {
1072 return true;
1073 }
1074 let _ = phase;
1079 false
1080 }
1081 }
1082}
1083
1084pub fn wgpu_graph_default() -> bool {
1085 #[cfg(feature = "gpu")]
1086 {
1087 matches!(backend(), Backend::Wgpu)
1093 && (crate::gpu_wgpu::discrete_active()
1094 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1095 }
1096 #[cfg(not(feature = "gpu"))]
1097 {
1098 false
1099 }
1100}
1101
1102#[allow(clippy::too_many_arguments, unused_variables)]
1104pub fn q8_matvec_range(
1105 model: &Arc<CmfModel>,
1106 idx: usize,
1107 row0: usize,
1108 row_scale: &[f32],
1109 xs: &[f32],
1110 rows: usize,
1111 cols: usize,
1112 out: &mut [f32],
1113) -> bool {
1114 match backend() {
1115 #[cfg(target_os = "macos")]
1116 Backend::Metal => {
1117 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1118 }
1119 #[cfg(feature = "gpu")]
1120 Backend::Wgpu => {
1121 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1122 }
1123 Backend::None => false,
1124 }
1125}
1126
1127#[allow(clippy::too_many_arguments, unused_variables)]
1130#[allow(clippy::too_many_arguments)]
1134pub fn q8_matmat_2f(
1135 model: &Arc<CmfModel>,
1136 idx: usize,
1137 row_scale: &[f32],
1138 col_field: &[f32],
1139 xs: &[f32],
1140 b: usize,
1141 rows: usize,
1142 cols: usize,
1143 out: &mut [f32],
1144) -> bool {
1145 #[allow(unreachable_patterns)]
1146 match backend() {
1147 #[cfg(feature = "gpu")]
1148 Backend::Wgpu => crate::gpu_wgpu::q8_matmat_2f(
1149 model, idx, row_scale, col_field, xs, b, rows, cols, out,
1150 ),
1151 _ => false,
1152 }
1153}
1154
1155pub fn q8_matmat(
1156 model: &Arc<CmfModel>,
1157 idx: usize,
1158 row_scale: &[f32],
1159 pre: &[f32],
1160 b: usize,
1161 rows: usize,
1162 cols: usize,
1163 out: &mut [f32],
1164) -> bool {
1165 match backend() {
1166 #[cfg(target_os = "macos")]
1167 Backend::Metal => {
1168 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1169 }
1170 #[cfg(feature = "gpu")]
1171 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1172 Backend::None => false,
1173 }
1174}
1175
1176#[allow(unused_variables)]
1179pub fn q1_matvec(
1180 model: &Arc<CmfModel>,
1181 idx: usize,
1182 xs: &[f32],
1183 rows: usize,
1184 cols: usize,
1185 out: &mut [f32],
1186) -> bool {
1187 match backend() {
1188 #[cfg(target_os = "macos")]
1189 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1190 #[cfg(feature = "gpu")]
1191 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1192 Backend::None => false,
1193 }
1194}
1195
1196#[allow(clippy::too_many_arguments)]
1200pub fn attn_dropin(
1201 model: &Arc<CmfModel>,
1202 kv_id: u64,
1203 layer: usize,
1204 normed: &[f32],
1205 wq_idx: usize,
1206 wk_idx: usize,
1207 wv_idx: usize,
1208 wo_idx: usize,
1209 q_norm: Option<&[f32]>,
1210 k_norm: Option<&[f32]>,
1211 invf: &[f32],
1212 nh: usize,
1213 nkv: usize,
1214 hd: usize,
1215 rd: usize,
1216 hidden: usize,
1217 pos: usize,
1218 cap: usize,
1219 gemma: bool,
1220 eps: f32,
1221 cpu_k: &[Vec<f32>],
1222 cpu_v: &[Vec<f32>],
1223 out: &mut [f32],
1224) -> bool {
1225 match backend() {
1226 #[cfg(feature = "gpu")]
1227 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1228 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1229 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1230 ),
1231 #[allow(unused_variables)]
1232 _ => false,
1233 }
1234}
1235
1236pub struct GraphW<'a> {
1240 pub idx: usize,
1241 pub kind: u8,
1242 pub row_scale: &'a [f32],
1243 pub data: &'a [f32],
1244}
1245
1246pub enum GraphAttn<'a> {
1249 Full {
1250 wq: GraphW<'a>,
1251 wk: GraphW<'a>,
1252 wv: GraphW<'a>,
1253 wo: GraphW<'a>,
1254 q_norm: Option<&'a [f32]>,
1255 k_norm: Option<&'a [f32]>,
1256 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1258 output_gate: bool,
1261 cpu_k: &'a [Vec<f32>],
1262 cpu_v: &'a [Vec<f32>],
1263 },
1264 Gdn {
1265 qkv: GraphW<'a>,
1266 z: GraphW<'a>,
1267 a: GraphW<'a>,
1268 b: GraphW<'a>,
1269 out: GraphW<'a>,
1270 conv1d: &'a [f32],
1271 a_log: &'a [f32],
1272 dt_bias: &'a [f32],
1273 norm: &'a [f32],
1274 nv: usize,
1275 nk: usize,
1276 dk: usize,
1277 dv: usize,
1278 kk: usize,
1279 cpu_state: &'a [f32],
1284 },
1285 ShortConv {
1292 inp: GraphW<'a>,
1294 out: GraphW<'a>,
1296 taps: &'a [f32],
1299 kernel: usize,
1300 cpu_state: &'a [f32],
1304 },
1305}
1306
1307pub struct GraphLayer<'a> {
1309 pub input_norm: &'a [f32],
1310 pub attn: GraphAttn<'a>,
1311 pub post_norm: &'a [f32],
1312 pub ffn: GraphFfn<'a>,
1313}
1314
1315pub enum GraphFfn<'a> {
1320 Dense {
1321 gate: GraphW<'a>,
1322 up: GraphW<'a>,
1323 down: GraphW<'a>,
1324 },
1325 Moe {
1326 router: GraphW<'a>,
1328 shared_gate: GraphW<'a>,
1330 experts: Vec<(usize, usize, usize)>,
1334 n_exp: usize,
1336 top_k: usize,
1337 inter: usize,
1338 norm_topk: bool,
1339 q4tp: bool,
1345 gu_q2: bool,
1349 },
1350}
1351
1352#[allow(clippy::too_many_arguments)]
1357pub fn forward_token_graph(
1358 model: &Arc<CmfModel>,
1359 kv_id: u64,
1360 layers: &[GraphLayer],
1361 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1364 o1_epoch: u64,
1365 invf: &[f32],
1366 h: &mut [f32],
1367 nh: usize,
1368 nkv: usize,
1369 hd: usize,
1370 rd: usize,
1371 hidden: usize,
1372 inter: usize,
1373 position: usize,
1374 cap: usize,
1375 gemma: bool,
1376 eps: f32,
1377 lm_head: Option<(&GraphW, usize)>,
1378 final_norm: &[f32],
1379 logits: &mut Vec<f32>,
1380 loop_norm_at: &[usize],
1381 steps: usize,
1382 embed: Option<(&GraphW, usize, f32)>,
1383 ids_out: Option<&mut Vec<u32>>,
1384 layers_run: Option<&mut usize>,
1387 layer_base: usize,
1391 hidden_too: bool,
1393) -> bool {
1394 match backend() {
1395 #[cfg(feature = "gpu")]
1396 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1397 model,
1398 kv_id,
1399 layers,
1400 o1,
1401 o1_epoch,
1402 invf,
1403 h,
1404 nh,
1405 nkv,
1406 hd,
1407 rd,
1408 hidden,
1409 inter,
1410 position,
1411 cap,
1412 gemma,
1413 eps,
1414 lm_head,
1415 final_norm,
1416 logits,
1417 loop_norm_at,
1418 steps,
1419 embed,
1420 ids_out,
1421 layers_run,
1422 layer_base,
1423 hidden_too,
1424 ),
1425 #[allow(unused_variables)]
1426 _ => {
1427 let _ = (
1428 lm_head,
1429 final_norm,
1430 logits,
1431 loop_norm_at,
1432 layers_run,
1433 layer_base,
1434 hidden_too,
1435 );
1436 false
1437 }
1438 }
1439}
1440
1441pub struct SpecTail<'a> {
1445 pub lm: GraphW<'a>,
1446 pub lm_rows: usize,
1447 pub final_norm: &'a [f32],
1448 pub logits_out: &'a mut Vec<f32>,
1449}
1450
1451#[allow(clippy::too_many_arguments)]
1455pub fn forward_batch_graph(
1456 model: &Arc<CmfModel>,
1457 kv_id: u64,
1458 layers: &[GraphLayer],
1459 invf: &[f32],
1460 h: &mut [f32],
1461 nh: usize,
1462 nkv: usize,
1463 hd: usize,
1464 rd: usize,
1465 hidden: usize,
1466 inter: usize,
1467 positions: &[usize],
1468 cap: usize,
1469 gemma: bool,
1470 eps: f32,
1471 k: usize,
1472 spec: Option<SpecTail<'_>>,
1473) -> bool {
1474 match backend() {
1475 #[cfg(feature = "gpu")]
1476 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1477 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1478 eps, k, spec,
1479 ),
1480 #[allow(unreachable_patterns)]
1481 _ => {
1482 let _ = spec;
1483 false
1484 }
1485 }
1486}
1487
1488pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1491 #[cfg(feature = "gpu")]
1492 if backend() == Backend::Wgpu {
1493 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1494 }
1495 #[allow(unreachable_code)]
1496 {
1497 let _ = (kv_id, slot);
1498 false
1499 }
1500}
1501
1502pub fn graph_kv_reset(_kv_id: u64) {
1504 #[cfg(feature = "gpu")]
1505 if backend() == Backend::Wgpu {
1506 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1507 }
1508}
1509
1510pub fn q1t_matvec(
1514 model: &Arc<CmfModel>,
1515 idx: usize,
1516 xs: &[f32],
1517 rows: usize,
1518 cols: usize,
1519 out: &mut [f32],
1520) -> bool {
1521 match backend() {
1522 #[cfg(target_os = "macos")]
1523 Backend::Metal => {
1524 if metal_q1t_enabled() {
1525 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1526 } else {
1527 false
1528 }
1529 }
1530 #[cfg(feature = "gpu")]
1531 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1532 Backend::None => false,
1533 }
1534}
1535
1536#[allow(unused_variables)]
1539pub fn q4b_matvec(
1540 model: &Arc<CmfModel>,
1541 idx: usize,
1542 xs: &[f32],
1543 rows: usize,
1544 cols: usize,
1545 out: &mut [f32],
1546) -> bool {
1547 match backend() {
1548 #[cfg(target_os = "macos")]
1549 Backend::Metal => false,
1550 #[cfg(feature = "gpu")]
1551 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1552 Backend::None => false,
1553 }
1554}
1555
1556pub fn q1t_matmat(
1559 model: &Arc<CmfModel>,
1560 idx: usize,
1561 xs: &[f32],
1562 b: usize,
1563 rows: usize,
1564 cols: usize,
1565 out: &mut [f32],
1566) -> bool {
1567 match backend() {
1568 #[cfg(target_os = "macos")]
1569 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1573 #[cfg(feature = "gpu")]
1574 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1575 Backend::None => false,
1576 }
1577}
1578
1579#[cfg(target_os = "macos")]
1583pub(crate) fn metal_q1t_enabled() -> bool {
1584 std::env::var("CMF_METAL_Q1T")
1585 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1586 .unwrap_or(true)
1587}
1588
1589pub fn q1_matmat(
1591 model: &Arc<CmfModel>,
1592 idx: usize,
1593 xs: &[f32],
1594 b: usize,
1595 rows: usize,
1596 cols: usize,
1597 out: &mut [f32],
1598) -> bool {
1599 match backend() {
1600 #[cfg(feature = "gpu")]
1601 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1602 #[allow(unused_variables)]
1603 _ => false,
1604 }
1605}
1606
1607static MM_KILL: AtomicBool = AtomicBool::new(false);
1612pub(crate) fn mm_killed() -> bool {
1613 MM_KILL.load(Ordering::Relaxed)
1614}
1615pub(crate) fn mm_kill() {
1616 MM_KILL.store(true, Ordering::Relaxed);
1617}
1618
1619static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1626const MM_STRIKES_TO_KILL: u32 = 3;
1627static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1634
1635pub fn mm_kill_arm(on: bool) {
1638 MM_ARMED.store(on, Ordering::Relaxed);
1639 if on {
1640 MM_STRIKES.store(0, Ordering::Relaxed);
1641 }
1642}
1643
1644pub(crate) fn mm_budget_check(
1651 what: &str,
1652 el: std::time::Duration,
1653 budget: std::time::Duration,
1654 exempt: bool,
1655) {
1656 if el <= budget {
1657 MM_STRIKES.store(0, Ordering::Relaxed);
1658 return;
1659 }
1660 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1661 return;
1662 }
1663 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1664 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1665 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1666 if !on {
1667 tracing::info!(
1668 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1669 );
1670 return;
1671 }
1672 if n >= MM_STRIKES_TO_KILL {
1673 tracing::warn!(
1674 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1675 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1676 );
1677 mm_kill();
1678 } else {
1679 tracing::info!(
1680 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1681 );
1682 }
1683}
1684
1685#[allow(unused_variables, clippy::too_many_arguments)]
1690pub fn chunk_attend(
1691 q: &[f32],
1692 k: &[&[f32]],
1693 v: &[&[f32]],
1694 b: usize,
1695 s0: usize,
1696 nh: usize,
1697 nkv: usize,
1698 hd: usize,
1699 scale: f32,
1700 out: &mut [f32],
1701) -> bool {
1702 match backend() {
1703 #[cfg(feature = "gpu")]
1704 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1705 #[allow(unreachable_patterns)]
1706 _ => false,
1707 }
1708}
1709
1710#[allow(unused_variables, clippy::too_many_arguments)]
1714pub fn q4t_qkv(
1715 model: &Arc<CmfModel>,
1716 wq: usize,
1717 wk: usize,
1718 wv: usize,
1719 xs: &[f32],
1720 b: usize,
1721 cols: usize,
1722 rq: usize,
1723 rk: usize,
1724 rv: usize,
1725 out: &mut [f32],
1726) -> bool {
1727 match backend() {
1728 #[cfg(feature = "gpu")]
1729 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1730 #[allow(unreachable_patterns)]
1731 _ => false,
1732 }
1733}
1734
1735#[allow(unused_variables, clippy::too_many_arguments)]
1737#[allow(clippy::too_many_arguments, unused_variables)]
1741pub fn q4tp_ffn_packed(
1742 model: &Arc<CmfModel>,
1743 w1: usize,
1744 w2: usize,
1745 xs: &[f32],
1746 b: usize,
1747 hidden: usize,
1748 inter: usize,
1749 bias: Option<&[f32]>,
1750 out: &mut [f32],
1751) -> bool {
1752 match backend() {
1753 #[cfg(feature = "gpu")]
1754 Backend::Wgpu => {
1755 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1756 }
1757 #[allow(unreachable_patterns)]
1758 _ => false,
1759 }
1760}
1761
1762pub fn q4tp_ffn(
1763 model: &Arc<CmfModel>,
1764 w1: usize,
1765 w3: usize,
1766 w2: usize,
1767 xs: &[f32],
1768 b: usize,
1769 hidden: usize,
1770 inter: usize,
1771 out: &mut [f32],
1772) -> bool {
1773 match backend() {
1774 #[cfg(target_os = "macos")]
1775 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1776 #[cfg(feature = "gpu")]
1777 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1778 #[allow(unreachable_patterns)]
1779 _ => false,
1780 }
1781}
1782
1783pub fn q4t_ffn(
1784 model: &Arc<CmfModel>,
1785 w1: usize,
1786 w3: usize,
1787 w2: usize,
1788 xs: &[f32],
1789 b: usize,
1790 hidden: usize,
1791 inter: usize,
1792 out: &mut [f32],
1793) -> bool {
1794 match backend() {
1795 #[cfg(target_os = "macos")]
1796 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1797 #[cfg(feature = "gpu")]
1798 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1799 #[allow(unreachable_patterns)]
1800 _ => false,
1801 }
1802}
1803
1804pub struct DitBlockArgs<'a> {
1809 pub n: usize,
1810 pub hidden: usize,
1811 pub inter: usize,
1812 pub nh: usize,
1813 pub nkv: usize,
1814 pub hd: usize,
1815 pub eps: f32,
1816 pub rope_cos: &'a [f32],
1817 pub rope_sin: &'a [f32],
1818 pub norm1: &'a [f32],
1819 pub norm2: &'a [f32],
1820 pub ffn_norm1: &'a [f32],
1821 pub ffn_norm2: &'a [f32],
1822 pub norm_q: &'a [f32],
1823 pub norm_k: &'a [f32],
1824 pub s_msa: &'a [f32],
1825 pub gate_msa: &'a [f32],
1826 pub s_mlp: &'a [f32],
1827 pub gate_mlp: &'a [f32],
1828 pub wq: usize,
1829 pub wk: usize,
1830 pub wv: usize,
1831 pub wo: usize,
1832 pub w1: usize,
1833 pub w3: usize,
1834 pub w2: usize,
1835 pub q4tp: bool,
1839 pub resident_in: bool,
1842 pub resident_out: bool,
1846}
1847
1848pub fn dit_chain_supported() -> bool {
1852 #[cfg(feature = "gpu")]
1853 {
1854 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1855 }
1856 #[allow(unreachable_code)]
1857 false
1858}
1859
1860pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1863 #[cfg(feature = "gpu")]
1864 {
1865 if matches!(backend(), Backend::Wgpu) {
1866 return crate::gpu_wgpu::dit_state_fetch(_x);
1867 }
1868 }
1869 false
1870}
1871
1872#[allow(unused_variables)]
1876#[allow(unused_variables, clippy::too_many_arguments)]
1880pub fn dit_qkv(
1881 model: &Arc<CmfModel>,
1882 wq: usize,
1883 wk: usize,
1884 wv: usize,
1885 xs: &[f32],
1886 b: usize,
1887 hidden: usize,
1888 qrows: usize,
1889 kvrows: usize,
1890 q_out: &mut [f32],
1891 k_out: &mut [f32],
1892 v_out: &mut [f32],
1893) -> bool {
1894 match backend() {
1895 #[cfg(feature = "gpu")]
1896 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1897 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1898 ),
1899 #[allow(unreachable_patterns)]
1900 _ => false,
1901 }
1902}
1903
1904pub fn fused_dit_block_available() -> bool {
1908 #[cfg(target_os = "macos")]
1909 {
1910 matches!(backend(), Backend::Metal) && fused_block_trusted()
1911 }
1912 #[cfg(not(target_os = "macos"))]
1913 {
1914 false
1915 }
1916}
1917
1918pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1919 dit_block_seg(model, a, &[a.n], x)
1920}
1921
1922pub fn dit_block_seg(
1926 model: &Arc<CmfModel>,
1927 a: &DitBlockArgs,
1928 segs: &[usize],
1929 x: &mut [f32],
1930) -> bool {
1931 match backend() {
1932 #[cfg(target_os = "macos")]
1933 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1934 #[cfg(feature = "gpu")]
1941 Backend::Wgpu
1942 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1943 Some("0") => false,
1944 Some(_) => true,
1945 None => crate::gpu_wgpu::discrete_active(),
1946 } =>
1947 {
1948 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1949 }
1950 #[allow(unreachable_patterns)]
1951 _ => false,
1952 }
1953}
1954
1955pub struct VaeResnetArgs<'a> {
1959 pub groups: usize,
1960 pub ic: usize,
1961 pub oc: usize,
1962 pub h: usize,
1963 pub w: usize,
1964 pub n1w: &'a [f32],
1965 pub n1b: &'a [f32],
1966 pub c1w: &'a [f32],
1967 pub c1b: &'a [f32],
1968 pub c1k: usize,
1969 pub n2w: &'a [f32],
1970 pub n2b: &'a [f32],
1971 pub c2w: &'a [f32],
1972 pub c2b: &'a [f32],
1973 pub c2k: usize,
1974 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1975}
1976
1977#[allow(unused_variables)]
1980pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1981 match backend() {
1982 #[cfg(target_os = "macos")]
1983 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1984 _ => false,
1985 }
1986}
1987
1988#[allow(unused_variables, clippy::too_many_arguments)]
1991pub fn vae_upsample_conv(
1992 w: &[f32],
1993 bias: &[f32],
1994 x: &[f32],
1995 ic: usize,
1996 oc: usize,
1997 h: usize,
1998 w_img: usize,
1999 k: usize,
2000 out: &mut [f32],
2001) -> bool {
2002 match backend() {
2003 #[cfg(target_os = "macos")]
2004 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2005 #[cfg(feature = "gpu")]
2006 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2007 #[allow(unreachable_patterns)]
2008 _ => false,
2009 }
2010}
2011
2012#[allow(unused_variables, clippy::too_many_arguments)]
2015pub fn vae_conv2d(
2016 w: &[f32],
2017 bias: &[f32],
2018 x: &[f32],
2019 ic: usize,
2020 oc: usize,
2021 h: usize,
2022 w_img: usize,
2023 k: usize,
2024 out: &mut [f32],
2025) -> bool {
2026 match backend() {
2027 #[cfg(target_os = "macos")]
2028 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2029 #[cfg(feature = "gpu")]
2030 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2031 #[allow(unreachable_patterns)]
2032 _ => false,
2033 }
2034}
2035
2036#[allow(unused_variables, clippy::too_many_arguments)]
2040#[allow(unused_variables)]
2044#[allow(clippy::too_many_arguments)]
2045#[allow(clippy::too_many_arguments, unused_variables)]
2048pub fn dit_qkv_attention(
2049 model: &Arc<CmfModel>,
2050 qkv_idx: usize,
2051 xn: &[f32],
2052 n: usize,
2053 hidden: usize,
2054 nh: usize,
2055 hd: usize,
2056 scale: f32,
2057 nr: (&[f32], &[f32], &[f32], f32),
2058 out: &mut [f32],
2059) -> bool {
2060 match backend() {
2061 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2062 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2063 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2064 ),
2065 #[allow(unreachable_patterns)]
2066 _ => false,
2067 }
2068}
2069
2070#[allow(clippy::too_many_arguments)]
2073pub fn dit_qkv_attn_out(
2074 model: &Arc<CmfModel>,
2075 qkv_idx: usize,
2076 out_idx: usize,
2077 xn: &[f32],
2078 n: usize,
2079 hidden: usize,
2080 nh: usize,
2081 hd: usize,
2082 scale: f32,
2083 nr: (&[f32], &[f32], &[f32], f32),
2084 proj: &mut [f32],
2085) -> bool {
2086 match backend() {
2087 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2088 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2089 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2090 ),
2091 #[allow(unreachable_patterns)]
2092 _ => false,
2093 }
2094}
2095
2096#[allow(clippy::too_many_arguments)]
2098pub fn vae_qkv_attn_out(
2099 model: &Arc<CmfModel>,
2100 qkv_idx: usize,
2101 out_idx: usize,
2102 xn: &[f32],
2103 n: usize,
2104 dim: usize,
2105 nh: usize,
2106 hd: usize,
2107 scale: f32,
2108 angles: &[f32],
2109 eps: f32,
2110 qkv_bias: &[f32],
2111 proj: &mut [f32],
2112) -> bool {
2113 match backend() {
2114 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2115 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2116 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2117 ),
2118 #[allow(unreachable_patterns)]
2119 _ => false,
2120 }
2121}
2122
2123#[allow(clippy::too_many_arguments)]
2124pub fn vae_attention_packed(
2125 qkv: &[f32],
2126 nh: usize,
2127 n: usize,
2128 hd: usize,
2129 scale: f32,
2130 angles: &[f32],
2131 eps: f32,
2132 out: &mut [f32],
2133) -> bool {
2134 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2135}
2136
2137#[allow(clippy::too_many_arguments)]
2138pub fn vae_attention_packed_layout(
2139 qkv: &[f32],
2140 nh: usize,
2141 n: usize,
2142 hd: usize,
2143 scale: f32,
2144 angles: &[f32],
2145 eps: f32,
2146 out: &mut [f32],
2147 layout: u32,
2148) -> bool {
2149 match backend() {
2150 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2151 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2152 qkv, nh, n, hd, scale, angles, eps, out, layout,
2153 ),
2154 #[allow(unreachable_patterns)]
2155 _ => false,
2156 }
2157}
2158
2159#[allow(clippy::too_many_arguments)]
2160pub fn dit_split_only(
2161 qkv: &[f32],
2162 nh: usize,
2163 n: usize,
2164 hd: usize,
2165 layout: u32,
2166 norm: Option<(&[f32], f32)>,
2167 out_q: &mut [f32],
2168) -> bool {
2169 match backend() {
2170 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2171 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2172 #[allow(unreachable_patterns)]
2173 _ => false,
2174 }
2175}
2176
2177pub fn gemm_nt_f32_transient(
2185 x: &[f32],
2186 w: &[f32],
2187 y: &mut [f32],
2188 n: usize,
2189 k: usize,
2190 m: usize,
2191) -> bool {
2192 match backend() {
2193 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2194 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2195 #[allow(unreachable_patterns)]
2196 _ => false,
2197 }
2198}
2199
2200pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2201 match backend() {
2202 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2203 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2204 #[allow(unreachable_patterns)]
2205 _ => false,
2206 }
2207}
2208
2209#[allow(clippy::too_many_arguments)]
2212pub fn music3_ffn(
2213 model: &std::sync::Arc<CmfModel>,
2214 idx_in: usize,
2215 idx_out: usize,
2216 h: &[f32],
2217 bias_in: &[f32],
2218 n: usize,
2219 hs: usize,
2220 inter: usize,
2221 out: &mut [f32],
2222) -> bool {
2223 match backend() {
2224 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2225 Backend::Wgpu => {
2226 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2227 }
2228 #[allow(unreachable_patterns)]
2229 _ => false,
2230 }
2231}
2232
2233#[allow(clippy::too_many_arguments)]
2237pub fn conv1d_gemm(
2238 x: &[f32],
2239 w: &[f32],
2240 ic: usize,
2241 oc: usize,
2242 n: usize,
2243 k: usize,
2244 pad: usize,
2245 dil: usize,
2246 out_n: usize,
2247 yt: &mut [f32],
2248) -> bool {
2249 match backend() {
2250 #[cfg(target_os = "macos")]
2251 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2252 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2253 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2254 #[allow(unreachable_patterns)]
2255 _ => false,
2256 }
2257}
2258
2259#[allow(clippy::too_many_arguments)]
2261pub fn vae_conv2d_coop(
2262 w: &[f32],
2263 bias: Option<&[f32]>,
2264 x: &[f32],
2265 ic: usize,
2266 oc: usize,
2267 h: usize,
2268 wi: usize,
2269 k: usize,
2270 out: &mut [f32],
2271) -> bool {
2272 match backend() {
2273 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2274 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2275 #[allow(unreachable_patterns)]
2276 _ => false,
2277 }
2278}
2279
2280pub fn dit_attention_packed(
2281 qkv: &[f32],
2282 nh: usize,
2283 n: usize,
2284 hd: usize,
2285 scale: f32,
2286 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2289 out: &mut [f32],
2290) -> bool {
2291 match backend() {
2292 #[cfg(feature = "gpu")]
2299 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2300 #[allow(unreachable_patterns)]
2301 _ => false,
2302 }
2303}
2304
2305pub fn dit_attention_packed_available() -> bool {
2313 #[allow(unreachable_patterns)]
2314 match backend() {
2315 #[cfg(feature = "gpu")]
2316 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2317 _ => false,
2318 }
2319}
2320
2321pub fn dit_attention(
2322 qh: &[f32],
2323 kh: &[f32],
2324 vh: &[f32],
2325 nh: usize,
2326 nkv: usize,
2327 n: usize,
2328 hd: usize,
2329 scale: f32,
2330 out: &mut [f32],
2331) -> bool {
2332 match backend() {
2333 #[cfg(target_os = "macos")]
2334 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2335 #[cfg(feature = "gpu")]
2336 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2337 #[allow(unreachable_patterns)]
2338 _ => false,
2339 }
2340}
2341
2342#[allow(unused_variables)]
2347pub fn q4tp_matmat(
2348 model: &Arc<CmfModel>,
2349 idx: usize,
2350 xs: &[f32],
2351 b: usize,
2352 rows: usize,
2353 cols: usize,
2354 out: &mut [f32],
2355) -> bool {
2356 match backend() {
2357 #[cfg(target_os = "macos")]
2358 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2359 #[cfg(feature = "gpu")]
2360 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2361 #[allow(unreachable_patterns)]
2362 _ => false,
2363 }
2364}
2365
2366pub fn q2tp_matmat(
2369 model: &Arc<CmfModel>,
2370 idx: usize,
2371 xs: &[f32],
2372 b: usize,
2373 rows: usize,
2374 cols: usize,
2375 out: &mut [f32],
2376) -> bool {
2377 match backend() {
2378 #[cfg(feature = "gpu")]
2379 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2380 #[allow(unreachable_patterns)]
2381 _ => false,
2382 }
2383}
2384
2385pub fn q4tp_matvec(
2390 model: &Arc<CmfModel>,
2391 idx: usize,
2392 xs: &[f32],
2393 rows: usize,
2394 cols: usize,
2395 out: &mut [f32],
2396) -> bool {
2397 match backend() {
2398 #[cfg(target_os = "macos")]
2399 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2400 #[cfg(feature = "gpu")]
2401 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2402 #[allow(unreachable_patterns)]
2403 _ => false,
2404 }
2405}
2406
2407pub fn q4t_matvec(
2413 model: &Arc<CmfModel>,
2414 idx: usize,
2415 xs: &[f32],
2416 rows: usize,
2417 cols: usize,
2418 out: &mut [f32],
2419) -> bool {
2420 match backend() {
2421 #[cfg(target_os = "macos")]
2422 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2423 #[allow(unreachable_patterns)]
2424 _ => false,
2425 }
2426}
2427
2428pub fn q4t_matmat(
2429 model: &Arc<CmfModel>,
2430 idx: usize,
2431 xs: &[f32],
2432 b: usize,
2433 rows: usize,
2434 cols: usize,
2435 out: &mut [f32],
2436) -> bool {
2437 match backend() {
2438 #[cfg(target_os = "macos")]
2439 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2440 #[cfg(feature = "gpu")]
2441 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2442 #[allow(unreachable_patterns)]
2443 _ => false,
2444 }
2445}
2446
2447#[cfg(target_os = "macos")]
2449pub use crate::gpu_metal::{
2450 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2451 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2452};
2453
2454#[cfg(target_os = "macos")]
2456pub fn gdn_block(
2457 model: &Arc<CmfModel>,
2458 layers: &[GdnGpuLayer],
2459 states: &mut [&mut [f32]],
2460 cfg: &GdnGpuCfg,
2461 h: &mut [f32],
2462) -> bool {
2463 match backend() {
2464 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2465 _ => false,
2466 }
2467}
2468
2469#[allow(unused_variables)]
2471pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2472 match backend() {
2473 #[cfg(target_os = "macos")]
2474 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2475 #[cfg(feature = "gpu")]
2476 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2477 Backend::None => false,
2478 }
2479}
2480
2481#[allow(unused_variables)]
2483pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2484 match backend() {
2485 #[cfg(target_os = "macos")]
2486 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2487 #[cfg(feature = "gpu")]
2488 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2489 Backend::None => false,
2490 }
2491}
2492
2493static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2509static 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)];
2513
2514const GRAPH_RACE_SAMPLES: u32 = 4;
2516
2517static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2527
2528pub fn graph_mark_unsupported() {
2533 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2534 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2535 }
2536}
2537
2538pub fn graph_unsupported() -> bool {
2539 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2540}
2541
2542pub fn graph_unsupported_reset() {
2544 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2545}
2546
2547pub fn graph_race_begin_generation() {
2548 #[cfg(feature = "gpu")]
2553 {
2554 static FLUSHED: std::sync::Once = std::sync::Once::new();
2566 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2567 if FIRST.swap(false, Ordering::Relaxed) {
2568 } else {
2570 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2571 }
2572 }
2573 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2574 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2575 return;
2576 }
2577 let (gn, cn) = (
2578 GRAPH_N[1].load(Ordering::Relaxed),
2579 GRAPH_N[0].load(Ordering::Relaxed),
2580 );
2581 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2582 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2583 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2584 let verdict = if g_avg < c_avg { 1 } else { 2 };
2585 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2586 tracing::info!(
2587 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2588 g_avg as f64 / 1e6,
2589 c_avg as f64 / 1e6,
2590 if verdict == 1 { "graph" } else { "normal path" }
2591 );
2592 return;
2593 }
2594 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2595 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2596}
2597
2598pub fn graph_race_use_graph(trusted: bool) -> bool {
2602 if trusted {
2603 return true;
2604 }
2605 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2606 1 => true,
2607 2 => false,
2608 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2609 }
2610}
2611
2612pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2617 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2618 return false;
2619 }
2620 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2621 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2622 if !first || cn == 0 {
2623 return false;
2624 }
2625 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2626 let ns = dur.as_nanos() as u64;
2627 if ns > 1_000_000_000 && ns > 4 * c_avg {
2628 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2629 tracing::info!(
2630 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2631 ns as f64 / 1e6,
2632 c_avg as f64 / 1e6
2633 );
2634 return true;
2635 }
2636 false
2637}
2638
2639pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2643 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2644 return;
2645 }
2646 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2647 if tok == 0 {
2648 return;
2649 }
2650 let i = used_graph as usize;
2651 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2652 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2653}
2654
2655pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2665 #[inline]
2666 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2667 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2668 for c in chunks.chunks_exact(8) {
2669 h ^= u64::from_le_bytes(c.try_into().unwrap());
2670 h = h.wrapping_mul(0x100_0000_01b3);
2671 }
2672 for &b in tail {
2673 h ^= b as u64;
2674 h = h.wrapping_mul(0x100_0000_01b3);
2675 }
2676 h
2677 }
2678 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2679 if data.len() <= 4096 {
2680 return fnv(h, data);
2681 }
2682 let step = (data.len() - 64) / 63;
2683 for i in 0..64 {
2684 h = fnv(h, &data[i * step..i * step + 64]);
2685 }
2686 h
2687}
2688
2689pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2692 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2693 fp_bytes(bytes)
2694}
2695
2696#[cfg(test)]
2697mod fp_tests {
2698 use super::fp_bytes;
2699
2700 #[test]
2705 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2706 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2708 let h0 = fp_bytes(&base);
2709 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2710 let mut dense = base.clone();
2713 for b in dense.iter_mut() {
2714 *b = b.wrapping_add(1);
2715 }
2716 assert_ne!(
2717 h0,
2718 fp_bytes(&dense),
2719 "a fully different tensor slipped through"
2720 );
2721 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2724 let mut small = vec![3u8; 4096];
2727 let hs = fp_bytes(&small);
2728 small[2048] ^= 1;
2729 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2730 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2732 let v = vec![9u8; n];
2733 let _ = fp_bytes(&v); }
2735 }
2736}
2737
2738pub fn bake_release() {
2742 #[cfg(feature = "gpu")]
2743 crate::gpu_wgpu::bake_release();
2744}
2745
2746pub fn bake_precision_strict(on: bool) {
2750 #[cfg(feature = "gpu")]
2751 crate::gpu_wgpu::bake_precision_strict(on);
2752 #[cfg(not(feature = "gpu"))]
2753 let _ = on;
2754}
2755
2756pub fn hostprof_encode_done(t0: std::time::Instant) {
2762 use std::sync::atomic::{AtomicU64, Ordering};
2763 static ENC: AtomicU64 = AtomicU64::new(0);
2764 static N: AtomicU64 = AtomicU64::new(0);
2765 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2766 return;
2767 }
2768 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2769 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2770 if n % 100 == 0 {
2771 eprintln!(
2772 "hostprof: encode {:.2} ms/token over {n} tokens",
2773 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2774 );
2775 }
2776}
2777
2778pub fn hostprof_total(t0: std::time::Instant) {
2779 use std::sync::atomic::{AtomicU64, Ordering};
2780 static TOT: AtomicU64 = AtomicU64::new(0);
2781 static N: AtomicU64 = AtomicU64::new(0);
2782 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2783 return;
2784 }
2785 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2786 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2787 if n % 100 == 0 {
2788 eprintln!(
2789 "hostprof: total {:.2} ms/token over {n} tokens",
2790 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2791 );
2792 }
2793}
2794
2795pub fn stageprof(stage: u32, dt: std::time::Duration) {
2799 use std::sync::atomic::{AtomicU64, Ordering};
2800 static NS: [AtomicU64; 4] = [
2801 AtomicU64::new(0),
2802 AtomicU64::new(0),
2803 AtomicU64::new(0),
2804 AtomicU64::new(0),
2805 ];
2806 static N: AtomicU64 = AtomicU64::new(0);
2807 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2808 return;
2809 }
2810 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2811 if stage == 1 {
2812 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2813 if n % 200 == 0 {
2814 eprintln!(
2815 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2816 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2817 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2818 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2819 );
2820 }
2821 }
2822}
2823
2824pub fn weight_bytes_dispatched() -> u64 {
2827 let mut total = 0u64;
2828 #[cfg(target_os = "macos")]
2829 {
2830 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2831 }
2832 #[cfg(feature = "gpu")]
2833 {
2834 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2835 }
2836 total
2837}
2838
2839pub fn weight_bytes_by() -> [u64; 6] {
2842 #[cfg(target_os = "macos")]
2843 {
2844 let mut o = [0u64; 6];
2845 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2846 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2847 }
2848 return o;
2849 }
2850 #[allow(unreachable_code)]
2851 [0; 6]
2852}
2853
2854#[cfg(test)]
2855mod probe_warmup_tests {
2856 use super::*;
2857 use std::time::Duration;
2858
2859 fn ms(v: f64) -> Duration {
2860 Duration::from_nanos((v * 1e6) as u64)
2861 }
2862
2863 #[test]
2868 fn one_cold_first_sample_does_not_lose_the_class() {
2869 let p = Probe::new();
2870 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
2872 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
2873 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
2874 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
2875 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
2876 assert_eq!(
2877 p.state.load(Ordering::Relaxed),
2878 1,
2879 "the device is 3x faster once warm and must win"
2880 );
2881 }
2882
2883 #[test]
2887 fn the_warmup_is_spent_once_and_never_underflows() {
2888 let p = Probe::new();
2889 for _ in 0..8 {
2890 probe_record_into(&p, "matmat", None, true, ms(10.0));
2891 }
2892 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
2893 assert_eq!(
2894 p.gpu_n.load(Ordering::Relaxed),
2895 7,
2896 "one sample burned, the rest counted"
2897 );
2898 }
2899
2900 #[test]
2906 fn a_class_whose_device_always_declines_settles_on_the_host() {
2907 let c = OpClass::MatmatWide;
2911 let p = &PROBES[c as usize];
2912 p.state.store(0, Ordering::Relaxed);
2913 p.declines.store(0, Ordering::Relaxed);
2914 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
2915 probe_note_decline(c);
2916 }
2917 assert_eq!(
2918 p.state.load(Ordering::Relaxed),
2919 0,
2920 "one short of the limit is still a question, not an answer"
2921 );
2922 probe_note_decline(c);
2923 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
2924 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
2925 p.state.store(0, Ordering::Relaxed);
2926 p.declines.store(0, Ordering::Relaxed);
2927 }
2928
2929 #[test]
2932 fn a_slow_device_still_loses_after_the_warmup() {
2933 let p = Probe::new();
2934 for _ in 0..4 {
2935 probe_record_into(&p, "matvec", None, true, ms(40.0));
2936 }
2937 for _ in 0..4 {
2938 probe_record_into(&p, "matvec", None, false, ms(2.0));
2939 }
2940 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
2941 }
2942}