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_WARMUP: u32 = 1;
338
339struct Probe {
340 state: AtomicU8,
342 flip: AtomicU32,
343 gpu_ns: AtomicU64,
344 gpu_n: AtomicU32,
345 gpu_burn: AtomicU32,
358 cpu_ns: AtomicU64,
359 cpu_n: AtomicU32,
360 gpu_min: AtomicU64,
367 cpu_min: AtomicU64,
368}
369
370impl Probe {
371 const fn new() -> Self {
372 Self {
373 state: AtomicU8::new(0),
374 flip: AtomicU32::new(0),
375 gpu_ns: AtomicU64::new(0),
376 gpu_n: AtomicU32::new(0),
377 gpu_burn: AtomicU32::new(PROBE_WARMUP),
378 cpu_ns: AtomicU64::new(0),
379 cpu_n: AtomicU32::new(0),
380 gpu_min: AtomicU64::new(u64::MAX),
381 cpu_min: AtomicU64::new(u64::MAX),
382 }
383 }
384}
385
386static PROBES: [Probe; 7] = [
387 Probe::new(),
388 Probe::new(),
389 Probe::new(),
390 Probe::new(),
391 Probe::new(),
392 Probe::new(),
393 Probe::new(),
394];
395
396static TRUST_GPU: AtomicBool = AtomicBool::new(false);
403
404pub fn trust_gpu() -> GpuTrust {
406 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
407 GpuTrust(was)
408}
409
410pub struct GpuTrust(bool);
411
412impl Drop for GpuTrust {
413 fn drop(&mut self) {
414 TRUST_GPU.store(self.0, Ordering::Relaxed);
415 }
416}
417
418fn probe_on_for(c: OpClass) -> bool {
419 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
425 return false;
426 }
427 probe_on()
428}
429
430fn probe_on() -> bool {
431 static ON: OnceLock<bool> = OnceLock::new();
432 *ON.get_or_init(|| {
433 std::env::var("CMF_GPU_PROBE")
434 .map(|v| v != "0" && v != "off")
435 .unwrap_or(true)
436 })
437}
438
439pub fn q1_force() -> bool {
444 #[cfg(target_os = "macos")]
445 {
446 backend() == Backend::Metal
447 }
448 #[cfg(not(target_os = "macos"))]
449 {
450 false
451 }
452}
453
454pub fn fused_block_trusted() -> bool {
473 #[cfg(target_os = "macos")]
474 if backend() == Backend::Metal {
475 return true;
476 }
477 wgpu_graph_default()
478}
479
480pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
492 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
493 {
494 return crate::gpu_wgpu::weight_is_resident(model, idx);
495 }
496 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
497 {
498 let _ = (model, idx);
499 true
500 }
501}
502
503pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
504 if !weights_resident && probe_deciding(c) {
505 return ProbeArm::Gpu;
506 }
507 probe_arm(c)
508}
509
510pub fn probe_arm(c: OpClass) -> ProbeArm {
511 PROBE_COLD.with(|f| f.set(false));
516 if !probe_on_for(c) {
517 return ProbeArm::Gpu;
518 }
519 probe_cache_load();
520 let p = &PROBES[c as usize];
521 match p.state.load(Ordering::Relaxed) {
522 1 => ProbeArm::Gpu,
523 2 => ProbeArm::Cpu,
524 _ => {
525 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
526 ProbeArm::Gpu
527 } else {
528 ProbeArm::CpuTimed
529 }
530 }
531 }
532}
533
534pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
537 probe_record_into(&PROBES[c as usize], CLASS_NAMES[c as usize], Some(c), gpu, dur)
538}
539
540fn probe_record_into(
543 p: &Probe,
544 class_name: &str,
545 cache: Option<OpClass>,
546 gpu: bool,
547 dur: std::time::Duration,
548) {
549 if p.state.load(Ordering::Relaxed) != 0 {
550 return;
551 }
552 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
553 return; }
555 if gpu {
556 let left = p.gpu_burn.load(Ordering::Relaxed);
560 if left > 0 {
561 p.gpu_burn.store(left - 1, Ordering::Relaxed);
562 return; }
564 }
565 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
566 if gpu {
567 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
568 p.gpu_n.fetch_add(1, Ordering::Relaxed);
569 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
570 } else {
571 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
572 p.cpu_n.fetch_add(1, Ordering::Relaxed);
573 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
574 }
575 let (gn, cn) = (
576 p.gpu_n.load(Ordering::Relaxed),
577 p.cpu_n.load(Ordering::Relaxed),
578 );
579 if gn >= 2 && cn >= 2 {
580 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
584 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
585 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
595 return;
596 }
597 let winner = if g <= cp { 1 } else { 2 };
598 if p.state
599 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
600 .is_ok()
601 {
602 tracing::info!(
603 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
604 class_name,
605 g / 1e6,
606 cp / 1e6,
607 if winner == 1 { "gpu" } else { "cpu" },
608 );
609 if let Some(c) = cache {
610 probe_cache_store(c, winner);
611 }
612 }
613 }
614}
615
616pub fn probe_deciding(c: OpClass) -> bool {
619 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
620}
621
622#[allow(unused_variables)]
632pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
633 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
634 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
635 let resident = match backend() {
636 #[cfg(target_os = "macos")]
637 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
638 #[cfg(feature = "gpu")]
639 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
640 Backend::None => false,
641 };
642 if !resident && may_upload {
643 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
644 }
645 resident
646}
647
648#[cfg(test)]
650pub(crate) fn probe_reset() {
651 for p in &PROBES {
652 p.state.store(0, Ordering::Relaxed);
653 p.flip.store(0, Ordering::Relaxed);
654 p.gpu_ns.store(0, Ordering::Relaxed);
655 p.gpu_n.store(0, Ordering::Relaxed);
656 p.cpu_ns.store(0, Ordering::Relaxed);
657 p.cpu_n.store(0, Ordering::Relaxed);
658 }
659}
660
661#[cfg(test)]
662mod probe_tests {
663 use super::*;
664 use std::time::Duration;
665
666 #[test]
669 fn probe_alternates_discards_cold_and_decides() {
670 probe_reset();
671 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
673 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
674
675 probe_note_cold();
679 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
680 for _ in 0..PROBE_SAMPLES {
681 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
682 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
683 }
684 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
685
686 for _ in 0..PROBE_SAMPLES {
688 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
689 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
690 }
691 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
692
693 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
695 CPU_ONLY.with(|c| assert!(!c.get()));
696 cpu_scope(|| {
697 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
698 CPU_ONLY.with(|c| assert!(c.get()));
699 });
700 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
701 CPU_ONLY.with(|c| assert!(!c.get()));
702 probe_reset();
703 }
704
705 #[test]
706 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
707 let mine = probe_cache_key_named("gemm-nt");
719 let state = || {
720 PROBES[OpClass::GemmNt as usize]
721 .state
722 .load(Ordering::Relaxed)
723 };
724
725 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
727 assert_eq!(state(), 0);
728 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
730 assert_ne!(older, mine);
731 probe_cache_adopt(&format!("{older}\tgpu\n"));
732 assert_eq!(state(), 0);
733 probe_cache_adopt(&format!("{mine}\tcpu\n"));
735 assert_eq!(state(), 2);
736
737 PROBES[OpClass::GemmNt as usize]
738 .state
739 .store(0, Ordering::Relaxed);
740 }
741}
742
743pub const GPU_MIN_ROWS: usize = 65_536;
746
747pub fn min_rows() -> usize {
754 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
755 .ok()
756 .and_then(|v| v.parse().ok())
757 {
758 return v;
759 }
760 if discrete() { 4096 } else { GPU_MIN_ROWS }
761}
762
763pub fn discrete() -> bool {
765 match backend() {
766 #[cfg(feature = "gpu")]
767 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
768 #[cfg(target_os = "macos")]
769 Backend::Metal => false, Backend::None => false,
771 }
772}
773
774pub struct MoeJob<'a> {
778 pub gate: (usize, usize, usize, &'a [f32]),
779 pub up: (usize, usize, usize, &'a [f32]),
780 pub down: (usize, usize, usize, &'a [f32]),
781 pub xs_gate: Vec<f32>,
782 pub xs_up: Vec<f32>,
783 pub down_col: &'a [f32],
784 pub w: f32,
785 pub q1: bool,
788 pub q4t: bool,
791 pub q4tp: bool,
795 pub gu_q2: bool,
799 pub swiglu_limit: f32,
804}
805
806pub struct BatchJob<'a> {
808 pub idx: usize,
809 pub rows: usize,
810 pub cols: usize,
811 pub row_scale: &'a [f32],
812 pub xs: Vec<f32>,
813 pub layout: BatchLayout,
817}
818
819#[derive(Clone, Copy, PartialEq, Eq, Debug)]
822pub enum BatchLayout {
823 Q8,
824 Q1,
825 Q4t,
826 Q4tp,
827}
828
829#[derive(Clone, Copy, PartialEq, Eq)]
830enum Backend {
831 None,
832 #[cfg(target_os = "macos")]
833 Metal,
834 #[cfg(feature = "gpu")]
835 Wgpu,
836}
837
838fn backend() -> Backend {
839 #[cfg(feature = "gpu")]
840 if crate::gpu_wgpu::selected() {
841 return if crate::gpu_wgpu::enabled() {
842 Backend::Wgpu
843 } else {
844 Backend::None
845 };
846 }
847 #[cfg(target_os = "macos")]
848 if crate::gpu_metal::enabled() {
849 return Backend::Metal;
850 }
851 Backend::None
852}
853
854pub fn backend_available() -> bool {
860 #[cfg(target_os = "macos")]
861 {
862 true
864 }
865 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
866 {
867 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
868 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
869 }
870 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
871 {
872 false
873 }
874}
875
876static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
882
883pub fn pause_gpu() -> GpuPause {
885 GPU_PAUSED.store(true, Ordering::Relaxed);
886 GpuPause(())
887}
888
889pub struct GpuPause(());
890
891impl Drop for GpuPause {
892 fn drop(&mut self) {
893 GPU_PAUSED.store(false, Ordering::Relaxed);
894 }
895}
896
897pub fn enabled() -> bool {
898 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
899}
900
901pub fn wgpu_active() -> bool {
915 #[cfg(feature = "gpu")]
916 {
917 matches!(backend(), Backend::Wgpu)
918 }
919 #[cfg(not(feature = "gpu"))]
920 {
921 false
922 }
923}
924
925pub fn default_device() -> usize {
932 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
933 *D.get_or_init(|| {
934 std::env::var("CMF_GPU_ADAPTER")
935 .ok()
936 .and_then(|v| v.trim().parse::<usize>().ok())
937 .unwrap_or(0)
938 })
939}
940
941thread_local! {
942 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
943}
944
945pub fn current_device() -> usize {
947 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
948}
949
950pub fn set_current_device(i: usize) {
954 CUR_DEV.with(|c| c.set(Some(i)));
955}
956
957pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
959 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
960 let r = f();
961 CUR_DEV.with(|c| c.set(prev));
962 r
963}
964
965pub fn device_count() -> usize {
968 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
969 {
970 return crate::gpu_wgpu::adapter_count();
971 }
972 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
973 {
974 usize::from(backend_available())
975 }
976}
977
978pub fn vram_budget() -> u64 {
982 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
983 {
984 return crate::gpu_wgpu::device_vram_budget();
985 }
986 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
987 {
988 if backend_available() { u64::MAX } else { 0 }
989 }
990}
991
992pub fn upload_bytes() -> u64 {
996 #[cfg(feature = "gpu")]
997 {
998 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
999 }
1000 #[cfg(not(feature = "gpu"))]
1001 0
1002}
1003
1004#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1016pub enum GraphPhase {
1017 Prefill,
1018 Decode,
1019}
1020
1021pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1029 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1030 Some("0") => false,
1031 Some("prefill") => phase == GraphPhase::Prefill,
1032 Some(_) => true,
1033 None => {
1034 if wgpu_graph_default() {
1035 return true;
1036 }
1037 let _ = phase;
1042 false
1043 }
1044 }
1045}
1046
1047pub fn wgpu_graph_default() -> bool {
1048 #[cfg(feature = "gpu")]
1049 {
1050 matches!(backend(), Backend::Wgpu)
1056 && (crate::gpu_wgpu::discrete_active()
1057 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1058 }
1059 #[cfg(not(feature = "gpu"))]
1060 {
1061 false
1062 }
1063}
1064
1065#[allow(clippy::too_many_arguments, unused_variables)]
1067pub fn q8_matvec_range(
1068 model: &Arc<CmfModel>,
1069 idx: usize,
1070 row0: usize,
1071 row_scale: &[f32],
1072 xs: &[f32],
1073 rows: usize,
1074 cols: usize,
1075 out: &mut [f32],
1076) -> bool {
1077 match backend() {
1078 #[cfg(target_os = "macos")]
1079 Backend::Metal => {
1080 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1081 }
1082 #[cfg(feature = "gpu")]
1083 Backend::Wgpu => {
1084 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1085 }
1086 Backend::None => false,
1087 }
1088}
1089
1090#[allow(clippy::too_many_arguments, unused_variables)]
1093#[allow(clippy::too_many_arguments)]
1097pub fn q8_matmat_2f(
1098 model: &Arc<CmfModel>,
1099 idx: usize,
1100 row_scale: &[f32],
1101 col_field: &[f32],
1102 xs: &[f32],
1103 b: usize,
1104 rows: usize,
1105 cols: usize,
1106 out: &mut [f32],
1107) -> bool {
1108 #[allow(unreachable_patterns)]
1109 match backend() {
1110 #[cfg(feature = "gpu")]
1111 Backend::Wgpu => crate::gpu_wgpu::q8_matmat_2f(
1112 model, idx, row_scale, col_field, xs, b, rows, cols, out,
1113 ),
1114 _ => false,
1115 }
1116}
1117
1118pub fn q8_matmat(
1119 model: &Arc<CmfModel>,
1120 idx: usize,
1121 row_scale: &[f32],
1122 pre: &[f32],
1123 b: usize,
1124 rows: usize,
1125 cols: usize,
1126 out: &mut [f32],
1127) -> bool {
1128 match backend() {
1129 #[cfg(target_os = "macos")]
1130 Backend::Metal => {
1131 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1132 }
1133 #[cfg(feature = "gpu")]
1134 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1135 Backend::None => false,
1136 }
1137}
1138
1139#[allow(unused_variables)]
1142pub fn q1_matvec(
1143 model: &Arc<CmfModel>,
1144 idx: usize,
1145 xs: &[f32],
1146 rows: usize,
1147 cols: usize,
1148 out: &mut [f32],
1149) -> bool {
1150 match backend() {
1151 #[cfg(target_os = "macos")]
1152 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1153 #[cfg(feature = "gpu")]
1154 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1155 Backend::None => false,
1156 }
1157}
1158
1159#[allow(clippy::too_many_arguments)]
1163pub fn attn_dropin(
1164 model: &Arc<CmfModel>,
1165 kv_id: u64,
1166 layer: usize,
1167 normed: &[f32],
1168 wq_idx: usize,
1169 wk_idx: usize,
1170 wv_idx: usize,
1171 wo_idx: usize,
1172 q_norm: Option<&[f32]>,
1173 k_norm: Option<&[f32]>,
1174 invf: &[f32],
1175 nh: usize,
1176 nkv: usize,
1177 hd: usize,
1178 rd: usize,
1179 hidden: usize,
1180 pos: usize,
1181 cap: usize,
1182 gemma: bool,
1183 eps: f32,
1184 cpu_k: &[Vec<f32>],
1185 cpu_v: &[Vec<f32>],
1186 out: &mut [f32],
1187) -> bool {
1188 match backend() {
1189 #[cfg(feature = "gpu")]
1190 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1191 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1192 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1193 ),
1194 #[allow(unused_variables)]
1195 _ => false,
1196 }
1197}
1198
1199pub struct GraphW<'a> {
1203 pub idx: usize,
1204 pub kind: u8,
1205 pub row_scale: &'a [f32],
1206 pub data: &'a [f32],
1207}
1208
1209pub enum GraphAttn<'a> {
1212 Full {
1213 wq: GraphW<'a>,
1214 wk: GraphW<'a>,
1215 wv: GraphW<'a>,
1216 wo: GraphW<'a>,
1217 q_norm: Option<&'a [f32]>,
1218 k_norm: Option<&'a [f32]>,
1219 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1221 output_gate: bool,
1224 cpu_k: &'a [Vec<f32>],
1225 cpu_v: &'a [Vec<f32>],
1226 },
1227 Gdn {
1228 qkv: GraphW<'a>,
1229 z: GraphW<'a>,
1230 a: GraphW<'a>,
1231 b: GraphW<'a>,
1232 out: GraphW<'a>,
1233 conv1d: &'a [f32],
1234 a_log: &'a [f32],
1235 dt_bias: &'a [f32],
1236 norm: &'a [f32],
1237 nv: usize,
1238 nk: usize,
1239 dk: usize,
1240 dv: usize,
1241 kk: usize,
1242 cpu_state: &'a [f32],
1247 },
1248}
1249
1250pub struct GraphLayer<'a> {
1252 pub input_norm: &'a [f32],
1253 pub attn: GraphAttn<'a>,
1254 pub post_norm: &'a [f32],
1255 pub ffn: GraphFfn<'a>,
1256}
1257
1258pub enum GraphFfn<'a> {
1263 Dense {
1264 gate: GraphW<'a>,
1265 up: GraphW<'a>,
1266 down: GraphW<'a>,
1267 },
1268 Moe {
1269 router: GraphW<'a>,
1271 shared_gate: GraphW<'a>,
1273 experts: Vec<(usize, usize, usize)>,
1277 n_exp: usize,
1279 top_k: usize,
1280 inter: usize,
1281 norm_topk: bool,
1282 q4tp: bool,
1288 gu_q2: bool,
1292 },
1293}
1294
1295#[allow(clippy::too_many_arguments)]
1300pub fn forward_token_graph(
1301 model: &Arc<CmfModel>,
1302 kv_id: u64,
1303 layers: &[GraphLayer],
1304 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1307 o1_epoch: u64,
1308 invf: &[f32],
1309 h: &mut [f32],
1310 nh: usize,
1311 nkv: usize,
1312 hd: usize,
1313 rd: usize,
1314 hidden: usize,
1315 inter: usize,
1316 position: usize,
1317 cap: usize,
1318 gemma: bool,
1319 eps: f32,
1320 lm_head: Option<(&GraphW, usize)>,
1321 final_norm: &[f32],
1322 logits: &mut Vec<f32>,
1323 loop_norm_at: &[usize],
1324 steps: usize,
1325 embed: Option<(&GraphW, usize, f32)>,
1326 ids_out: Option<&mut Vec<u32>>,
1327 layers_run: Option<&mut usize>,
1330 layer_base: usize,
1334 hidden_too: bool,
1336) -> bool {
1337 match backend() {
1338 #[cfg(feature = "gpu")]
1339 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1340 model,
1341 kv_id,
1342 layers,
1343 o1,
1344 o1_epoch,
1345 invf,
1346 h,
1347 nh,
1348 nkv,
1349 hd,
1350 rd,
1351 hidden,
1352 inter,
1353 position,
1354 cap,
1355 gemma,
1356 eps,
1357 lm_head,
1358 final_norm,
1359 logits,
1360 loop_norm_at,
1361 steps,
1362 embed,
1363 ids_out,
1364 layers_run,
1365 layer_base,
1366 hidden_too,
1367 ),
1368 #[allow(unused_variables)]
1369 _ => {
1370 let _ = (
1371 lm_head,
1372 final_norm,
1373 logits,
1374 loop_norm_at,
1375 layers_run,
1376 layer_base,
1377 hidden_too,
1378 );
1379 false
1380 }
1381 }
1382}
1383
1384pub struct SpecTail<'a> {
1388 pub lm: GraphW<'a>,
1389 pub lm_rows: usize,
1390 pub final_norm: &'a [f32],
1391 pub logits_out: &'a mut Vec<f32>,
1392}
1393
1394#[allow(clippy::too_many_arguments)]
1398pub fn forward_batch_graph(
1399 model: &Arc<CmfModel>,
1400 kv_id: u64,
1401 layers: &[GraphLayer],
1402 invf: &[f32],
1403 h: &mut [f32],
1404 nh: usize,
1405 nkv: usize,
1406 hd: usize,
1407 rd: usize,
1408 hidden: usize,
1409 inter: usize,
1410 positions: &[usize],
1411 cap: usize,
1412 gemma: bool,
1413 eps: f32,
1414 k: usize,
1415 spec: Option<SpecTail<'_>>,
1416) -> bool {
1417 match backend() {
1418 #[cfg(feature = "gpu")]
1419 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1420 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1421 eps, k, spec,
1422 ),
1423 #[allow(unreachable_patterns)]
1424 _ => {
1425 let _ = spec;
1426 false
1427 }
1428 }
1429}
1430
1431pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1434 #[cfg(feature = "gpu")]
1435 if backend() == Backend::Wgpu {
1436 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1437 }
1438 #[allow(unreachable_code)]
1439 {
1440 let _ = (kv_id, slot);
1441 false
1442 }
1443}
1444
1445pub fn graph_kv_reset(_kv_id: u64) {
1447 #[cfg(feature = "gpu")]
1448 if backend() == Backend::Wgpu {
1449 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1450 }
1451}
1452
1453pub fn q1t_matvec(
1457 model: &Arc<CmfModel>,
1458 idx: usize,
1459 xs: &[f32],
1460 rows: usize,
1461 cols: usize,
1462 out: &mut [f32],
1463) -> bool {
1464 match backend() {
1465 #[cfg(target_os = "macos")]
1466 Backend::Metal => {
1467 if metal_q1t_enabled() {
1468 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1469 } else {
1470 false
1471 }
1472 }
1473 #[cfg(feature = "gpu")]
1474 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1475 Backend::None => false,
1476 }
1477}
1478
1479#[allow(unused_variables)]
1482pub fn q4b_matvec(
1483 model: &Arc<CmfModel>,
1484 idx: usize,
1485 xs: &[f32],
1486 rows: usize,
1487 cols: usize,
1488 out: &mut [f32],
1489) -> bool {
1490 match backend() {
1491 #[cfg(target_os = "macos")]
1492 Backend::Metal => false,
1493 #[cfg(feature = "gpu")]
1494 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1495 Backend::None => false,
1496 }
1497}
1498
1499pub fn q1t_matmat(
1502 model: &Arc<CmfModel>,
1503 idx: usize,
1504 xs: &[f32],
1505 b: usize,
1506 rows: usize,
1507 cols: usize,
1508 out: &mut [f32],
1509) -> bool {
1510 match backend() {
1511 #[cfg(target_os = "macos")]
1512 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1516 #[cfg(feature = "gpu")]
1517 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1518 Backend::None => false,
1519 }
1520}
1521
1522#[cfg(target_os = "macos")]
1526pub(crate) fn metal_q1t_enabled() -> bool {
1527 std::env::var("CMF_METAL_Q1T")
1528 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1529 .unwrap_or(true)
1530}
1531
1532pub fn q1_matmat(
1534 model: &Arc<CmfModel>,
1535 idx: usize,
1536 xs: &[f32],
1537 b: usize,
1538 rows: usize,
1539 cols: usize,
1540 out: &mut [f32],
1541) -> bool {
1542 match backend() {
1543 #[cfg(feature = "gpu")]
1544 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1545 #[allow(unused_variables)]
1546 _ => false,
1547 }
1548}
1549
1550static MM_KILL: AtomicBool = AtomicBool::new(false);
1555pub(crate) fn mm_killed() -> bool {
1556 MM_KILL.load(Ordering::Relaxed)
1557}
1558pub(crate) fn mm_kill() {
1559 MM_KILL.store(true, Ordering::Relaxed);
1560}
1561
1562static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1569const MM_STRIKES_TO_KILL: u32 = 3;
1570static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1577
1578pub fn mm_kill_arm(on: bool) {
1581 MM_ARMED.store(on, Ordering::Relaxed);
1582 if on {
1583 MM_STRIKES.store(0, Ordering::Relaxed);
1584 }
1585}
1586
1587pub(crate) fn mm_budget_check(
1594 what: &str,
1595 el: std::time::Duration,
1596 budget: std::time::Duration,
1597 exempt: bool,
1598) {
1599 if el <= budget {
1600 MM_STRIKES.store(0, Ordering::Relaxed);
1601 return;
1602 }
1603 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1604 return;
1605 }
1606 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1607 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1608 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1609 if !on {
1610 tracing::info!(
1611 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1612 );
1613 return;
1614 }
1615 if n >= MM_STRIKES_TO_KILL {
1616 tracing::warn!(
1617 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1618 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1619 );
1620 mm_kill();
1621 } else {
1622 tracing::info!(
1623 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1624 );
1625 }
1626}
1627
1628#[allow(unused_variables, clippy::too_many_arguments)]
1633pub fn chunk_attend(
1634 q: &[f32],
1635 k: &[&[f32]],
1636 v: &[&[f32]],
1637 b: usize,
1638 s0: usize,
1639 nh: usize,
1640 nkv: usize,
1641 hd: usize,
1642 scale: f32,
1643 out: &mut [f32],
1644) -> bool {
1645 match backend() {
1646 #[cfg(feature = "gpu")]
1647 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1648 #[allow(unreachable_patterns)]
1649 _ => false,
1650 }
1651}
1652
1653#[allow(unused_variables, clippy::too_many_arguments)]
1657pub fn q4t_qkv(
1658 model: &Arc<CmfModel>,
1659 wq: usize,
1660 wk: usize,
1661 wv: usize,
1662 xs: &[f32],
1663 b: usize,
1664 cols: usize,
1665 rq: usize,
1666 rk: usize,
1667 rv: usize,
1668 out: &mut [f32],
1669) -> bool {
1670 match backend() {
1671 #[cfg(feature = "gpu")]
1672 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1673 #[allow(unreachable_patterns)]
1674 _ => false,
1675 }
1676}
1677
1678#[allow(unused_variables, clippy::too_many_arguments)]
1680#[allow(clippy::too_many_arguments, unused_variables)]
1684pub fn q4tp_ffn_packed(
1685 model: &Arc<CmfModel>,
1686 w1: usize,
1687 w2: usize,
1688 xs: &[f32],
1689 b: usize,
1690 hidden: usize,
1691 inter: usize,
1692 bias: Option<&[f32]>,
1693 out: &mut [f32],
1694) -> bool {
1695 match backend() {
1696 #[cfg(feature = "gpu")]
1697 Backend::Wgpu => {
1698 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1699 }
1700 #[allow(unreachable_patterns)]
1701 _ => false,
1702 }
1703}
1704
1705pub fn q4tp_ffn(
1706 model: &Arc<CmfModel>,
1707 w1: usize,
1708 w3: usize,
1709 w2: usize,
1710 xs: &[f32],
1711 b: usize,
1712 hidden: usize,
1713 inter: usize,
1714 out: &mut [f32],
1715) -> bool {
1716 match backend() {
1717 #[cfg(target_os = "macos")]
1718 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1719 #[cfg(feature = "gpu")]
1720 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1721 #[allow(unreachable_patterns)]
1722 _ => false,
1723 }
1724}
1725
1726pub fn q4t_ffn(
1727 model: &Arc<CmfModel>,
1728 w1: usize,
1729 w3: usize,
1730 w2: usize,
1731 xs: &[f32],
1732 b: usize,
1733 hidden: usize,
1734 inter: usize,
1735 out: &mut [f32],
1736) -> bool {
1737 match backend() {
1738 #[cfg(target_os = "macos")]
1739 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1740 #[cfg(feature = "gpu")]
1741 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1742 #[allow(unreachable_patterns)]
1743 _ => false,
1744 }
1745}
1746
1747pub struct DitBlockArgs<'a> {
1752 pub n: usize,
1753 pub hidden: usize,
1754 pub inter: usize,
1755 pub nh: usize,
1756 pub nkv: usize,
1757 pub hd: usize,
1758 pub eps: f32,
1759 pub rope_cos: &'a [f32],
1760 pub rope_sin: &'a [f32],
1761 pub norm1: &'a [f32],
1762 pub norm2: &'a [f32],
1763 pub ffn_norm1: &'a [f32],
1764 pub ffn_norm2: &'a [f32],
1765 pub norm_q: &'a [f32],
1766 pub norm_k: &'a [f32],
1767 pub s_msa: &'a [f32],
1768 pub gate_msa: &'a [f32],
1769 pub s_mlp: &'a [f32],
1770 pub gate_mlp: &'a [f32],
1771 pub wq: usize,
1772 pub wk: usize,
1773 pub wv: usize,
1774 pub wo: usize,
1775 pub w1: usize,
1776 pub w3: usize,
1777 pub w2: usize,
1778 pub q4tp: bool,
1782 pub resident_in: bool,
1785 pub resident_out: bool,
1789}
1790
1791pub fn dit_chain_supported() -> bool {
1795 #[cfg(feature = "gpu")]
1796 {
1797 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1798 }
1799 #[allow(unreachable_code)]
1800 false
1801}
1802
1803pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1806 #[cfg(feature = "gpu")]
1807 {
1808 if matches!(backend(), Backend::Wgpu) {
1809 return crate::gpu_wgpu::dit_state_fetch(_x);
1810 }
1811 }
1812 false
1813}
1814
1815#[allow(unused_variables)]
1819#[allow(unused_variables, clippy::too_many_arguments)]
1823pub fn dit_qkv(
1824 model: &Arc<CmfModel>,
1825 wq: usize,
1826 wk: usize,
1827 wv: usize,
1828 xs: &[f32],
1829 b: usize,
1830 hidden: usize,
1831 qrows: usize,
1832 kvrows: usize,
1833 q_out: &mut [f32],
1834 k_out: &mut [f32],
1835 v_out: &mut [f32],
1836) -> bool {
1837 match backend() {
1838 #[cfg(feature = "gpu")]
1839 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1840 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1841 ),
1842 #[allow(unreachable_patterns)]
1843 _ => false,
1844 }
1845}
1846
1847pub fn fused_dit_block_available() -> bool {
1851 #[cfg(target_os = "macos")]
1852 {
1853 matches!(backend(), Backend::Metal) && fused_block_trusted()
1854 }
1855 #[cfg(not(target_os = "macos"))]
1856 {
1857 false
1858 }
1859}
1860
1861pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1862 dit_block_seg(model, a, &[a.n], x)
1863}
1864
1865pub fn dit_block_seg(
1869 model: &Arc<CmfModel>,
1870 a: &DitBlockArgs,
1871 segs: &[usize],
1872 x: &mut [f32],
1873) -> bool {
1874 match backend() {
1875 #[cfg(target_os = "macos")]
1876 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1877 #[cfg(feature = "gpu")]
1884 Backend::Wgpu
1885 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1886 Some("0") => false,
1887 Some(_) => true,
1888 None => crate::gpu_wgpu::discrete_active(),
1889 } =>
1890 {
1891 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1892 }
1893 #[allow(unreachable_patterns)]
1894 _ => false,
1895 }
1896}
1897
1898pub struct VaeResnetArgs<'a> {
1902 pub groups: usize,
1903 pub ic: usize,
1904 pub oc: usize,
1905 pub h: usize,
1906 pub w: usize,
1907 pub n1w: &'a [f32],
1908 pub n1b: &'a [f32],
1909 pub c1w: &'a [f32],
1910 pub c1b: &'a [f32],
1911 pub c1k: usize,
1912 pub n2w: &'a [f32],
1913 pub n2b: &'a [f32],
1914 pub c2w: &'a [f32],
1915 pub c2b: &'a [f32],
1916 pub c2k: usize,
1917 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1918}
1919
1920#[allow(unused_variables)]
1923pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1924 match backend() {
1925 #[cfg(target_os = "macos")]
1926 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1927 _ => false,
1928 }
1929}
1930
1931#[allow(unused_variables, clippy::too_many_arguments)]
1934pub fn vae_upsample_conv(
1935 w: &[f32],
1936 bias: &[f32],
1937 x: &[f32],
1938 ic: usize,
1939 oc: usize,
1940 h: usize,
1941 w_img: usize,
1942 k: usize,
1943 out: &mut [f32],
1944) -> bool {
1945 match backend() {
1946 #[cfg(target_os = "macos")]
1947 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1948 #[cfg(feature = "gpu")]
1949 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1950 #[allow(unreachable_patterns)]
1951 _ => false,
1952 }
1953}
1954
1955#[allow(unused_variables, clippy::too_many_arguments)]
1958pub fn vae_conv2d(
1959 w: &[f32],
1960 bias: &[f32],
1961 x: &[f32],
1962 ic: usize,
1963 oc: usize,
1964 h: usize,
1965 w_img: usize,
1966 k: usize,
1967 out: &mut [f32],
1968) -> bool {
1969 match backend() {
1970 #[cfg(target_os = "macos")]
1971 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1972 #[cfg(feature = "gpu")]
1973 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1974 #[allow(unreachable_patterns)]
1975 _ => false,
1976 }
1977}
1978
1979#[allow(unused_variables, clippy::too_many_arguments)]
1983#[allow(unused_variables)]
1987#[allow(clippy::too_many_arguments)]
1988#[allow(clippy::too_many_arguments, unused_variables)]
1991pub fn dit_qkv_attention(
1992 model: &Arc<CmfModel>,
1993 qkv_idx: usize,
1994 xn: &[f32],
1995 n: usize,
1996 hidden: usize,
1997 nh: usize,
1998 hd: usize,
1999 scale: f32,
2000 nr: (&[f32], &[f32], &[f32], f32),
2001 out: &mut [f32],
2002) -> bool {
2003 match backend() {
2004 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2005 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2006 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2007 ),
2008 #[allow(unreachable_patterns)]
2009 _ => false,
2010 }
2011}
2012
2013#[allow(clippy::too_many_arguments)]
2016pub fn dit_qkv_attn_out(
2017 model: &Arc<CmfModel>,
2018 qkv_idx: usize,
2019 out_idx: usize,
2020 xn: &[f32],
2021 n: usize,
2022 hidden: usize,
2023 nh: usize,
2024 hd: usize,
2025 scale: f32,
2026 nr: (&[f32], &[f32], &[f32], f32),
2027 proj: &mut [f32],
2028) -> bool {
2029 match backend() {
2030 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2031 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2032 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2033 ),
2034 #[allow(unreachable_patterns)]
2035 _ => false,
2036 }
2037}
2038
2039#[allow(clippy::too_many_arguments)]
2041pub fn vae_qkv_attn_out(
2042 model: &Arc<CmfModel>,
2043 qkv_idx: usize,
2044 out_idx: usize,
2045 xn: &[f32],
2046 n: usize,
2047 dim: usize,
2048 nh: usize,
2049 hd: usize,
2050 scale: f32,
2051 angles: &[f32],
2052 eps: f32,
2053 qkv_bias: &[f32],
2054 proj: &mut [f32],
2055) -> bool {
2056 match backend() {
2057 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2058 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2059 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2060 ),
2061 #[allow(unreachable_patterns)]
2062 _ => false,
2063 }
2064}
2065
2066#[allow(clippy::too_many_arguments)]
2067pub fn vae_attention_packed(
2068 qkv: &[f32],
2069 nh: usize,
2070 n: usize,
2071 hd: usize,
2072 scale: f32,
2073 angles: &[f32],
2074 eps: f32,
2075 out: &mut [f32],
2076) -> bool {
2077 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2078}
2079
2080#[allow(clippy::too_many_arguments)]
2081pub fn vae_attention_packed_layout(
2082 qkv: &[f32],
2083 nh: usize,
2084 n: usize,
2085 hd: usize,
2086 scale: f32,
2087 angles: &[f32],
2088 eps: f32,
2089 out: &mut [f32],
2090 layout: u32,
2091) -> bool {
2092 match backend() {
2093 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2094 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2095 qkv, nh, n, hd, scale, angles, eps, out, layout,
2096 ),
2097 #[allow(unreachable_patterns)]
2098 _ => false,
2099 }
2100}
2101
2102#[allow(clippy::too_many_arguments)]
2103pub fn dit_split_only(
2104 qkv: &[f32],
2105 nh: usize,
2106 n: usize,
2107 hd: usize,
2108 layout: u32,
2109 norm: Option<(&[f32], f32)>,
2110 out_q: &mut [f32],
2111) -> bool {
2112 match backend() {
2113 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2114 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2115 #[allow(unreachable_patterns)]
2116 _ => false,
2117 }
2118}
2119
2120pub fn gemm_nt_f32_transient(
2128 x: &[f32],
2129 w: &[f32],
2130 y: &mut [f32],
2131 n: usize,
2132 k: usize,
2133 m: usize,
2134) -> bool {
2135 match backend() {
2136 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2137 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2138 #[allow(unreachable_patterns)]
2139 _ => false,
2140 }
2141}
2142
2143pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2144 match backend() {
2145 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2146 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2147 #[allow(unreachable_patterns)]
2148 _ => false,
2149 }
2150}
2151
2152#[allow(clippy::too_many_arguments)]
2155pub fn music3_ffn(
2156 model: &std::sync::Arc<CmfModel>,
2157 idx_in: usize,
2158 idx_out: usize,
2159 h: &[f32],
2160 bias_in: &[f32],
2161 n: usize,
2162 hs: usize,
2163 inter: usize,
2164 out: &mut [f32],
2165) -> bool {
2166 match backend() {
2167 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2168 Backend::Wgpu => {
2169 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2170 }
2171 #[allow(unreachable_patterns)]
2172 _ => false,
2173 }
2174}
2175
2176#[allow(clippy::too_many_arguments)]
2180pub fn conv1d_gemm(
2181 x: &[f32],
2182 w: &[f32],
2183 ic: usize,
2184 oc: usize,
2185 n: usize,
2186 k: usize,
2187 pad: usize,
2188 dil: usize,
2189 out_n: usize,
2190 yt: &mut [f32],
2191) -> bool {
2192 match backend() {
2193 #[cfg(target_os = "macos")]
2194 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2195 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2196 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2197 #[allow(unreachable_patterns)]
2198 _ => false,
2199 }
2200}
2201
2202#[allow(clippy::too_many_arguments)]
2204pub fn vae_conv2d_coop(
2205 w: &[f32],
2206 bias: Option<&[f32]>,
2207 x: &[f32],
2208 ic: usize,
2209 oc: usize,
2210 h: usize,
2211 wi: usize,
2212 k: usize,
2213 out: &mut [f32],
2214) -> bool {
2215 match backend() {
2216 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2217 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2218 #[allow(unreachable_patterns)]
2219 _ => false,
2220 }
2221}
2222
2223pub fn dit_attention_packed(
2224 qkv: &[f32],
2225 nh: usize,
2226 n: usize,
2227 hd: usize,
2228 scale: f32,
2229 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2232 out: &mut [f32],
2233) -> bool {
2234 match backend() {
2235 #[cfg(feature = "gpu")]
2242 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2243 #[allow(unreachable_patterns)]
2244 _ => false,
2245 }
2246}
2247
2248pub fn dit_attention_packed_available() -> bool {
2256 #[allow(unreachable_patterns)]
2257 match backend() {
2258 #[cfg(feature = "gpu")]
2259 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2260 _ => false,
2261 }
2262}
2263
2264pub fn dit_attention(
2265 qh: &[f32],
2266 kh: &[f32],
2267 vh: &[f32],
2268 nh: usize,
2269 nkv: usize,
2270 n: usize,
2271 hd: usize,
2272 scale: f32,
2273 out: &mut [f32],
2274) -> bool {
2275 match backend() {
2276 #[cfg(target_os = "macos")]
2277 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2278 #[cfg(feature = "gpu")]
2279 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2280 #[allow(unreachable_patterns)]
2281 _ => false,
2282 }
2283}
2284
2285#[allow(unused_variables)]
2290pub fn q4tp_matmat(
2291 model: &Arc<CmfModel>,
2292 idx: usize,
2293 xs: &[f32],
2294 b: usize,
2295 rows: usize,
2296 cols: usize,
2297 out: &mut [f32],
2298) -> bool {
2299 match backend() {
2300 #[cfg(target_os = "macos")]
2301 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2302 #[cfg(feature = "gpu")]
2303 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2304 #[allow(unreachable_patterns)]
2305 _ => false,
2306 }
2307}
2308
2309pub fn q2tp_matmat(
2312 model: &Arc<CmfModel>,
2313 idx: usize,
2314 xs: &[f32],
2315 b: usize,
2316 rows: usize,
2317 cols: usize,
2318 out: &mut [f32],
2319) -> bool {
2320 match backend() {
2321 #[cfg(feature = "gpu")]
2322 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2323 #[allow(unreachable_patterns)]
2324 _ => false,
2325 }
2326}
2327
2328pub fn q4tp_matvec(
2333 model: &Arc<CmfModel>,
2334 idx: usize,
2335 xs: &[f32],
2336 rows: usize,
2337 cols: usize,
2338 out: &mut [f32],
2339) -> bool {
2340 match backend() {
2341 #[cfg(target_os = "macos")]
2342 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2343 #[cfg(feature = "gpu")]
2344 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2345 #[allow(unreachable_patterns)]
2346 _ => false,
2347 }
2348}
2349
2350pub fn q4t_matvec(
2356 model: &Arc<CmfModel>,
2357 idx: usize,
2358 xs: &[f32],
2359 rows: usize,
2360 cols: usize,
2361 out: &mut [f32],
2362) -> bool {
2363 match backend() {
2364 #[cfg(target_os = "macos")]
2365 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2366 #[allow(unreachable_patterns)]
2367 _ => false,
2368 }
2369}
2370
2371pub fn q4t_matmat(
2372 model: &Arc<CmfModel>,
2373 idx: usize,
2374 xs: &[f32],
2375 b: usize,
2376 rows: usize,
2377 cols: usize,
2378 out: &mut [f32],
2379) -> bool {
2380 match backend() {
2381 #[cfg(target_os = "macos")]
2382 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2383 #[cfg(feature = "gpu")]
2384 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2385 #[allow(unreachable_patterns)]
2386 _ => false,
2387 }
2388}
2389
2390#[cfg(target_os = "macos")]
2392pub use crate::gpu_metal::{
2393 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2394 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2395};
2396
2397#[cfg(target_os = "macos")]
2399pub fn gdn_block(
2400 model: &Arc<CmfModel>,
2401 layers: &[GdnGpuLayer],
2402 states: &mut [&mut [f32]],
2403 cfg: &GdnGpuCfg,
2404 h: &mut [f32],
2405) -> bool {
2406 match backend() {
2407 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2408 _ => false,
2409 }
2410}
2411
2412#[allow(unused_variables)]
2414pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2415 match backend() {
2416 #[cfg(target_os = "macos")]
2417 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2418 #[cfg(feature = "gpu")]
2419 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2420 Backend::None => false,
2421 }
2422}
2423
2424#[allow(unused_variables)]
2426pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2427 match backend() {
2428 #[cfg(target_os = "macos")]
2429 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2430 #[cfg(feature = "gpu")]
2431 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2432 Backend::None => false,
2433 }
2434}
2435
2436static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2452static 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)];
2456
2457const GRAPH_RACE_SAMPLES: u32 = 4;
2459
2460static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2470
2471pub fn graph_mark_unsupported() {
2476 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2477 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2478 }
2479}
2480
2481pub fn graph_unsupported() -> bool {
2482 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2483}
2484
2485pub fn graph_unsupported_reset() {
2487 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2488}
2489
2490pub fn graph_race_begin_generation() {
2491 #[cfg(feature = "gpu")]
2496 {
2497 static FLUSHED: std::sync::Once = std::sync::Once::new();
2509 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2510 if FIRST.swap(false, Ordering::Relaxed) {
2511 } else {
2513 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2514 }
2515 }
2516 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2517 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2518 return;
2519 }
2520 let (gn, cn) = (
2521 GRAPH_N[1].load(Ordering::Relaxed),
2522 GRAPH_N[0].load(Ordering::Relaxed),
2523 );
2524 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2525 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2526 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2527 let verdict = if g_avg < c_avg { 1 } else { 2 };
2528 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2529 tracing::info!(
2530 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2531 g_avg as f64 / 1e6,
2532 c_avg as f64 / 1e6,
2533 if verdict == 1 { "graph" } else { "normal path" }
2534 );
2535 return;
2536 }
2537 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2538 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2539}
2540
2541pub fn graph_race_use_graph(trusted: bool) -> bool {
2545 if trusted {
2546 return true;
2547 }
2548 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2549 1 => true,
2550 2 => false,
2551 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2552 }
2553}
2554
2555pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2560 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2561 return false;
2562 }
2563 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2564 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2565 if !first || cn == 0 {
2566 return false;
2567 }
2568 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2569 let ns = dur.as_nanos() as u64;
2570 if ns > 1_000_000_000 && ns > 4 * c_avg {
2571 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2572 tracing::info!(
2573 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2574 ns as f64 / 1e6,
2575 c_avg as f64 / 1e6
2576 );
2577 return true;
2578 }
2579 false
2580}
2581
2582pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2586 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2587 return;
2588 }
2589 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2590 if tok == 0 {
2591 return;
2592 }
2593 let i = used_graph as usize;
2594 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2595 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2596}
2597
2598pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2608 #[inline]
2609 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2610 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2611 for c in chunks.chunks_exact(8) {
2612 h ^= u64::from_le_bytes(c.try_into().unwrap());
2613 h = h.wrapping_mul(0x100_0000_01b3);
2614 }
2615 for &b in tail {
2616 h ^= b as u64;
2617 h = h.wrapping_mul(0x100_0000_01b3);
2618 }
2619 h
2620 }
2621 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2622 if data.len() <= 4096 {
2623 return fnv(h, data);
2624 }
2625 let step = (data.len() - 64) / 63;
2626 for i in 0..64 {
2627 h = fnv(h, &data[i * step..i * step + 64]);
2628 }
2629 h
2630}
2631
2632pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2635 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2636 fp_bytes(bytes)
2637}
2638
2639#[cfg(test)]
2640mod fp_tests {
2641 use super::fp_bytes;
2642
2643 #[test]
2648 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2649 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2651 let h0 = fp_bytes(&base);
2652 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2653 let mut dense = base.clone();
2656 for b in dense.iter_mut() {
2657 *b = b.wrapping_add(1);
2658 }
2659 assert_ne!(
2660 h0,
2661 fp_bytes(&dense),
2662 "a fully different tensor slipped through"
2663 );
2664 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2667 let mut small = vec![3u8; 4096];
2670 let hs = fp_bytes(&small);
2671 small[2048] ^= 1;
2672 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2673 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2675 let v = vec![9u8; n];
2676 let _ = fp_bytes(&v); }
2678 }
2679}
2680
2681pub fn bake_release() {
2685 #[cfg(feature = "gpu")]
2686 crate::gpu_wgpu::bake_release();
2687}
2688
2689pub fn bake_precision_strict(on: bool) {
2693 #[cfg(feature = "gpu")]
2694 crate::gpu_wgpu::bake_precision_strict(on);
2695 #[cfg(not(feature = "gpu"))]
2696 let _ = on;
2697}
2698
2699pub fn hostprof_encode_done(t0: std::time::Instant) {
2705 use std::sync::atomic::{AtomicU64, Ordering};
2706 static ENC: AtomicU64 = AtomicU64::new(0);
2707 static N: AtomicU64 = AtomicU64::new(0);
2708 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2709 return;
2710 }
2711 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2712 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2713 if n % 100 == 0 {
2714 eprintln!(
2715 "hostprof: encode {:.2} ms/token over {n} tokens",
2716 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2717 );
2718 }
2719}
2720
2721pub fn hostprof_total(t0: std::time::Instant) {
2722 use std::sync::atomic::{AtomicU64, Ordering};
2723 static TOT: AtomicU64 = AtomicU64::new(0);
2724 static N: AtomicU64 = AtomicU64::new(0);
2725 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2726 return;
2727 }
2728 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2729 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2730 if n % 100 == 0 {
2731 eprintln!(
2732 "hostprof: total {:.2} ms/token over {n} tokens",
2733 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2734 );
2735 }
2736}
2737
2738pub fn stageprof(stage: u32, dt: std::time::Duration) {
2742 use std::sync::atomic::{AtomicU64, Ordering};
2743 static NS: [AtomicU64; 4] = [
2744 AtomicU64::new(0),
2745 AtomicU64::new(0),
2746 AtomicU64::new(0),
2747 AtomicU64::new(0),
2748 ];
2749 static N: AtomicU64 = AtomicU64::new(0);
2750 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2751 return;
2752 }
2753 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2754 if stage == 1 {
2755 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2756 if n % 200 == 0 {
2757 eprintln!(
2758 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2759 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2760 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2761 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2762 );
2763 }
2764 }
2765}
2766
2767pub fn weight_bytes_dispatched() -> u64 {
2770 let mut total = 0u64;
2771 #[cfg(target_os = "macos")]
2772 {
2773 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2774 }
2775 #[cfg(feature = "gpu")]
2776 {
2777 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2778 }
2779 total
2780}
2781
2782pub fn weight_bytes_by() -> [u64; 6] {
2785 #[cfg(target_os = "macos")]
2786 {
2787 let mut o = [0u64; 6];
2788 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2789 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2790 }
2791 return o;
2792 }
2793 #[allow(unreachable_code)]
2794 [0; 6]
2795}
2796
2797#[cfg(test)]
2798mod probe_warmup_tests {
2799 use super::*;
2800 use std::time::Duration;
2801
2802 fn ms(v: f64) -> Duration {
2803 Duration::from_nanos((v * 1e6) as u64)
2804 }
2805
2806 #[test]
2811 fn one_cold_first_sample_does_not_lose_the_class() {
2812 let p = Probe::new();
2813 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
2815 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
2816 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
2817 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
2818 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
2819 assert_eq!(
2820 p.state.load(Ordering::Relaxed),
2821 1,
2822 "the device is 3x faster once warm and must win"
2823 );
2824 }
2825
2826 #[test]
2830 fn the_warmup_is_spent_once_and_never_underflows() {
2831 let p = Probe::new();
2832 for _ in 0..8 {
2833 probe_record_into(&p, "matmat", None, true, ms(10.0));
2834 }
2835 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
2836 assert_eq!(
2837 p.gpu_n.load(Ordering::Relaxed),
2838 7,
2839 "one sample burned, the rest counted"
2840 );
2841 }
2842
2843 #[test]
2846 fn a_slow_device_still_loses_after_the_warmup() {
2847 let p = Probe::new();
2848 for _ in 0..4 {
2849 probe_record_into(&p, "matvec", None, true, ms(40.0));
2850 }
2851 for _ in 0..4 {
2852 probe_record_into(&p, "matvec", None, false, ms(2.0));
2853 }
2854 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
2855 }
2856}