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 sigmoid: bool,
1353 bias: Option<&'a [f32]>,
1356 has_shared: bool,
1360 },
1361}
1362
1363#[allow(clippy::too_many_arguments)]
1368pub fn forward_token_graph(
1369 model: &Arc<CmfModel>,
1370 kv_id: u64,
1371 layers: &[GraphLayer],
1372 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1375 o1_epoch: u64,
1376 invf: &[f32],
1377 h: &mut [f32],
1378 nh: usize,
1379 nkv: usize,
1380 hd: usize,
1381 rd: usize,
1382 hidden: usize,
1383 inter: usize,
1384 position: usize,
1385 cap: usize,
1386 gemma: bool,
1387 eps: f32,
1388 lm_head: Option<(&GraphW, usize)>,
1389 final_norm: &[f32],
1390 logits: &mut Vec<f32>,
1391 loop_norm_at: &[usize],
1392 steps: usize,
1393 embed: Option<(&GraphW, usize, f32)>,
1394 ids_out: Option<&mut Vec<u32>>,
1395 layers_run: Option<&mut usize>,
1398 layer_base: usize,
1402 hidden_too: bool,
1404) -> bool {
1405 match backend() {
1406 #[cfg(feature = "gpu")]
1407 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1408 model,
1409 kv_id,
1410 layers,
1411 o1,
1412 o1_epoch,
1413 invf,
1414 h,
1415 nh,
1416 nkv,
1417 hd,
1418 rd,
1419 hidden,
1420 inter,
1421 position,
1422 cap,
1423 gemma,
1424 eps,
1425 lm_head,
1426 final_norm,
1427 logits,
1428 loop_norm_at,
1429 steps,
1430 embed,
1431 ids_out,
1432 layers_run,
1433 layer_base,
1434 hidden_too,
1435 ),
1436 #[allow(unused_variables)]
1437 _ => {
1438 let _ = (
1439 lm_head,
1440 final_norm,
1441 logits,
1442 loop_norm_at,
1443 layers_run,
1444 layer_base,
1445 hidden_too,
1446 );
1447 false
1448 }
1449 }
1450}
1451
1452pub struct SpecTail<'a> {
1456 pub lm: GraphW<'a>,
1457 pub lm_rows: usize,
1458 pub final_norm: &'a [f32],
1459 pub logits_out: &'a mut Vec<f32>,
1460}
1461
1462#[allow(clippy::too_many_arguments)]
1466pub fn forward_batch_graph(
1467 model: &Arc<CmfModel>,
1468 kv_id: u64,
1469 layers: &[GraphLayer],
1470 invf: &[f32],
1471 h: &mut [f32],
1472 nh: usize,
1473 nkv: usize,
1474 hd: usize,
1475 rd: usize,
1476 hidden: usize,
1477 inter: usize,
1478 positions: &[usize],
1479 cap: usize,
1480 gemma: bool,
1481 eps: f32,
1482 k: usize,
1483 spec: Option<SpecTail<'_>>,
1484) -> bool {
1485 match backend() {
1486 #[cfg(feature = "gpu")]
1487 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1488 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1489 eps, k, spec,
1490 ),
1491 #[allow(unreachable_patterns)]
1492 _ => {
1493 let _ = spec;
1494 false
1495 }
1496 }
1497}
1498
1499pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1502 #[cfg(feature = "gpu")]
1503 if backend() == Backend::Wgpu {
1504 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1505 }
1506 #[allow(unreachable_code)]
1507 {
1508 let _ = (kv_id, slot);
1509 false
1510 }
1511}
1512
1513pub fn graph_kv_reset(_kv_id: u64) {
1515 #[cfg(feature = "gpu")]
1516 if backend() == Backend::Wgpu {
1517 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1518 }
1519}
1520
1521pub fn q1t_matvec(
1525 model: &Arc<CmfModel>,
1526 idx: usize,
1527 xs: &[f32],
1528 rows: usize,
1529 cols: usize,
1530 out: &mut [f32],
1531) -> bool {
1532 match backend() {
1533 #[cfg(target_os = "macos")]
1534 Backend::Metal => {
1535 if metal_q1t_enabled() {
1536 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1537 } else {
1538 false
1539 }
1540 }
1541 #[cfg(feature = "gpu")]
1542 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1543 Backend::None => false,
1544 }
1545}
1546
1547#[allow(unused_variables)]
1550pub fn q4b_matvec(
1551 model: &Arc<CmfModel>,
1552 idx: usize,
1553 xs: &[f32],
1554 rows: usize,
1555 cols: usize,
1556 out: &mut [f32],
1557) -> bool {
1558 match backend() {
1559 #[cfg(target_os = "macos")]
1560 Backend::Metal => false,
1561 #[cfg(feature = "gpu")]
1562 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1563 Backend::None => false,
1564 }
1565}
1566
1567pub fn q1t_matmat(
1570 model: &Arc<CmfModel>,
1571 idx: usize,
1572 xs: &[f32],
1573 b: usize,
1574 rows: usize,
1575 cols: usize,
1576 out: &mut [f32],
1577) -> bool {
1578 match backend() {
1579 #[cfg(target_os = "macos")]
1580 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1584 #[cfg(feature = "gpu")]
1585 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1586 Backend::None => false,
1587 }
1588}
1589
1590#[cfg(target_os = "macos")]
1594pub(crate) fn metal_q1t_enabled() -> bool {
1595 std::env::var("CMF_METAL_Q1T")
1596 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1597 .unwrap_or(true)
1598}
1599
1600pub fn q1_matmat(
1602 model: &Arc<CmfModel>,
1603 idx: usize,
1604 xs: &[f32],
1605 b: usize,
1606 rows: usize,
1607 cols: usize,
1608 out: &mut [f32],
1609) -> bool {
1610 match backend() {
1611 #[cfg(feature = "gpu")]
1612 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1613 #[allow(unused_variables)]
1614 _ => false,
1615 }
1616}
1617
1618static MM_KILL: AtomicBool = AtomicBool::new(false);
1623pub(crate) fn mm_killed() -> bool {
1624 MM_KILL.load(Ordering::Relaxed)
1625}
1626pub(crate) fn mm_kill() {
1627 MM_KILL.store(true, Ordering::Relaxed);
1628}
1629
1630static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1637const MM_STRIKES_TO_KILL: u32 = 3;
1638static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1645
1646pub fn mm_kill_arm(on: bool) {
1649 MM_ARMED.store(on, Ordering::Relaxed);
1650 if on {
1651 MM_STRIKES.store(0, Ordering::Relaxed);
1652 }
1653}
1654
1655pub(crate) fn mm_budget_check(
1662 what: &str,
1663 el: std::time::Duration,
1664 budget: std::time::Duration,
1665 exempt: bool,
1666) {
1667 if el <= budget {
1668 MM_STRIKES.store(0, Ordering::Relaxed);
1669 return;
1670 }
1671 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1672 return;
1673 }
1674 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1675 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1676 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1677 if !on {
1678 tracing::info!(
1679 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1680 );
1681 return;
1682 }
1683 if n >= MM_STRIKES_TO_KILL {
1684 tracing::warn!(
1685 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1686 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1687 );
1688 mm_kill();
1689 } else {
1690 tracing::info!(
1691 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1692 );
1693 }
1694}
1695
1696#[allow(unused_variables, clippy::too_many_arguments)]
1701pub fn chunk_attend(
1702 q: &[f32],
1703 k: &[&[f32]],
1704 v: &[&[f32]],
1705 b: usize,
1706 s0: usize,
1707 nh: usize,
1708 nkv: usize,
1709 hd: usize,
1710 scale: f32,
1711 out: &mut [f32],
1712) -> bool {
1713 match backend() {
1714 #[cfg(feature = "gpu")]
1715 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1716 #[allow(unreachable_patterns)]
1717 _ => false,
1718 }
1719}
1720
1721#[allow(unused_variables, clippy::too_many_arguments)]
1725pub fn q4t_qkv(
1726 model: &Arc<CmfModel>,
1727 wq: usize,
1728 wk: usize,
1729 wv: usize,
1730 xs: &[f32],
1731 b: usize,
1732 cols: usize,
1733 rq: usize,
1734 rk: usize,
1735 rv: usize,
1736 out: &mut [f32],
1737) -> bool {
1738 match backend() {
1739 #[cfg(feature = "gpu")]
1740 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1741 #[allow(unreachable_patterns)]
1742 _ => false,
1743 }
1744}
1745
1746#[allow(unused_variables, clippy::too_many_arguments)]
1748#[allow(clippy::too_many_arguments, unused_variables)]
1752pub fn q4tp_ffn_packed(
1753 model: &Arc<CmfModel>,
1754 w1: usize,
1755 w2: usize,
1756 xs: &[f32],
1757 b: usize,
1758 hidden: usize,
1759 inter: usize,
1760 bias: Option<&[f32]>,
1761 out: &mut [f32],
1762) -> bool {
1763 match backend() {
1764 #[cfg(feature = "gpu")]
1765 Backend::Wgpu => {
1766 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1767 }
1768 #[allow(unreachable_patterns)]
1769 _ => false,
1770 }
1771}
1772
1773pub fn q4tp_ffn(
1774 model: &Arc<CmfModel>,
1775 w1: usize,
1776 w3: usize,
1777 w2: usize,
1778 xs: &[f32],
1779 b: usize,
1780 hidden: usize,
1781 inter: usize,
1782 out: &mut [f32],
1783) -> bool {
1784 match backend() {
1785 #[cfg(target_os = "macos")]
1786 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1787 #[cfg(feature = "gpu")]
1788 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1789 #[allow(unreachable_patterns)]
1790 _ => false,
1791 }
1792}
1793
1794pub fn q4t_ffn(
1795 model: &Arc<CmfModel>,
1796 w1: usize,
1797 w3: usize,
1798 w2: usize,
1799 xs: &[f32],
1800 b: usize,
1801 hidden: usize,
1802 inter: usize,
1803 out: &mut [f32],
1804) -> bool {
1805 match backend() {
1806 #[cfg(target_os = "macos")]
1807 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1808 #[cfg(feature = "gpu")]
1809 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1810 #[allow(unreachable_patterns)]
1811 _ => false,
1812 }
1813}
1814
1815pub struct DitBlockArgs<'a> {
1820 pub n: usize,
1821 pub hidden: usize,
1822 pub inter: usize,
1823 pub nh: usize,
1824 pub nkv: usize,
1825 pub hd: usize,
1826 pub eps: f32,
1827 pub rope_cos: &'a [f32],
1828 pub rope_sin: &'a [f32],
1829 pub norm1: &'a [f32],
1830 pub norm2: &'a [f32],
1831 pub ffn_norm1: &'a [f32],
1832 pub ffn_norm2: &'a [f32],
1833 pub norm_q: &'a [f32],
1834 pub norm_k: &'a [f32],
1835 pub s_msa: &'a [f32],
1836 pub gate_msa: &'a [f32],
1837 pub s_mlp: &'a [f32],
1838 pub gate_mlp: &'a [f32],
1839 pub wq: usize,
1840 pub wk: usize,
1841 pub wv: usize,
1842 pub wo: usize,
1843 pub w1: usize,
1844 pub w3: usize,
1845 pub w2: usize,
1846 pub q4tp: bool,
1850 pub resident_in: bool,
1853 pub resident_out: bool,
1857}
1858
1859pub fn dit_chain_supported() -> bool {
1863 #[cfg(feature = "gpu")]
1864 {
1865 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1866 }
1867 #[allow(unreachable_code)]
1868 false
1869}
1870
1871pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1874 #[cfg(feature = "gpu")]
1875 {
1876 if matches!(backend(), Backend::Wgpu) {
1877 return crate::gpu_wgpu::dit_state_fetch(_x);
1878 }
1879 }
1880 false
1881}
1882
1883#[allow(unused_variables)]
1887#[allow(unused_variables, clippy::too_many_arguments)]
1891pub fn dit_qkv(
1892 model: &Arc<CmfModel>,
1893 wq: usize,
1894 wk: usize,
1895 wv: usize,
1896 xs: &[f32],
1897 b: usize,
1898 hidden: usize,
1899 qrows: usize,
1900 kvrows: usize,
1901 q_out: &mut [f32],
1902 k_out: &mut [f32],
1903 v_out: &mut [f32],
1904) -> bool {
1905 match backend() {
1906 #[cfg(feature = "gpu")]
1907 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1908 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1909 ),
1910 #[allow(unreachable_patterns)]
1911 _ => false,
1912 }
1913}
1914
1915pub fn fused_dit_block_available() -> bool {
1919 #[cfg(target_os = "macos")]
1920 {
1921 matches!(backend(), Backend::Metal) && fused_block_trusted()
1922 }
1923 #[cfg(not(target_os = "macos"))]
1924 {
1925 false
1926 }
1927}
1928
1929pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1930 dit_block_seg(model, a, &[a.n], x)
1931}
1932
1933pub fn dit_block_seg(
1937 model: &Arc<CmfModel>,
1938 a: &DitBlockArgs,
1939 segs: &[usize],
1940 x: &mut [f32],
1941) -> bool {
1942 match backend() {
1943 #[cfg(target_os = "macos")]
1944 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1945 #[cfg(feature = "gpu")]
1952 Backend::Wgpu
1953 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1954 Some("0") => false,
1955 Some(_) => true,
1956 None => crate::gpu_wgpu::discrete_active(),
1957 } =>
1958 {
1959 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1960 }
1961 #[allow(unreachable_patterns)]
1962 _ => false,
1963 }
1964}
1965
1966pub struct VaeResnetArgs<'a> {
1970 pub groups: usize,
1971 pub ic: usize,
1972 pub oc: usize,
1973 pub h: usize,
1974 pub w: usize,
1975 pub n1w: &'a [f32],
1976 pub n1b: &'a [f32],
1977 pub c1w: &'a [f32],
1978 pub c1b: &'a [f32],
1979 pub c1k: usize,
1980 pub n2w: &'a [f32],
1981 pub n2b: &'a [f32],
1982 pub c2w: &'a [f32],
1983 pub c2b: &'a [f32],
1984 pub c2k: usize,
1985 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1986}
1987
1988#[allow(unused_variables)]
1991pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1992 match backend() {
1993 #[cfg(target_os = "macos")]
1994 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1995 _ => false,
1996 }
1997}
1998
1999#[allow(unused_variables, clippy::too_many_arguments)]
2002pub fn vae_upsample_conv(
2003 w: &[f32],
2004 bias: &[f32],
2005 x: &[f32],
2006 ic: usize,
2007 oc: usize,
2008 h: usize,
2009 w_img: usize,
2010 k: usize,
2011 out: &mut [f32],
2012) -> bool {
2013 match backend() {
2014 #[cfg(target_os = "macos")]
2015 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2016 #[cfg(feature = "gpu")]
2017 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2018 #[allow(unreachable_patterns)]
2019 _ => false,
2020 }
2021}
2022
2023#[allow(unused_variables, clippy::too_many_arguments)]
2026pub fn vae_conv2d(
2027 w: &[f32],
2028 bias: &[f32],
2029 x: &[f32],
2030 ic: usize,
2031 oc: usize,
2032 h: usize,
2033 w_img: usize,
2034 k: usize,
2035 out: &mut [f32],
2036) -> bool {
2037 match backend() {
2038 #[cfg(target_os = "macos")]
2039 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2040 #[cfg(feature = "gpu")]
2041 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2042 #[allow(unreachable_patterns)]
2043 _ => false,
2044 }
2045}
2046
2047#[allow(unused_variables, clippy::too_many_arguments)]
2051#[allow(unused_variables)]
2055#[allow(clippy::too_many_arguments)]
2056#[allow(clippy::too_many_arguments, unused_variables)]
2059pub fn dit_qkv_attention(
2060 model: &Arc<CmfModel>,
2061 qkv_idx: usize,
2062 xn: &[f32],
2063 n: usize,
2064 hidden: usize,
2065 nh: usize,
2066 hd: usize,
2067 scale: f32,
2068 nr: (&[f32], &[f32], &[f32], f32),
2069 out: &mut [f32],
2070) -> bool {
2071 match backend() {
2072 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2073 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2074 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2075 ),
2076 #[allow(unreachable_patterns)]
2077 _ => false,
2078 }
2079}
2080
2081#[allow(clippy::too_many_arguments)]
2084pub fn dit_qkv_attn_out(
2085 model: &Arc<CmfModel>,
2086 qkv_idx: usize,
2087 out_idx: usize,
2088 xn: &[f32],
2089 n: usize,
2090 hidden: usize,
2091 nh: usize,
2092 hd: usize,
2093 scale: f32,
2094 nr: (&[f32], &[f32], &[f32], f32),
2095 proj: &mut [f32],
2096) -> bool {
2097 match backend() {
2098 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2099 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2100 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2101 ),
2102 #[allow(unreachable_patterns)]
2103 _ => false,
2104 }
2105}
2106
2107#[allow(clippy::too_many_arguments)]
2109pub fn vae_qkv_attn_out(
2110 model: &Arc<CmfModel>,
2111 qkv_idx: usize,
2112 out_idx: usize,
2113 xn: &[f32],
2114 n: usize,
2115 dim: usize,
2116 nh: usize,
2117 hd: usize,
2118 scale: f32,
2119 angles: &[f32],
2120 eps: f32,
2121 qkv_bias: &[f32],
2122 proj: &mut [f32],
2123) -> bool {
2124 match backend() {
2125 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2126 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2127 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2128 ),
2129 #[allow(unreachable_patterns)]
2130 _ => false,
2131 }
2132}
2133
2134#[allow(clippy::too_many_arguments)]
2135pub fn vae_attention_packed(
2136 qkv: &[f32],
2137 nh: usize,
2138 n: usize,
2139 hd: usize,
2140 scale: f32,
2141 angles: &[f32],
2142 eps: f32,
2143 out: &mut [f32],
2144) -> bool {
2145 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2146}
2147
2148#[allow(clippy::too_many_arguments)]
2149pub fn vae_attention_packed_layout(
2150 qkv: &[f32],
2151 nh: usize,
2152 n: usize,
2153 hd: usize,
2154 scale: f32,
2155 angles: &[f32],
2156 eps: f32,
2157 out: &mut [f32],
2158 layout: u32,
2159) -> bool {
2160 match backend() {
2161 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2162 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2163 qkv, nh, n, hd, scale, angles, eps, out, layout,
2164 ),
2165 #[allow(unreachable_patterns)]
2166 _ => false,
2167 }
2168}
2169
2170#[allow(clippy::too_many_arguments)]
2171pub fn dit_split_only(
2172 qkv: &[f32],
2173 nh: usize,
2174 n: usize,
2175 hd: usize,
2176 layout: u32,
2177 norm: Option<(&[f32], f32)>,
2178 out_q: &mut [f32],
2179) -> bool {
2180 match backend() {
2181 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2182 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2183 #[allow(unreachable_patterns)]
2184 _ => false,
2185 }
2186}
2187
2188pub fn gemm_nt_f32_transient(
2196 x: &[f32],
2197 w: &[f32],
2198 y: &mut [f32],
2199 n: usize,
2200 k: usize,
2201 m: usize,
2202) -> bool {
2203 match backend() {
2204 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2205 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2206 #[allow(unreachable_patterns)]
2207 _ => false,
2208 }
2209}
2210
2211pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2212 match backend() {
2213 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2214 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2215 #[allow(unreachable_patterns)]
2216 _ => false,
2217 }
2218}
2219
2220#[allow(clippy::too_many_arguments)]
2223pub fn music3_ffn(
2224 model: &std::sync::Arc<CmfModel>,
2225 idx_in: usize,
2226 idx_out: usize,
2227 h: &[f32],
2228 bias_in: &[f32],
2229 n: usize,
2230 hs: usize,
2231 inter: usize,
2232 out: &mut [f32],
2233) -> bool {
2234 match backend() {
2235 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2236 Backend::Wgpu => {
2237 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2238 }
2239 #[allow(unreachable_patterns)]
2240 _ => false,
2241 }
2242}
2243
2244#[allow(clippy::too_many_arguments)]
2248pub fn conv1d_gemm(
2249 x: &[f32],
2250 w: &[f32],
2251 ic: usize,
2252 oc: usize,
2253 n: usize,
2254 k: usize,
2255 pad: usize,
2256 dil: usize,
2257 out_n: usize,
2258 yt: &mut [f32],
2259) -> bool {
2260 match backend() {
2261 #[cfg(target_os = "macos")]
2262 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2263 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2264 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2265 #[allow(unreachable_patterns)]
2266 _ => false,
2267 }
2268}
2269
2270#[allow(clippy::too_many_arguments)]
2272pub fn vae_conv2d_coop(
2273 w: &[f32],
2274 bias: Option<&[f32]>,
2275 x: &[f32],
2276 ic: usize,
2277 oc: usize,
2278 h: usize,
2279 wi: usize,
2280 k: usize,
2281 out: &mut [f32],
2282) -> bool {
2283 match backend() {
2284 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2285 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2286 #[allow(unreachable_patterns)]
2287 _ => false,
2288 }
2289}
2290
2291pub fn dit_attention_packed(
2292 qkv: &[f32],
2293 nh: usize,
2294 n: usize,
2295 hd: usize,
2296 scale: f32,
2297 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2300 out: &mut [f32],
2301) -> bool {
2302 match backend() {
2303 #[cfg(feature = "gpu")]
2310 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2311 #[allow(unreachable_patterns)]
2312 _ => false,
2313 }
2314}
2315
2316pub fn dit_attention_packed_available() -> bool {
2324 #[allow(unreachable_patterns)]
2325 match backend() {
2326 #[cfg(feature = "gpu")]
2327 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2328 _ => false,
2329 }
2330}
2331
2332pub fn dit_attention(
2333 qh: &[f32],
2334 kh: &[f32],
2335 vh: &[f32],
2336 nh: usize,
2337 nkv: usize,
2338 n: usize,
2339 hd: usize,
2340 scale: f32,
2341 out: &mut [f32],
2342) -> bool {
2343 match backend() {
2344 #[cfg(target_os = "macos")]
2345 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2346 #[cfg(feature = "gpu")]
2347 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2348 #[allow(unreachable_patterns)]
2349 _ => false,
2350 }
2351}
2352
2353#[allow(unused_variables)]
2358pub fn q4tp_matmat(
2359 model: &Arc<CmfModel>,
2360 idx: usize,
2361 xs: &[f32],
2362 b: usize,
2363 rows: usize,
2364 cols: usize,
2365 out: &mut [f32],
2366) -> bool {
2367 match backend() {
2368 #[cfg(target_os = "macos")]
2369 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2370 #[cfg(feature = "gpu")]
2371 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2372 #[allow(unreachable_patterns)]
2373 _ => false,
2374 }
2375}
2376
2377pub fn q2tp_matmat(
2380 model: &Arc<CmfModel>,
2381 idx: usize,
2382 xs: &[f32],
2383 b: usize,
2384 rows: usize,
2385 cols: usize,
2386 out: &mut [f32],
2387) -> bool {
2388 match backend() {
2389 #[cfg(feature = "gpu")]
2390 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2391 #[allow(unreachable_patterns)]
2392 _ => false,
2393 }
2394}
2395
2396pub fn q4tp_matvec(
2401 model: &Arc<CmfModel>,
2402 idx: usize,
2403 xs: &[f32],
2404 rows: usize,
2405 cols: usize,
2406 out: &mut [f32],
2407) -> bool {
2408 match backend() {
2409 #[cfg(target_os = "macos")]
2410 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2411 #[cfg(feature = "gpu")]
2412 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2413 #[allow(unreachable_patterns)]
2414 _ => false,
2415 }
2416}
2417
2418pub fn q4t_matvec(
2424 model: &Arc<CmfModel>,
2425 idx: usize,
2426 xs: &[f32],
2427 rows: usize,
2428 cols: usize,
2429 out: &mut [f32],
2430) -> bool {
2431 match backend() {
2432 #[cfg(target_os = "macos")]
2433 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2434 #[allow(unreachable_patterns)]
2435 _ => false,
2436 }
2437}
2438
2439pub fn q4t_matmat(
2440 model: &Arc<CmfModel>,
2441 idx: usize,
2442 xs: &[f32],
2443 b: usize,
2444 rows: usize,
2445 cols: usize,
2446 out: &mut [f32],
2447) -> bool {
2448 match backend() {
2449 #[cfg(target_os = "macos")]
2450 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2451 #[cfg(feature = "gpu")]
2452 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2453 #[allow(unreachable_patterns)]
2454 _ => false,
2455 }
2456}
2457
2458#[cfg(target_os = "macos")]
2460pub use crate::gpu_metal::{
2461 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2462 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2463};
2464
2465#[cfg(target_os = "macos")]
2467pub fn gdn_block(
2468 model: &Arc<CmfModel>,
2469 layers: &[GdnGpuLayer],
2470 states: &mut [&mut [f32]],
2471 cfg: &GdnGpuCfg,
2472 h: &mut [f32],
2473) -> bool {
2474 match backend() {
2475 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2476 _ => false,
2477 }
2478}
2479
2480#[allow(unused_variables)]
2482pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2483 match backend() {
2484 #[cfg(target_os = "macos")]
2485 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2486 #[cfg(feature = "gpu")]
2487 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2488 Backend::None => false,
2489 }
2490}
2491
2492#[allow(unused_variables)]
2494pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2495 match backend() {
2496 #[cfg(target_os = "macos")]
2497 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2498 #[cfg(feature = "gpu")]
2499 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2500 Backend::None => false,
2501 }
2502}
2503
2504static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2520static 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)];
2524
2525const GRAPH_RACE_SAMPLES: u32 = 4;
2527
2528static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2538
2539pub fn graph_mark_unsupported() {
2544 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2545 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2546 }
2547}
2548
2549pub fn graph_unsupported() -> bool {
2550 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2551}
2552
2553pub fn graph_unsupported_reset() {
2555 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2556}
2557
2558pub fn graph_race_begin_generation() {
2559 #[cfg(feature = "gpu")]
2564 {
2565 static FLUSHED: std::sync::Once = std::sync::Once::new();
2577 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2578 if FIRST.swap(false, Ordering::Relaxed) {
2579 } else {
2581 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2582 }
2583 }
2584 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2585 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2586 return;
2587 }
2588 let (gn, cn) = (
2589 GRAPH_N[1].load(Ordering::Relaxed),
2590 GRAPH_N[0].load(Ordering::Relaxed),
2591 );
2592 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2593 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2594 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2595 let verdict = if g_avg < c_avg { 1 } else { 2 };
2596 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2597 tracing::info!(
2598 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2599 g_avg as f64 / 1e6,
2600 c_avg as f64 / 1e6,
2601 if verdict == 1 { "graph" } else { "normal path" }
2602 );
2603 return;
2604 }
2605 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2606 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2607}
2608
2609pub fn graph_race_use_graph(trusted: bool) -> bool {
2613 if trusted {
2614 return true;
2615 }
2616 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2617 1 => true,
2618 2 => false,
2619 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2620 }
2621}
2622
2623pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2628 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2629 return false;
2630 }
2631 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2632 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2633 if !first || cn == 0 {
2634 return false;
2635 }
2636 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2637 let ns = dur.as_nanos() as u64;
2638 if ns > 1_000_000_000 && ns > 4 * c_avg {
2639 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2640 tracing::info!(
2641 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2642 ns as f64 / 1e6,
2643 c_avg as f64 / 1e6
2644 );
2645 return true;
2646 }
2647 false
2648}
2649
2650pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2654 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2655 return;
2656 }
2657 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2658 if tok == 0 {
2659 return;
2660 }
2661 let i = used_graph as usize;
2662 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2663 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2664}
2665
2666pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2676 #[inline]
2677 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2678 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2679 for c in chunks.chunks_exact(8) {
2680 h ^= u64::from_le_bytes(c.try_into().unwrap());
2681 h = h.wrapping_mul(0x100_0000_01b3);
2682 }
2683 for &b in tail {
2684 h ^= b as u64;
2685 h = h.wrapping_mul(0x100_0000_01b3);
2686 }
2687 h
2688 }
2689 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2690 if data.len() <= 4096 {
2691 return fnv(h, data);
2692 }
2693 let step = (data.len() - 64) / 63;
2694 for i in 0..64 {
2695 h = fnv(h, &data[i * step..i * step + 64]);
2696 }
2697 h
2698}
2699
2700pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2703 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2704 fp_bytes(bytes)
2705}
2706
2707#[cfg(test)]
2708mod fp_tests {
2709 use super::fp_bytes;
2710
2711 #[test]
2716 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2717 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2719 let h0 = fp_bytes(&base);
2720 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2721 let mut dense = base.clone();
2724 for b in dense.iter_mut() {
2725 *b = b.wrapping_add(1);
2726 }
2727 assert_ne!(
2728 h0,
2729 fp_bytes(&dense),
2730 "a fully different tensor slipped through"
2731 );
2732 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2735 let mut small = vec![3u8; 4096];
2738 let hs = fp_bytes(&small);
2739 small[2048] ^= 1;
2740 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2741 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2743 let v = vec![9u8; n];
2744 let _ = fp_bytes(&v); }
2746 }
2747}
2748
2749pub fn bake_release() {
2753 #[cfg(feature = "gpu")]
2754 crate::gpu_wgpu::bake_release();
2755}
2756
2757pub fn bake_precision_strict(on: bool) {
2761 #[cfg(feature = "gpu")]
2762 crate::gpu_wgpu::bake_precision_strict(on);
2763 #[cfg(not(feature = "gpu"))]
2764 let _ = on;
2765}
2766
2767pub fn hostprof_encode_done(t0: std::time::Instant) {
2773 use std::sync::atomic::{AtomicU64, Ordering};
2774 static ENC: AtomicU64 = AtomicU64::new(0);
2775 static N: AtomicU64 = AtomicU64::new(0);
2776 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2777 return;
2778 }
2779 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2780 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2781 if n % 100 == 0 {
2782 eprintln!(
2783 "hostprof: encode {:.2} ms/token over {n} tokens",
2784 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2785 );
2786 }
2787}
2788
2789pub fn hostprof_total(t0: std::time::Instant) {
2790 use std::sync::atomic::{AtomicU64, Ordering};
2791 static TOT: AtomicU64 = AtomicU64::new(0);
2792 static N: AtomicU64 = AtomicU64::new(0);
2793 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2794 return;
2795 }
2796 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2797 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2798 if n % 100 == 0 {
2799 eprintln!(
2800 "hostprof: total {:.2} ms/token over {n} tokens",
2801 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2802 );
2803 }
2804}
2805
2806pub fn stageprof(stage: u32, dt: std::time::Duration) {
2810 use std::sync::atomic::{AtomicU64, Ordering};
2811 static NS: [AtomicU64; 4] = [
2812 AtomicU64::new(0),
2813 AtomicU64::new(0),
2814 AtomicU64::new(0),
2815 AtomicU64::new(0),
2816 ];
2817 static N: AtomicU64 = AtomicU64::new(0);
2818 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2819 return;
2820 }
2821 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2822 if stage == 1 {
2823 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2824 if n % 200 == 0 {
2825 eprintln!(
2826 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2827 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2828 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2829 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2830 );
2831 }
2832 }
2833}
2834
2835pub fn weight_bytes_dispatched() -> u64 {
2838 let mut total = 0u64;
2839 #[cfg(target_os = "macos")]
2840 {
2841 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2842 }
2843 #[cfg(feature = "gpu")]
2844 {
2845 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2846 }
2847 total
2848}
2849
2850pub fn weight_bytes_by() -> [u64; 6] {
2853 #[cfg(target_os = "macos")]
2854 {
2855 let mut o = [0u64; 6];
2856 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2857 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2858 }
2859 return o;
2860 }
2861 #[allow(unreachable_code)]
2862 [0; 6]
2863}
2864
2865#[cfg(test)]
2866mod probe_warmup_tests {
2867 use super::*;
2868 use std::time::Duration;
2869
2870 fn ms(v: f64) -> Duration {
2871 Duration::from_nanos((v * 1e6) as u64)
2872 }
2873
2874 #[test]
2879 fn one_cold_first_sample_does_not_lose_the_class() {
2880 let p = Probe::new();
2881 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
2883 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
2884 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
2885 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
2886 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
2887 assert_eq!(
2888 p.state.load(Ordering::Relaxed),
2889 1,
2890 "the device is 3x faster once warm and must win"
2891 );
2892 }
2893
2894 #[test]
2898 fn the_warmup_is_spent_once_and_never_underflows() {
2899 let p = Probe::new();
2900 for _ in 0..8 {
2901 probe_record_into(&p, "matmat", None, true, ms(10.0));
2902 }
2903 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
2904 assert_eq!(
2905 p.gpu_n.load(Ordering::Relaxed),
2906 7,
2907 "one sample burned, the rest counted"
2908 );
2909 }
2910
2911 #[test]
2917 fn a_class_whose_device_always_declines_settles_on_the_host() {
2918 let c = OpClass::MatmatWide;
2922 let p = &PROBES[c as usize];
2923 p.state.store(0, Ordering::Relaxed);
2924 p.declines.store(0, Ordering::Relaxed);
2925 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
2926 probe_note_decline(c);
2927 }
2928 assert_eq!(
2929 p.state.load(Ordering::Relaxed),
2930 0,
2931 "one short of the limit is still a question, not an answer"
2932 );
2933 probe_note_decline(c);
2934 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
2935 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
2936 p.state.store(0, Ordering::Relaxed);
2937 p.declines.store(0, Ordering::Relaxed);
2938 }
2939
2940 #[test]
2943 fn a_slow_device_still_loses_after_the_warmup() {
2944 let p = Probe::new();
2945 for _ in 0..4 {
2946 probe_record_into(&p, "matvec", None, true, ms(40.0));
2947 }
2948 for _ in 0..4 {
2949 probe_record_into(&p, "matvec", None, false, ms(2.0));
2950 }
2951 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
2952 }
2953}