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(
575 &PROBES[c as usize],
576 CLASS_NAMES[c as usize],
577 Some(c),
578 gpu,
579 dur,
580 )
581}
582
583fn probe_record_into(
586 p: &Probe,
587 class_name: &str,
588 cache: Option<OpClass>,
589 gpu: bool,
590 dur: std::time::Duration,
591) {
592 if p.state.load(Ordering::Relaxed) != 0 {
593 return;
594 }
595 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
596 return; }
598 if gpu {
599 let left = p.gpu_burn.load(Ordering::Relaxed);
603 if left > 0 {
604 p.gpu_burn.store(left - 1, Ordering::Relaxed);
605 return; }
607 }
608 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
609 if gpu {
610 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
611 p.gpu_n.fetch_add(1, Ordering::Relaxed);
612 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
613 } else {
614 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
615 p.cpu_n.fetch_add(1, Ordering::Relaxed);
616 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
617 }
618 let (gn, cn) = (
619 p.gpu_n.load(Ordering::Relaxed),
620 p.cpu_n.load(Ordering::Relaxed),
621 );
622 if gn >= 2 && cn >= 2 {
623 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
627 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
628 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
638 return;
639 }
640 let winner = if g <= cp { 1 } else { 2 };
641 if p.state
642 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
643 .is_ok()
644 {
645 tracing::info!(
646 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
647 class_name,
648 g / 1e6,
649 cp / 1e6,
650 if winner == 1 { "gpu" } else { "cpu" },
651 );
652 if let Some(c) = cache {
653 probe_cache_store(c, winner);
654 }
655 }
656 }
657}
658
659pub fn probe_deciding(c: OpClass) -> bool {
662 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
663}
664
665#[allow(unused_variables)]
675pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
676 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
677 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
678 let resident = match backend() {
679 #[cfg(target_os = "macos")]
680 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
681 #[cfg(feature = "gpu")]
682 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
683 Backend::None => false,
684 };
685 if !resident && may_upload {
686 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
687 }
688 resident
689}
690
691#[cfg(test)]
693pub(crate) fn probe_reset() {
694 for p in &PROBES {
695 p.state.store(0, Ordering::Relaxed);
696 p.flip.store(0, Ordering::Relaxed);
697 p.gpu_ns.store(0, Ordering::Relaxed);
698 p.gpu_n.store(0, Ordering::Relaxed);
699 p.cpu_ns.store(0, Ordering::Relaxed);
700 p.cpu_n.store(0, Ordering::Relaxed);
701 }
702}
703
704#[cfg(test)]
705mod probe_tests {
706 use super::*;
707 use std::time::Duration;
708
709 #[test]
712 fn probe_alternates_discards_cold_and_decides() {
713 probe_reset();
714 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
716 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
717
718 probe_note_cold();
722 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
723 for _ in 0..PROBE_SAMPLES {
724 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
725 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
726 }
727 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
728
729 for _ in 0..PROBE_SAMPLES {
731 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
732 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
733 }
734 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
735
736 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
738 CPU_ONLY.with(|c| assert!(!c.get()));
739 cpu_scope(|| {
740 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
741 CPU_ONLY.with(|c| assert!(c.get()));
742 });
743 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
744 CPU_ONLY.with(|c| assert!(!c.get()));
745 probe_reset();
746 }
747
748 #[test]
749 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
750 let mine = probe_cache_key_named("gemm-nt");
762 let state = || {
763 PROBES[OpClass::GemmNt as usize]
764 .state
765 .load(Ordering::Relaxed)
766 };
767
768 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
770 assert_eq!(state(), 0);
771 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
773 assert_ne!(older, mine);
774 probe_cache_adopt(&format!("{older}\tgpu\n"));
775 assert_eq!(state(), 0);
776 probe_cache_adopt(&format!("{mine}\tcpu\n"));
778 assert_eq!(state(), 2);
779
780 PROBES[OpClass::GemmNt as usize]
781 .state
782 .store(0, Ordering::Relaxed);
783 }
784}
785
786pub const GPU_MIN_ROWS: usize = 65_536;
789
790pub fn min_rows() -> usize {
797 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
798 .ok()
799 .and_then(|v| v.parse().ok())
800 {
801 return v;
802 }
803 if discrete() { 4096 } else { GPU_MIN_ROWS }
804}
805
806pub fn discrete() -> bool {
808 match backend() {
809 #[cfg(feature = "gpu")]
810 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
811 #[cfg(target_os = "macos")]
812 Backend::Metal => false, Backend::None => false,
814 }
815}
816
817pub struct MoeJob<'a> {
821 pub gate: (usize, usize, usize, &'a [f32]),
822 pub up: (usize, usize, usize, &'a [f32]),
823 pub down: (usize, usize, usize, &'a [f32]),
824 pub xs_gate: Vec<f32>,
825 pub xs_up: Vec<f32>,
826 pub down_col: &'a [f32],
827 pub w: f32,
828 pub q1: bool,
831 pub q4t: bool,
834 pub q4tp: bool,
838 pub gu_q2: bool,
842 pub swiglu_limit: f32,
847}
848
849pub struct BatchJob<'a> {
851 pub idx: usize,
852 pub rows: usize,
853 pub cols: usize,
854 pub row_scale: &'a [f32],
855 pub xs: Vec<f32>,
856 pub layout: BatchLayout,
860}
861
862#[derive(Clone, Copy, PartialEq, Eq, Debug)]
865pub enum BatchLayout {
866 Q8,
867 Q1,
868 Q4t,
869 Q4tp,
870}
871
872#[derive(Clone, Copy, PartialEq, Eq)]
873enum Backend {
874 None,
875 #[cfg(target_os = "macos")]
876 Metal,
877 #[cfg(feature = "gpu")]
878 Wgpu,
879}
880
881fn backend() -> Backend {
882 #[cfg(feature = "gpu")]
883 if crate::gpu_wgpu::selected() {
884 return if crate::gpu_wgpu::enabled() {
885 Backend::Wgpu
886 } else {
887 Backend::None
888 };
889 }
890 #[cfg(target_os = "macos")]
891 if crate::gpu_metal::enabled() {
892 return Backend::Metal;
893 }
894 Backend::None
895}
896
897pub fn backend_available() -> bool {
903 #[cfg(target_os = "macos")]
904 {
905 true
907 }
908 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
909 {
910 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
911 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
912 }
913 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
914 {
915 false
916 }
917}
918
919static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
925
926pub fn pause_gpu() -> GpuPause {
928 GPU_PAUSED.store(true, Ordering::Relaxed);
929 GpuPause(())
930}
931
932pub struct GpuPause(());
933
934impl Drop for GpuPause {
935 fn drop(&mut self) {
936 GPU_PAUSED.store(false, Ordering::Relaxed);
937 }
938}
939
940pub fn enabled() -> bool {
941 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
942}
943
944pub fn wgpu_active() -> bool {
958 #[cfg(feature = "gpu")]
959 {
960 matches!(backend(), Backend::Wgpu)
961 }
962 #[cfg(not(feature = "gpu"))]
963 {
964 false
965 }
966}
967
968pub fn default_device() -> usize {
975 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
976 *D.get_or_init(|| {
977 std::env::var("CMF_GPU_ADAPTER")
978 .ok()
979 .and_then(|v| v.trim().parse::<usize>().ok())
980 .unwrap_or(0)
981 })
982}
983
984thread_local! {
985 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
986}
987
988pub fn current_device() -> usize {
990 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
991}
992
993pub fn set_current_device(i: usize) {
997 CUR_DEV.with(|c| c.set(Some(i)));
998}
999
1000pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1002 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1003 let r = f();
1004 CUR_DEV.with(|c| c.set(prev));
1005 r
1006}
1007
1008pub fn device_count() -> usize {
1011 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1012 {
1013 return crate::gpu_wgpu::adapter_count();
1014 }
1015 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1016 {
1017 usize::from(backend_available())
1018 }
1019}
1020
1021pub fn vram_budget() -> u64 {
1025 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1026 {
1027 return crate::gpu_wgpu::device_vram_budget();
1028 }
1029 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1030 {
1031 if backend_available() { u64::MAX } else { 0 }
1032 }
1033}
1034
1035pub fn upload_bytes() -> u64 {
1039 #[cfg(feature = "gpu")]
1040 {
1041 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1042 }
1043 #[cfg(not(feature = "gpu"))]
1044 0
1045}
1046
1047#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1059pub enum GraphPhase {
1060 Prefill,
1061 Decode,
1062}
1063
1064pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1072 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1073 Some("0") => false,
1074 Some("prefill") => phase == GraphPhase::Prefill,
1075 Some(_) => true,
1076 None => {
1077 if wgpu_graph_default() {
1078 return true;
1079 }
1080 let _ = phase;
1085 false
1086 }
1087 }
1088}
1089
1090pub fn wgpu_graph_default() -> bool {
1091 #[cfg(feature = "gpu")]
1092 {
1093 matches!(backend(), Backend::Wgpu)
1099 && (crate::gpu_wgpu::discrete_active()
1100 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1101 }
1102 #[cfg(not(feature = "gpu"))]
1103 {
1104 false
1105 }
1106}
1107
1108#[allow(clippy::too_many_arguments, unused_variables)]
1110pub fn q8_matvec_range(
1111 model: &Arc<CmfModel>,
1112 idx: usize,
1113 row0: usize,
1114 row_scale: &[f32],
1115 xs: &[f32],
1116 rows: usize,
1117 cols: usize,
1118 out: &mut [f32],
1119) -> bool {
1120 match backend() {
1121 #[cfg(target_os = "macos")]
1122 Backend::Metal => {
1123 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1124 }
1125 #[cfg(feature = "gpu")]
1126 Backend::Wgpu => {
1127 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1128 }
1129 Backend::None => false,
1130 }
1131}
1132
1133#[allow(clippy::too_many_arguments, unused_variables)]
1136#[allow(clippy::too_many_arguments)]
1140pub fn q8_matmat_2f(
1141 model: &Arc<CmfModel>,
1142 idx: usize,
1143 row_scale: &[f32],
1144 col_field: &[f32],
1145 xs: &[f32],
1146 b: usize,
1147 rows: usize,
1148 cols: usize,
1149 out: &mut [f32],
1150) -> bool {
1151 #[allow(unreachable_patterns)]
1152 match backend() {
1153 #[cfg(feature = "gpu")]
1154 Backend::Wgpu => {
1155 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1156 }
1157 _ => false,
1158 }
1159}
1160
1161pub fn q8_matmat(
1162 model: &Arc<CmfModel>,
1163 idx: usize,
1164 row_scale: &[f32],
1165 pre: &[f32],
1166 b: usize,
1167 rows: usize,
1168 cols: usize,
1169 out: &mut [f32],
1170) -> bool {
1171 match backend() {
1172 #[cfg(target_os = "macos")]
1173 Backend::Metal => {
1174 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1175 }
1176 #[cfg(feature = "gpu")]
1177 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1178 Backend::None => false,
1179 }
1180}
1181
1182#[allow(unused_variables)]
1185pub fn q1_matvec(
1186 model: &Arc<CmfModel>,
1187 idx: usize,
1188 xs: &[f32],
1189 rows: usize,
1190 cols: usize,
1191 out: &mut [f32],
1192) -> bool {
1193 match backend() {
1194 #[cfg(target_os = "macos")]
1195 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1196 #[cfg(feature = "gpu")]
1197 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1198 Backend::None => false,
1199 }
1200}
1201
1202#[allow(clippy::too_many_arguments)]
1206pub fn attn_dropin(
1207 model: &Arc<CmfModel>,
1208 kv_id: u64,
1209 layer: usize,
1210 normed: &[f32],
1211 wq_idx: usize,
1212 wk_idx: usize,
1213 wv_idx: usize,
1214 wo_idx: usize,
1215 q_norm: Option<&[f32]>,
1216 k_norm: Option<&[f32]>,
1217 invf: &[f32],
1218 nh: usize,
1219 nkv: usize,
1220 hd: usize,
1221 rd: usize,
1222 hidden: usize,
1223 pos: usize,
1224 cap: usize,
1225 gemma: bool,
1226 eps: f32,
1227 cpu_k: &[Vec<f32>],
1228 cpu_v: &[Vec<f32>],
1229 out: &mut [f32],
1230) -> bool {
1231 match backend() {
1232 #[cfg(feature = "gpu")]
1233 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1234 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1235 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1236 ),
1237 #[allow(unused_variables)]
1238 _ => false,
1239 }
1240}
1241
1242pub struct GraphW<'a> {
1246 pub idx: usize,
1247 pub kind: u8,
1248 pub row_scale: &'a [f32],
1249 pub data: &'a [f32],
1250}
1251
1252pub enum GraphAttn<'a> {
1255 Full {
1256 wq: GraphW<'a>,
1257 wk: GraphW<'a>,
1258 wv: GraphW<'a>,
1259 wo: GraphW<'a>,
1260 q_norm: Option<&'a [f32]>,
1261 k_norm: Option<&'a [f32]>,
1262 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1264 output_gate: bool,
1267 cpu_k: &'a [Vec<f32>],
1268 cpu_v: &'a [Vec<f32>],
1269 },
1270 Gdn {
1271 qkv: GraphW<'a>,
1272 z: GraphW<'a>,
1273 a: GraphW<'a>,
1274 b: GraphW<'a>,
1275 out: GraphW<'a>,
1276 conv1d: &'a [f32],
1277 a_log: &'a [f32],
1278 dt_bias: &'a [f32],
1279 norm: &'a [f32],
1280 nv: usize,
1281 nk: usize,
1282 dk: usize,
1283 dv: usize,
1284 kk: usize,
1285 cpu_state: &'a [f32],
1290 },
1291 ShortConv {
1298 inp: GraphW<'a>,
1300 out: GraphW<'a>,
1302 taps: &'a [f32],
1305 kernel: usize,
1306 cpu_state: &'a [f32],
1310 },
1311}
1312
1313pub struct GraphLayer<'a> {
1315 pub input_norm: &'a [f32],
1316 pub attn: GraphAttn<'a>,
1317 pub post_norm: &'a [f32],
1318 pub ffn: GraphFfn<'a>,
1319}
1320
1321pub enum GraphFfn<'a> {
1326 Dense {
1327 gate: GraphW<'a>,
1328 up: GraphW<'a>,
1329 down: GraphW<'a>,
1330 },
1331 Moe {
1332 router: GraphW<'a>,
1334 shared_gate: GraphW<'a>,
1336 experts: Vec<(usize, usize, usize)>,
1340 n_exp: usize,
1342 top_k: usize,
1343 inter: usize,
1344 norm_topk: bool,
1345 q4tp: bool,
1351 gu_q2: bool,
1355 sigmoid: bool,
1359 bias: Option<&'a [f32]>,
1362 has_shared: bool,
1366 },
1367}
1368
1369#[allow(clippy::too_many_arguments)]
1374pub fn forward_token_graph(
1375 model: &Arc<CmfModel>,
1376 kv_id: u64,
1377 layers: &[GraphLayer],
1378 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1381 o1_epoch: u64,
1382 invf: &[f32],
1383 h: &mut [f32],
1384 nh: usize,
1385 nkv: usize,
1386 hd: usize,
1387 rd: usize,
1388 hidden: usize,
1389 inter: usize,
1390 position: usize,
1391 cap: usize,
1392 gemma: bool,
1393 eps: f32,
1394 lm_head: Option<(&GraphW, usize)>,
1395 final_norm: &[f32],
1396 logits: &mut Vec<f32>,
1397 loop_norm_at: &[usize],
1398 steps: usize,
1399 embed: Option<(&GraphW, usize, f32)>,
1400 ids_out: Option<&mut Vec<u32>>,
1401 layers_run: Option<&mut usize>,
1404 layer_base: usize,
1408 hidden_too: bool,
1410) -> bool {
1411 match backend() {
1412 #[cfg(feature = "gpu")]
1413 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1414 model,
1415 kv_id,
1416 layers,
1417 o1,
1418 o1_epoch,
1419 invf,
1420 h,
1421 nh,
1422 nkv,
1423 hd,
1424 rd,
1425 hidden,
1426 inter,
1427 position,
1428 cap,
1429 gemma,
1430 eps,
1431 lm_head,
1432 final_norm,
1433 logits,
1434 loop_norm_at,
1435 steps,
1436 embed,
1437 ids_out,
1438 layers_run,
1439 layer_base,
1440 hidden_too,
1441 ),
1442 #[allow(unused_variables)]
1443 _ => {
1444 let _ = (
1445 lm_head,
1446 final_norm,
1447 logits,
1448 loop_norm_at,
1449 layers_run,
1450 layer_base,
1451 hidden_too,
1452 );
1453 false
1454 }
1455 }
1456}
1457
1458pub struct SpecTail<'a> {
1462 pub lm: GraphW<'a>,
1463 pub lm_rows: usize,
1464 pub final_norm: &'a [f32],
1465 pub logits_out: &'a mut Vec<f32>,
1466}
1467
1468#[allow(clippy::too_many_arguments)]
1472pub fn forward_batch_graph(
1473 model: &Arc<CmfModel>,
1474 kv_id: u64,
1475 layers: &[GraphLayer],
1476 invf: &[f32],
1477 h: &mut [f32],
1478 nh: usize,
1479 nkv: usize,
1480 hd: usize,
1481 rd: usize,
1482 hidden: usize,
1483 inter: usize,
1484 positions: &[usize],
1485 cap: usize,
1486 gemma: bool,
1487 eps: f32,
1488 k: usize,
1489 spec: Option<SpecTail<'_>>,
1490) -> bool {
1491 match backend() {
1492 #[cfg(feature = "gpu")]
1493 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1494 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1495 eps, k, spec,
1496 ),
1497 #[allow(unreachable_patterns)]
1498 _ => {
1499 let _ = spec;
1500 false
1501 }
1502 }
1503}
1504
1505pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1508 #[cfg(feature = "gpu")]
1509 if backend() == Backend::Wgpu {
1510 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1511 }
1512 #[allow(unreachable_code)]
1513 {
1514 let _ = (kv_id, slot);
1515 false
1516 }
1517}
1518
1519pub fn graph_kv_reset(_kv_id: u64) {
1521 #[cfg(feature = "gpu")]
1522 if backend() == Backend::Wgpu {
1523 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1524 }
1525}
1526
1527pub fn q1t_matvec(
1531 model: &Arc<CmfModel>,
1532 idx: usize,
1533 xs: &[f32],
1534 rows: usize,
1535 cols: usize,
1536 out: &mut [f32],
1537) -> bool {
1538 match backend() {
1539 #[cfg(target_os = "macos")]
1540 Backend::Metal => {
1541 if metal_q1t_enabled() {
1542 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1543 } else {
1544 false
1545 }
1546 }
1547 #[cfg(feature = "gpu")]
1548 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1549 Backend::None => false,
1550 }
1551}
1552
1553#[allow(unused_variables)]
1556pub fn q4b_matvec(
1557 model: &Arc<CmfModel>,
1558 idx: usize,
1559 xs: &[f32],
1560 rows: usize,
1561 cols: usize,
1562 out: &mut [f32],
1563) -> bool {
1564 match backend() {
1565 #[cfg(target_os = "macos")]
1566 Backend::Metal => false,
1567 #[cfg(feature = "gpu")]
1568 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1569 Backend::None => false,
1570 }
1571}
1572
1573pub fn q1t_matmat(
1576 model: &Arc<CmfModel>,
1577 idx: usize,
1578 xs: &[f32],
1579 b: usize,
1580 rows: usize,
1581 cols: usize,
1582 out: &mut [f32],
1583) -> bool {
1584 match backend() {
1585 #[cfg(target_os = "macos")]
1586 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1590 #[cfg(feature = "gpu")]
1591 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1592 Backend::None => false,
1593 }
1594}
1595
1596#[cfg(target_os = "macos")]
1600pub(crate) fn metal_q1t_enabled() -> bool {
1601 std::env::var("CMF_METAL_Q1T")
1602 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1603 .unwrap_or(true)
1604}
1605
1606pub fn q1_matmat(
1608 model: &Arc<CmfModel>,
1609 idx: usize,
1610 xs: &[f32],
1611 b: usize,
1612 rows: usize,
1613 cols: usize,
1614 out: &mut [f32],
1615) -> bool {
1616 match backend() {
1617 #[cfg(feature = "gpu")]
1618 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1619 #[allow(unused_variables)]
1620 _ => false,
1621 }
1622}
1623
1624static MM_KILL: AtomicBool = AtomicBool::new(false);
1629pub(crate) fn mm_killed() -> bool {
1630 MM_KILL.load(Ordering::Relaxed)
1631}
1632pub(crate) fn mm_kill() {
1633 MM_KILL.store(true, Ordering::Relaxed);
1634}
1635
1636static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1643const MM_STRIKES_TO_KILL: u32 = 3;
1644static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1651
1652pub fn mm_kill_arm(on: bool) {
1655 MM_ARMED.store(on, Ordering::Relaxed);
1656 if on {
1657 MM_STRIKES.store(0, Ordering::Relaxed);
1658 }
1659}
1660
1661pub(crate) fn mm_budget_check(
1668 what: &str,
1669 el: std::time::Duration,
1670 budget: std::time::Duration,
1671 exempt: bool,
1672) {
1673 if el <= budget {
1674 MM_STRIKES.store(0, Ordering::Relaxed);
1675 return;
1676 }
1677 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1678 return;
1679 }
1680 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1681 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1682 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1683 if !on {
1684 tracing::info!(
1685 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1686 );
1687 return;
1688 }
1689 if n >= MM_STRIKES_TO_KILL {
1690 tracing::warn!(
1691 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1692 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1693 );
1694 mm_kill();
1695 } else {
1696 tracing::info!(
1697 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1698 );
1699 }
1700}
1701
1702#[allow(unused_variables, clippy::too_many_arguments)]
1707pub fn chunk_attend(
1708 q: &[f32],
1709 k: &[&[f32]],
1710 v: &[&[f32]],
1711 b: usize,
1712 s0: usize,
1713 nh: usize,
1714 nkv: usize,
1715 hd: usize,
1716 scale: f32,
1717 out: &mut [f32],
1718) -> bool {
1719 match backend() {
1720 #[cfg(feature = "gpu")]
1721 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1722 #[allow(unreachable_patterns)]
1723 _ => false,
1724 }
1725}
1726
1727#[allow(unused_variables, clippy::too_many_arguments)]
1731pub fn q4t_qkv(
1732 model: &Arc<CmfModel>,
1733 wq: usize,
1734 wk: usize,
1735 wv: usize,
1736 xs: &[f32],
1737 b: usize,
1738 cols: usize,
1739 rq: usize,
1740 rk: usize,
1741 rv: usize,
1742 out: &mut [f32],
1743) -> bool {
1744 match backend() {
1745 #[cfg(feature = "gpu")]
1746 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1747 #[allow(unreachable_patterns)]
1748 _ => false,
1749 }
1750}
1751
1752#[allow(unused_variables, clippy::too_many_arguments)]
1754#[allow(clippy::too_many_arguments, unused_variables)]
1758pub fn q4tp_ffn_packed(
1759 model: &Arc<CmfModel>,
1760 w1: usize,
1761 w2: usize,
1762 xs: &[f32],
1763 b: usize,
1764 hidden: usize,
1765 inter: usize,
1766 bias: Option<&[f32]>,
1767 out: &mut [f32],
1768) -> bool {
1769 match backend() {
1770 #[cfg(feature = "gpu")]
1771 Backend::Wgpu => {
1772 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1773 }
1774 #[allow(unreachable_patterns)]
1775 _ => false,
1776 }
1777}
1778
1779pub fn q4tp_ffn(
1780 model: &Arc<CmfModel>,
1781 w1: usize,
1782 w3: usize,
1783 w2: usize,
1784 xs: &[f32],
1785 b: usize,
1786 hidden: usize,
1787 inter: usize,
1788 out: &mut [f32],
1789) -> bool {
1790 match backend() {
1791 #[cfg(target_os = "macos")]
1792 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1793 #[cfg(feature = "gpu")]
1794 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1795 #[allow(unreachable_patterns)]
1796 _ => false,
1797 }
1798}
1799
1800pub fn q4t_ffn(
1801 model: &Arc<CmfModel>,
1802 w1: usize,
1803 w3: usize,
1804 w2: usize,
1805 xs: &[f32],
1806 b: usize,
1807 hidden: usize,
1808 inter: usize,
1809 out: &mut [f32],
1810) -> bool {
1811 match backend() {
1812 #[cfg(target_os = "macos")]
1813 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1814 #[cfg(feature = "gpu")]
1815 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1816 #[allow(unreachable_patterns)]
1817 _ => false,
1818 }
1819}
1820
1821pub struct DitBlockArgs<'a> {
1826 pub n: usize,
1827 pub hidden: usize,
1828 pub inter: usize,
1829 pub nh: usize,
1830 pub nkv: usize,
1831 pub hd: usize,
1832 pub eps: f32,
1833 pub rope_cos: &'a [f32],
1834 pub rope_sin: &'a [f32],
1835 pub norm1: &'a [f32],
1836 pub norm2: &'a [f32],
1837 pub ffn_norm1: &'a [f32],
1838 pub ffn_norm2: &'a [f32],
1839 pub norm_q: &'a [f32],
1840 pub norm_k: &'a [f32],
1841 pub s_msa: &'a [f32],
1842 pub gate_msa: &'a [f32],
1843 pub s_mlp: &'a [f32],
1844 pub gate_mlp: &'a [f32],
1845 pub wq: usize,
1846 pub wk: usize,
1847 pub wv: usize,
1848 pub wo: usize,
1849 pub w1: usize,
1850 pub w3: usize,
1851 pub w2: usize,
1852 pub q4tp: bool,
1856 pub resident_in: bool,
1859 pub resident_out: bool,
1863}
1864
1865pub fn dit_chain_supported() -> bool {
1869 #[cfg(feature = "gpu")]
1870 {
1871 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1872 }
1873 #[allow(unreachable_code)]
1874 false
1875}
1876
1877pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1880 #[cfg(feature = "gpu")]
1881 {
1882 if matches!(backend(), Backend::Wgpu) {
1883 return crate::gpu_wgpu::dit_state_fetch(_x);
1884 }
1885 }
1886 false
1887}
1888
1889#[allow(unused_variables)]
1893#[allow(unused_variables, clippy::too_many_arguments)]
1897pub fn dit_qkv(
1898 model: &Arc<CmfModel>,
1899 wq: usize,
1900 wk: usize,
1901 wv: usize,
1902 xs: &[f32],
1903 b: usize,
1904 hidden: usize,
1905 qrows: usize,
1906 kvrows: usize,
1907 q_out: &mut [f32],
1908 k_out: &mut [f32],
1909 v_out: &mut [f32],
1910) -> bool {
1911 match backend() {
1912 #[cfg(feature = "gpu")]
1913 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1914 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1915 ),
1916 #[allow(unreachable_patterns)]
1917 _ => false,
1918 }
1919}
1920
1921pub fn fused_dit_block_available() -> bool {
1925 #[cfg(target_os = "macos")]
1926 {
1927 matches!(backend(), Backend::Metal) && fused_block_trusted()
1928 }
1929 #[cfg(not(target_os = "macos"))]
1930 {
1931 false
1932 }
1933}
1934
1935pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1936 dit_block_seg(model, a, &[a.n], x)
1937}
1938
1939pub fn dit_block_seg(
1943 model: &Arc<CmfModel>,
1944 a: &DitBlockArgs,
1945 segs: &[usize],
1946 x: &mut [f32],
1947) -> bool {
1948 match backend() {
1949 #[cfg(target_os = "macos")]
1950 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1951 #[cfg(feature = "gpu")]
1958 Backend::Wgpu
1959 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1960 Some("0") => false,
1961 Some(_) => true,
1962 None => crate::gpu_wgpu::discrete_active(),
1963 } =>
1964 {
1965 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1966 }
1967 #[allow(unreachable_patterns)]
1968 _ => false,
1969 }
1970}
1971
1972pub struct VaeResnetArgs<'a> {
1976 pub groups: usize,
1977 pub ic: usize,
1978 pub oc: usize,
1979 pub h: usize,
1980 pub w: usize,
1981 pub n1w: &'a [f32],
1982 pub n1b: &'a [f32],
1983 pub c1w: &'a [f32],
1984 pub c1b: &'a [f32],
1985 pub c1k: usize,
1986 pub n2w: &'a [f32],
1987 pub n2b: &'a [f32],
1988 pub c2w: &'a [f32],
1989 pub c2b: &'a [f32],
1990 pub c2k: usize,
1991 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1992}
1993
1994#[allow(unused_variables)]
1997pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1998 match backend() {
1999 #[cfg(target_os = "macos")]
2000 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2001 _ => false,
2002 }
2003}
2004
2005#[allow(unused_variables, clippy::too_many_arguments)]
2008pub fn vae_upsample_conv(
2009 w: &[f32],
2010 bias: &[f32],
2011 x: &[f32],
2012 ic: usize,
2013 oc: usize,
2014 h: usize,
2015 w_img: usize,
2016 k: usize,
2017 out: &mut [f32],
2018) -> bool {
2019 match backend() {
2020 #[cfg(target_os = "macos")]
2021 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2022 #[cfg(feature = "gpu")]
2023 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2024 #[allow(unreachable_patterns)]
2025 _ => false,
2026 }
2027}
2028
2029#[allow(unused_variables, clippy::too_many_arguments)]
2032pub fn vae_conv2d(
2033 w: &[f32],
2034 bias: &[f32],
2035 x: &[f32],
2036 ic: usize,
2037 oc: usize,
2038 h: usize,
2039 w_img: usize,
2040 k: usize,
2041 out: &mut [f32],
2042) -> bool {
2043 match backend() {
2044 #[cfg(target_os = "macos")]
2045 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2046 #[cfg(feature = "gpu")]
2047 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2048 #[allow(unreachable_patterns)]
2049 _ => false,
2050 }
2051}
2052
2053#[allow(unused_variables, clippy::too_many_arguments)]
2057#[allow(unused_variables)]
2061#[allow(clippy::too_many_arguments)]
2062#[allow(clippy::too_many_arguments, unused_variables)]
2065pub fn dit_qkv_attention(
2066 model: &Arc<CmfModel>,
2067 qkv_idx: usize,
2068 xn: &[f32],
2069 n: usize,
2070 hidden: usize,
2071 nh: usize,
2072 hd: usize,
2073 scale: f32,
2074 nr: (&[f32], &[f32], &[f32], f32),
2075 out: &mut [f32],
2076) -> bool {
2077 match backend() {
2078 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2079 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2080 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2081 ),
2082 #[allow(unreachable_patterns)]
2083 _ => false,
2084 }
2085}
2086
2087#[allow(clippy::too_many_arguments)]
2090pub fn dit_qkv_attn_out(
2091 model: &Arc<CmfModel>,
2092 qkv_idx: usize,
2093 out_idx: usize,
2094 xn: &[f32],
2095 n: usize,
2096 hidden: usize,
2097 nh: usize,
2098 hd: usize,
2099 scale: f32,
2100 nr: (&[f32], &[f32], &[f32], f32),
2101 proj: &mut [f32],
2102) -> bool {
2103 match backend() {
2104 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2105 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2106 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2107 ),
2108 #[allow(unreachable_patterns)]
2109 _ => false,
2110 }
2111}
2112
2113#[allow(clippy::too_many_arguments)]
2115pub fn vae_qkv_attn_out(
2116 model: &Arc<CmfModel>,
2117 qkv_idx: usize,
2118 out_idx: usize,
2119 xn: &[f32],
2120 n: usize,
2121 dim: usize,
2122 nh: usize,
2123 hd: usize,
2124 scale: f32,
2125 angles: &[f32],
2126 eps: f32,
2127 qkv_bias: &[f32],
2128 proj: &mut [f32],
2129) -> bool {
2130 match backend() {
2131 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2132 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2133 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2134 ),
2135 #[allow(unreachable_patterns)]
2136 _ => false,
2137 }
2138}
2139
2140#[allow(clippy::too_many_arguments)]
2141pub fn vae_attention_packed(
2142 qkv: &[f32],
2143 nh: usize,
2144 n: usize,
2145 hd: usize,
2146 scale: f32,
2147 angles: &[f32],
2148 eps: f32,
2149 out: &mut [f32],
2150) -> bool {
2151 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2152}
2153
2154#[allow(clippy::too_many_arguments)]
2155pub fn vae_attention_packed_layout(
2156 qkv: &[f32],
2157 nh: usize,
2158 n: usize,
2159 hd: usize,
2160 scale: f32,
2161 angles: &[f32],
2162 eps: f32,
2163 out: &mut [f32],
2164 layout: u32,
2165) -> bool {
2166 match backend() {
2167 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2168 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2169 qkv, nh, n, hd, scale, angles, eps, out, layout,
2170 ),
2171 #[allow(unreachable_patterns)]
2172 _ => false,
2173 }
2174}
2175
2176#[allow(clippy::too_many_arguments)]
2177pub fn dit_split_only(
2178 qkv: &[f32],
2179 nh: usize,
2180 n: usize,
2181 hd: usize,
2182 layout: u32,
2183 norm: Option<(&[f32], f32)>,
2184 out_q: &mut [f32],
2185) -> bool {
2186 match backend() {
2187 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2188 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2189 #[allow(unreachable_patterns)]
2190 _ => false,
2191 }
2192}
2193
2194pub fn gemm_nt_f32_transient(
2202 x: &[f32],
2203 w: &[f32],
2204 y: &mut [f32],
2205 n: usize,
2206 k: usize,
2207 m: usize,
2208) -> bool {
2209 match backend() {
2210 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2211 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2212 #[allow(unreachable_patterns)]
2213 _ => false,
2214 }
2215}
2216
2217pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2218 match backend() {
2219 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2220 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2221 #[allow(unreachable_patterns)]
2222 _ => false,
2223 }
2224}
2225
2226#[allow(clippy::too_many_arguments)]
2229pub fn music3_ffn(
2230 model: &std::sync::Arc<CmfModel>,
2231 idx_in: usize,
2232 idx_out: usize,
2233 h: &[f32],
2234 bias_in: &[f32],
2235 n: usize,
2236 hs: usize,
2237 inter: usize,
2238 out: &mut [f32],
2239) -> bool {
2240 match backend() {
2241 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2242 Backend::Wgpu => {
2243 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2244 }
2245 #[allow(unreachable_patterns)]
2246 _ => false,
2247 }
2248}
2249
2250#[allow(clippy::too_many_arguments)]
2254pub fn conv1d_gemm(
2255 x: &[f32],
2256 w: &[f32],
2257 ic: usize,
2258 oc: usize,
2259 n: usize,
2260 k: usize,
2261 pad: usize,
2262 dil: usize,
2263 out_n: usize,
2264 yt: &mut [f32],
2265) -> bool {
2266 match backend() {
2267 #[cfg(target_os = "macos")]
2268 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2269 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2270 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2271 #[allow(unreachable_patterns)]
2272 _ => false,
2273 }
2274}
2275
2276#[allow(clippy::too_many_arguments)]
2278pub fn vae_conv2d_coop(
2279 w: &[f32],
2280 bias: Option<&[f32]>,
2281 x: &[f32],
2282 ic: usize,
2283 oc: usize,
2284 h: usize,
2285 wi: usize,
2286 k: usize,
2287 out: &mut [f32],
2288) -> bool {
2289 match backend() {
2290 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2291 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2292 #[allow(unreachable_patterns)]
2293 _ => false,
2294 }
2295}
2296
2297pub fn dit_attention_packed(
2298 qkv: &[f32],
2299 nh: usize,
2300 n: usize,
2301 hd: usize,
2302 scale: f32,
2303 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2306 out: &mut [f32],
2307) -> bool {
2308 match backend() {
2309 #[cfg(feature = "gpu")]
2316 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2317 #[allow(unreachable_patterns)]
2318 _ => false,
2319 }
2320}
2321
2322pub fn dit_attention_packed_available() -> bool {
2330 #[allow(unreachable_patterns)]
2331 match backend() {
2332 #[cfg(feature = "gpu")]
2333 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2334 _ => false,
2335 }
2336}
2337
2338pub fn dit_attention(
2339 qh: &[f32],
2340 kh: &[f32],
2341 vh: &[f32],
2342 nh: usize,
2343 nkv: usize,
2344 n: usize,
2345 hd: usize,
2346 scale: f32,
2347 out: &mut [f32],
2348) -> bool {
2349 match backend() {
2350 #[cfg(target_os = "macos")]
2351 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2352 #[cfg(feature = "gpu")]
2353 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2354 #[allow(unreachable_patterns)]
2355 _ => false,
2356 }
2357}
2358
2359#[allow(unused_variables)]
2364pub fn q4tp_matmat(
2365 model: &Arc<CmfModel>,
2366 idx: usize,
2367 xs: &[f32],
2368 b: usize,
2369 rows: usize,
2370 cols: usize,
2371 out: &mut [f32],
2372) -> bool {
2373 match backend() {
2374 #[cfg(target_os = "macos")]
2375 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2376 #[cfg(feature = "gpu")]
2377 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2378 #[allow(unreachable_patterns)]
2379 _ => false,
2380 }
2381}
2382
2383pub fn q2tp_matmat(
2386 model: &Arc<CmfModel>,
2387 idx: usize,
2388 xs: &[f32],
2389 b: usize,
2390 rows: usize,
2391 cols: usize,
2392 out: &mut [f32],
2393) -> bool {
2394 match backend() {
2395 #[cfg(feature = "gpu")]
2396 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2397 #[allow(unreachable_patterns)]
2398 _ => false,
2399 }
2400}
2401
2402pub fn q4tp_matvec(
2407 model: &Arc<CmfModel>,
2408 idx: usize,
2409 xs: &[f32],
2410 rows: usize,
2411 cols: usize,
2412 out: &mut [f32],
2413) -> bool {
2414 match backend() {
2415 #[cfg(target_os = "macos")]
2416 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2417 #[cfg(feature = "gpu")]
2418 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2419 #[allow(unreachable_patterns)]
2420 _ => false,
2421 }
2422}
2423
2424pub fn q4t_matvec(
2430 model: &Arc<CmfModel>,
2431 idx: usize,
2432 xs: &[f32],
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_matvec_for_test(model, idx, xs, rows, cols, out),
2440 #[allow(unreachable_patterns)]
2441 _ => false,
2442 }
2443}
2444
2445pub fn q4t_matmat(
2446 model: &Arc<CmfModel>,
2447 idx: usize,
2448 xs: &[f32],
2449 b: usize,
2450 rows: usize,
2451 cols: usize,
2452 out: &mut [f32],
2453) -> bool {
2454 match backend() {
2455 #[cfg(target_os = "macos")]
2456 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2457 #[cfg(feature = "gpu")]
2458 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2459 #[allow(unreachable_patterns)]
2460 _ => false,
2461 }
2462}
2463
2464#[cfg(target_os = "macos")]
2466pub use crate::gpu_metal::{
2467 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2468 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2469};
2470
2471#[cfg(target_os = "macos")]
2473pub fn gdn_block(
2474 model: &Arc<CmfModel>,
2475 layers: &[GdnGpuLayer],
2476 states: &mut [&mut [f32]],
2477 cfg: &GdnGpuCfg,
2478 h: &mut [f32],
2479) -> bool {
2480 match backend() {
2481 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2482 _ => false,
2483 }
2484}
2485
2486#[allow(unused_variables)]
2488pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2489 match backend() {
2490 #[cfg(target_os = "macos")]
2491 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2492 #[cfg(feature = "gpu")]
2493 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2494 Backend::None => false,
2495 }
2496}
2497
2498#[allow(unused_variables)]
2500pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2501 match backend() {
2502 #[cfg(target_os = "macos")]
2503 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2504 #[cfg(feature = "gpu")]
2505 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2506 Backend::None => false,
2507 }
2508}
2509
2510static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2526static 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)];
2530
2531const GRAPH_RACE_SAMPLES: u32 = 4;
2533
2534static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2544
2545pub fn graph_mark_unsupported() {
2550 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2551 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2552 }
2553}
2554
2555pub fn graph_unsupported() -> bool {
2556 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2557}
2558
2559pub fn graph_unsupported_reset() {
2561 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2562}
2563
2564pub fn graph_race_begin_generation() {
2565 #[cfg(feature = "gpu")]
2570 {
2571 static FLUSHED: std::sync::Once = std::sync::Once::new();
2583 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2584 if FIRST.swap(false, Ordering::Relaxed) {
2585 } else {
2587 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2588 }
2589 }
2590 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2591 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2592 return;
2593 }
2594 let (gn, cn) = (
2595 GRAPH_N[1].load(Ordering::Relaxed),
2596 GRAPH_N[0].load(Ordering::Relaxed),
2597 );
2598 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2599 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2600 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2601 let verdict = if g_avg < c_avg { 1 } else { 2 };
2602 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2603 tracing::info!(
2604 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2605 g_avg as f64 / 1e6,
2606 c_avg as f64 / 1e6,
2607 if verdict == 1 { "graph" } else { "normal path" }
2608 );
2609 return;
2610 }
2611 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2612 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2613}
2614
2615pub fn graph_race_use_graph(trusted: bool) -> bool {
2619 if trusted {
2620 return true;
2621 }
2622 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2623 1 => true,
2624 2 => false,
2625 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2626 }
2627}
2628
2629pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2634 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2635 return false;
2636 }
2637 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2638 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2639 if !first || cn == 0 {
2640 return false;
2641 }
2642 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2643 let ns = dur.as_nanos() as u64;
2644 if ns > 1_000_000_000 && ns > 4 * c_avg {
2645 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2646 tracing::info!(
2647 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2648 ns as f64 / 1e6,
2649 c_avg as f64 / 1e6
2650 );
2651 return true;
2652 }
2653 false
2654}
2655
2656pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2660 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2661 return;
2662 }
2663 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2664 if tok == 0 {
2665 return;
2666 }
2667 let i = used_graph as usize;
2668 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2669 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2670}
2671
2672pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2682 #[inline]
2683 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2684 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2685 for c in chunks.chunks_exact(8) {
2686 h ^= u64::from_le_bytes(c.try_into().unwrap());
2687 h = h.wrapping_mul(0x100_0000_01b3);
2688 }
2689 for &b in tail {
2690 h ^= b as u64;
2691 h = h.wrapping_mul(0x100_0000_01b3);
2692 }
2693 h
2694 }
2695 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2696 if data.len() <= 4096 {
2697 return fnv(h, data);
2698 }
2699 let step = (data.len() - 64) / 63;
2700 for i in 0..64 {
2701 h = fnv(h, &data[i * step..i * step + 64]);
2702 }
2703 h
2704}
2705
2706pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2709 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2710 fp_bytes(bytes)
2711}
2712
2713#[cfg(test)]
2714mod fp_tests {
2715 use super::fp_bytes;
2716
2717 #[test]
2722 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2723 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2725 let h0 = fp_bytes(&base);
2726 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2727 let mut dense = base.clone();
2730 for b in dense.iter_mut() {
2731 *b = b.wrapping_add(1);
2732 }
2733 assert_ne!(
2734 h0,
2735 fp_bytes(&dense),
2736 "a fully different tensor slipped through"
2737 );
2738 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2741 let mut small = vec![3u8; 4096];
2744 let hs = fp_bytes(&small);
2745 small[2048] ^= 1;
2746 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2747 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2749 let v = vec![9u8; n];
2750 let _ = fp_bytes(&v); }
2752 }
2753}
2754
2755pub fn bake_release() {
2759 #[cfg(feature = "gpu")]
2760 crate::gpu_wgpu::bake_release();
2761}
2762
2763pub fn bake_precision_strict(on: bool) {
2767 #[cfg(feature = "gpu")]
2768 crate::gpu_wgpu::bake_precision_strict(on);
2769 #[cfg(not(feature = "gpu"))]
2770 let _ = on;
2771}
2772
2773pub fn hostprof_encode_done(t0: std::time::Instant) {
2779 use std::sync::atomic::{AtomicU64, Ordering};
2780 static ENC: 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 ENC.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: encode {:.2} ms/token over {n} tokens",
2790 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2791 );
2792 }
2793}
2794
2795pub fn hostprof_total(t0: std::time::Instant) {
2796 use std::sync::atomic::{AtomicU64, Ordering};
2797 static TOT: AtomicU64 = AtomicU64::new(0);
2798 static N: AtomicU64 = AtomicU64::new(0);
2799 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2800 return;
2801 }
2802 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2803 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2804 if n % 100 == 0 {
2805 eprintln!(
2806 "hostprof: total {:.2} ms/token over {n} tokens",
2807 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2808 );
2809 }
2810}
2811
2812pub fn stageprof(stage: u32, dt: std::time::Duration) {
2816 use std::sync::atomic::{AtomicU64, Ordering};
2817 static NS: [AtomicU64; 4] = [
2818 AtomicU64::new(0),
2819 AtomicU64::new(0),
2820 AtomicU64::new(0),
2821 AtomicU64::new(0),
2822 ];
2823 static N: AtomicU64 = AtomicU64::new(0);
2824 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2825 return;
2826 }
2827 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2828 if stage == 1 {
2829 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2830 if n % 200 == 0 {
2831 eprintln!(
2832 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2833 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2834 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2835 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2836 );
2837 }
2838 }
2839}
2840
2841pub fn weight_bytes_dispatched() -> u64 {
2844 let mut total = 0u64;
2845 #[cfg(target_os = "macos")]
2846 {
2847 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2848 }
2849 #[cfg(feature = "gpu")]
2850 {
2851 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2852 }
2853 total
2854}
2855
2856pub fn weight_bytes_by() -> [u64; 6] {
2859 #[cfg(target_os = "macos")]
2860 {
2861 let mut o = [0u64; 6];
2862 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2863 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2864 }
2865 return o;
2866 }
2867 #[allow(unreachable_code)]
2868 [0; 6]
2869}
2870
2871#[cfg(test)]
2872mod probe_warmup_tests {
2873 use super::*;
2874 use std::time::Duration;
2875
2876 fn ms(v: f64) -> Duration {
2877 Duration::from_nanos((v * 1e6) as u64)
2878 }
2879
2880 #[test]
2885 fn one_cold_first_sample_does_not_lose_the_class() {
2886 let p = Probe::new();
2887 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
2889 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
2890 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
2891 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
2892 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
2893 assert_eq!(
2894 p.state.load(Ordering::Relaxed),
2895 1,
2896 "the device is 3x faster once warm and must win"
2897 );
2898 }
2899
2900 #[test]
2904 fn the_warmup_is_spent_once_and_never_underflows() {
2905 let p = Probe::new();
2906 for _ in 0..8 {
2907 probe_record_into(&p, "matmat", None, true, ms(10.0));
2908 }
2909 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
2910 assert_eq!(
2911 p.gpu_n.load(Ordering::Relaxed),
2912 7,
2913 "one sample burned, the rest counted"
2914 );
2915 }
2916
2917 #[test]
2923 fn a_class_whose_device_always_declines_settles_on_the_host() {
2924 let c = OpClass::MatmatWide;
2928 let p = &PROBES[c as usize];
2929 p.state.store(0, Ordering::Relaxed);
2930 p.declines.store(0, Ordering::Relaxed);
2931 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
2932 probe_note_decline(c);
2933 }
2934 assert_eq!(
2935 p.state.load(Ordering::Relaxed),
2936 0,
2937 "one short of the limit is still a question, not an answer"
2938 );
2939 probe_note_decline(c);
2940 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
2941 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
2942 p.state.store(0, Ordering::Relaxed);
2943 p.declines.store(0, Ordering::Relaxed);
2944 }
2945
2946 #[test]
2949 fn a_slow_device_still_loses_after_the_warmup() {
2950 let p = Probe::new();
2951 for _ in 0..4 {
2952 probe_record_into(&p, "matvec", None, true, ms(40.0));
2953 }
2954 for _ in 0..4 {
2955 probe_record_into(&p, "matvec", None, false, ms(2.0));
2956 }
2957 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
2958 }
2959}