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
336struct Probe {
337 state: AtomicU8,
339 flip: AtomicU32,
340 gpu_ns: AtomicU64,
341 gpu_n: AtomicU32,
342 cpu_ns: AtomicU64,
343 cpu_n: AtomicU32,
344 gpu_min: AtomicU64,
351 cpu_min: AtomicU64,
352}
353
354impl Probe {
355 const fn new() -> Self {
356 Self {
357 state: AtomicU8::new(0),
358 flip: AtomicU32::new(0),
359 gpu_ns: AtomicU64::new(0),
360 gpu_n: AtomicU32::new(0),
361 cpu_ns: AtomicU64::new(0),
362 cpu_n: AtomicU32::new(0),
363 gpu_min: AtomicU64::new(u64::MAX),
364 cpu_min: AtomicU64::new(u64::MAX),
365 }
366 }
367}
368
369static PROBES: [Probe; 7] = [
370 Probe::new(),
371 Probe::new(),
372 Probe::new(),
373 Probe::new(),
374 Probe::new(),
375 Probe::new(),
376 Probe::new(),
377];
378
379fn probe_on() -> bool {
380 static ON: OnceLock<bool> = OnceLock::new();
381 *ON.get_or_init(|| {
382 std::env::var("CMF_GPU_PROBE")
383 .map(|v| v != "0" && v != "off")
384 .unwrap_or(true)
385 })
386}
387
388pub fn q1_force() -> bool {
393 #[cfg(target_os = "macos")]
394 {
395 backend() == Backend::Metal
396 }
397 #[cfg(not(target_os = "macos"))]
398 {
399 false
400 }
401}
402
403pub fn fused_block_trusted() -> bool {
422 #[cfg(target_os = "macos")]
423 if backend() == Backend::Metal {
424 return true;
425 }
426 wgpu_graph_default()
427}
428
429pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
441 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
442 {
443 return crate::gpu_wgpu::weight_is_resident(model, idx);
444 }
445 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
446 {
447 let _ = (model, idx);
448 true
449 }
450}
451
452pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
453 if !weights_resident && probe_deciding(c) {
454 return ProbeArm::Gpu;
455 }
456 probe_arm(c)
457}
458
459pub fn probe_arm(c: OpClass) -> ProbeArm {
460 PROBE_COLD.with(|f| f.set(false));
465 if !probe_on() {
466 return ProbeArm::Gpu;
467 }
468 probe_cache_load();
469 let p = &PROBES[c as usize];
470 match p.state.load(Ordering::Relaxed) {
471 1 => ProbeArm::Gpu,
472 2 => ProbeArm::Cpu,
473 _ => {
474 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
475 ProbeArm::Gpu
476 } else {
477 ProbeArm::CpuTimed
478 }
479 }
480 }
481}
482
483pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
486 let p = &PROBES[c as usize];
487 if p.state.load(Ordering::Relaxed) != 0 {
488 return;
489 }
490 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
491 return; }
493 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
494 if gpu {
495 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
496 p.gpu_n.fetch_add(1, Ordering::Relaxed);
497 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
498 } else {
499 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
500 p.cpu_n.fetch_add(1, Ordering::Relaxed);
501 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
502 }
503 let (gn, cn) = (
504 p.gpu_n.load(Ordering::Relaxed),
505 p.cpu_n.load(Ordering::Relaxed),
506 );
507 if gn >= 2 && cn >= 2 {
508 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
512 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
513 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
523 return;
524 }
525 let winner = if g <= cp { 1 } else { 2 };
526 if p.state
527 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
528 .is_ok()
529 {
530 tracing::info!(
531 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
532 CLASS_NAMES[c as usize],
533 g / 1e6,
534 cp / 1e6,
535 if winner == 1 { "gpu" } else { "cpu" },
536 );
537 probe_cache_store(c, winner);
538 }
539 }
540}
541
542pub fn probe_deciding(c: OpClass) -> bool {
545 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
546}
547
548#[allow(unused_variables)]
558pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
559 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
560 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
561 let resident = match backend() {
562 #[cfg(target_os = "macos")]
563 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
564 #[cfg(feature = "gpu")]
565 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
566 Backend::None => false,
567 };
568 if !resident && may_upload {
569 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
570 }
571 resident
572}
573
574#[cfg(test)]
576pub(crate) fn probe_reset() {
577 for p in &PROBES {
578 p.state.store(0, Ordering::Relaxed);
579 p.flip.store(0, Ordering::Relaxed);
580 p.gpu_ns.store(0, Ordering::Relaxed);
581 p.gpu_n.store(0, Ordering::Relaxed);
582 p.cpu_ns.store(0, Ordering::Relaxed);
583 p.cpu_n.store(0, Ordering::Relaxed);
584 }
585}
586
587#[cfg(test)]
588mod probe_tests {
589 use super::*;
590 use std::time::Duration;
591
592 #[test]
595 fn probe_alternates_discards_cold_and_decides() {
596 probe_reset();
597 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
599 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
600
601 probe_note_cold();
605 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
606 for _ in 0..PROBE_SAMPLES {
607 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
608 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
609 }
610 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
611
612 for _ in 0..PROBE_SAMPLES {
614 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
615 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
616 }
617 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
618
619 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
621 CPU_ONLY.with(|c| assert!(!c.get()));
622 cpu_scope(|| {
623 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
624 CPU_ONLY.with(|c| assert!(c.get()));
625 });
626 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
627 CPU_ONLY.with(|c| assert!(!c.get()));
628 probe_reset();
629 }
630
631 #[test]
632 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
633 let mine = probe_cache_key_named("gemm-nt");
645 let state = || {
646 PROBES[OpClass::GemmNt as usize]
647 .state
648 .load(Ordering::Relaxed)
649 };
650
651 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
653 assert_eq!(state(), 0);
654 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
656 assert_ne!(older, mine);
657 probe_cache_adopt(&format!("{older}\tgpu\n"));
658 assert_eq!(state(), 0);
659 probe_cache_adopt(&format!("{mine}\tcpu\n"));
661 assert_eq!(state(), 2);
662
663 PROBES[OpClass::GemmNt as usize]
664 .state
665 .store(0, Ordering::Relaxed);
666 }
667}
668
669pub const GPU_MIN_ROWS: usize = 65_536;
672
673pub fn min_rows() -> usize {
680 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
681 .ok()
682 .and_then(|v| v.parse().ok())
683 {
684 return v;
685 }
686 if discrete() { 4096 } else { GPU_MIN_ROWS }
687}
688
689pub fn discrete() -> bool {
691 match backend() {
692 #[cfg(feature = "gpu")]
693 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
694 #[cfg(target_os = "macos")]
695 Backend::Metal => false, Backend::None => false,
697 }
698}
699
700pub struct MoeJob<'a> {
704 pub gate: (usize, usize, usize, &'a [f32]),
705 pub up: (usize, usize, usize, &'a [f32]),
706 pub down: (usize, usize, usize, &'a [f32]),
707 pub xs_gate: Vec<f32>,
708 pub xs_up: Vec<f32>,
709 pub down_col: &'a [f32],
710 pub w: f32,
711 pub q1: bool,
714 pub q4t: bool,
717 pub q4tp: bool,
721 pub gu_q2: bool,
725 pub swiglu_limit: f32,
730}
731
732pub struct BatchJob<'a> {
734 pub idx: usize,
735 pub rows: usize,
736 pub cols: usize,
737 pub row_scale: &'a [f32],
738 pub xs: Vec<f32>,
739 pub layout: BatchLayout,
743}
744
745#[derive(Clone, Copy, PartialEq, Eq, Debug)]
748pub enum BatchLayout {
749 Q8,
750 Q1,
751 Q4t,
752 Q4tp,
753}
754
755#[derive(Clone, Copy, PartialEq, Eq)]
756enum Backend {
757 None,
758 #[cfg(target_os = "macos")]
759 Metal,
760 #[cfg(feature = "gpu")]
761 Wgpu,
762}
763
764fn backend() -> Backend {
765 #[cfg(feature = "gpu")]
766 if crate::gpu_wgpu::selected() {
767 return if crate::gpu_wgpu::enabled() {
768 Backend::Wgpu
769 } else {
770 Backend::None
771 };
772 }
773 #[cfg(target_os = "macos")]
774 if crate::gpu_metal::enabled() {
775 return Backend::Metal;
776 }
777 Backend::None
778}
779
780pub fn backend_available() -> bool {
786 #[cfg(target_os = "macos")]
787 {
788 true
790 }
791 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
792 {
793 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
794 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
795 }
796 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
797 {
798 false
799 }
800}
801
802pub fn enabled() -> bool {
803 backend() != Backend::None
804}
805
806pub fn wgpu_active() -> bool {
820 #[cfg(feature = "gpu")]
821 {
822 matches!(backend(), Backend::Wgpu)
823 }
824 #[cfg(not(feature = "gpu"))]
825 {
826 false
827 }
828}
829
830pub fn default_device() -> usize {
837 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
838 *D.get_or_init(|| {
839 std::env::var("CMF_GPU_ADAPTER")
840 .ok()
841 .and_then(|v| v.trim().parse::<usize>().ok())
842 .unwrap_or(0)
843 })
844}
845
846thread_local! {
847 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
848}
849
850pub fn current_device() -> usize {
852 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
853}
854
855pub fn set_current_device(i: usize) {
859 CUR_DEV.with(|c| c.set(Some(i)));
860}
861
862pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
864 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
865 let r = f();
866 CUR_DEV.with(|c| c.set(prev));
867 r
868}
869
870pub fn device_count() -> usize {
873 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
874 {
875 return crate::gpu_wgpu::adapter_count();
876 }
877 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
878 {
879 usize::from(backend_available())
880 }
881}
882
883pub fn vram_budget() -> u64 {
887 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
888 {
889 return crate::gpu_wgpu::device_vram_budget();
890 }
891 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
892 {
893 if backend_available() { u64::MAX } else { 0 }
894 }
895}
896
897pub fn upload_bytes() -> u64 {
901 #[cfg(feature = "gpu")]
902 {
903 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
904 }
905 #[cfg(not(feature = "gpu"))]
906 0
907}
908
909#[derive(Clone, Copy, PartialEq, Eq, Debug)]
921pub enum GraphPhase {
922 Prefill,
923 Decode,
924}
925
926pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
934 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
935 Some("0") => false,
936 Some("prefill") => phase == GraphPhase::Prefill,
937 Some(_) => true,
938 None => {
939 if wgpu_graph_default() {
940 return true;
941 }
942 let _ = phase;
947 false
948 }
949 }
950}
951
952pub fn wgpu_graph_default() -> bool {
953 #[cfg(feature = "gpu")]
954 {
955 matches!(backend(), Backend::Wgpu)
961 && (crate::gpu_wgpu::discrete_active()
962 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
963 }
964 #[cfg(not(feature = "gpu"))]
965 {
966 false
967 }
968}
969
970#[allow(clippy::too_many_arguments, unused_variables)]
972pub fn q8_matvec_range(
973 model: &Arc<CmfModel>,
974 idx: usize,
975 row0: usize,
976 row_scale: &[f32],
977 xs: &[f32],
978 rows: usize,
979 cols: usize,
980 out: &mut [f32],
981) -> bool {
982 match backend() {
983 #[cfg(target_os = "macos")]
984 Backend::Metal => {
985 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
986 }
987 #[cfg(feature = "gpu")]
988 Backend::Wgpu => {
989 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
990 }
991 Backend::None => false,
992 }
993}
994
995#[allow(clippy::too_many_arguments, unused_variables)]
998pub fn q8_matmat(
999 model: &Arc<CmfModel>,
1000 idx: usize,
1001 row_scale: &[f32],
1002 pre: &[f32],
1003 b: usize,
1004 rows: usize,
1005 cols: usize,
1006 out: &mut [f32],
1007) -> bool {
1008 match backend() {
1009 #[cfg(target_os = "macos")]
1010 Backend::Metal => {
1011 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1012 }
1013 #[cfg(feature = "gpu")]
1014 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1015 Backend::None => false,
1016 }
1017}
1018
1019#[allow(unused_variables)]
1022pub fn q1_matvec(
1023 model: &Arc<CmfModel>,
1024 idx: usize,
1025 xs: &[f32],
1026 rows: usize,
1027 cols: usize,
1028 out: &mut [f32],
1029) -> bool {
1030 match backend() {
1031 #[cfg(target_os = "macos")]
1032 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1033 #[cfg(feature = "gpu")]
1034 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1035 Backend::None => false,
1036 }
1037}
1038
1039#[allow(clippy::too_many_arguments)]
1043pub fn attn_dropin(
1044 model: &Arc<CmfModel>,
1045 kv_id: u64,
1046 layer: usize,
1047 normed: &[f32],
1048 wq_idx: usize,
1049 wk_idx: usize,
1050 wv_idx: usize,
1051 wo_idx: usize,
1052 q_norm: Option<&[f32]>,
1053 k_norm: Option<&[f32]>,
1054 invf: &[f32],
1055 nh: usize,
1056 nkv: usize,
1057 hd: usize,
1058 rd: usize,
1059 hidden: usize,
1060 pos: usize,
1061 cap: usize,
1062 gemma: bool,
1063 eps: f32,
1064 cpu_k: &[Vec<f32>],
1065 cpu_v: &[Vec<f32>],
1066 out: &mut [f32],
1067) -> bool {
1068 match backend() {
1069 #[cfg(feature = "gpu")]
1070 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1071 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1072 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1073 ),
1074 #[allow(unused_variables)]
1075 _ => false,
1076 }
1077}
1078
1079pub struct GraphW<'a> {
1083 pub idx: usize,
1084 pub kind: u8,
1085 pub row_scale: &'a [f32],
1086 pub data: &'a [f32],
1087}
1088
1089pub enum GraphAttn<'a> {
1092 Full {
1093 wq: GraphW<'a>,
1094 wk: GraphW<'a>,
1095 wv: GraphW<'a>,
1096 wo: GraphW<'a>,
1097 q_norm: Option<&'a [f32]>,
1098 k_norm: Option<&'a [f32]>,
1099 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1101 output_gate: bool,
1104 cpu_k: &'a [Vec<f32>],
1105 cpu_v: &'a [Vec<f32>],
1106 },
1107 Gdn {
1108 qkv: GraphW<'a>,
1109 z: GraphW<'a>,
1110 a: GraphW<'a>,
1111 b: GraphW<'a>,
1112 out: GraphW<'a>,
1113 conv1d: &'a [f32],
1114 a_log: &'a [f32],
1115 dt_bias: &'a [f32],
1116 norm: &'a [f32],
1117 nv: usize,
1118 nk: usize,
1119 dk: usize,
1120 dv: usize,
1121 kk: usize,
1122 cpu_state: &'a [f32],
1127 },
1128}
1129
1130pub struct GraphLayer<'a> {
1132 pub input_norm: &'a [f32],
1133 pub attn: GraphAttn<'a>,
1134 pub post_norm: &'a [f32],
1135 pub ffn: GraphFfn<'a>,
1136}
1137
1138pub enum GraphFfn<'a> {
1143 Dense {
1144 gate: GraphW<'a>,
1145 up: GraphW<'a>,
1146 down: GraphW<'a>,
1147 },
1148 Moe {
1149 router: GraphW<'a>,
1151 shared_gate: GraphW<'a>,
1153 experts: Vec<(usize, usize, usize)>,
1157 n_exp: usize,
1159 top_k: usize,
1160 inter: usize,
1161 norm_topk: bool,
1162 q4tp: bool,
1168 gu_q2: bool,
1172 },
1173}
1174
1175#[allow(clippy::too_many_arguments)]
1180pub fn forward_token_graph(
1181 model: &Arc<CmfModel>,
1182 kv_id: u64,
1183 layers: &[GraphLayer],
1184 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1187 o1_epoch: u64,
1188 invf: &[f32],
1189 h: &mut [f32],
1190 nh: usize,
1191 nkv: usize,
1192 hd: usize,
1193 rd: usize,
1194 hidden: usize,
1195 inter: usize,
1196 position: usize,
1197 cap: usize,
1198 gemma: bool,
1199 eps: f32,
1200 lm_head: Option<(&GraphW, usize)>,
1201 final_norm: &[f32],
1202 logits: &mut Vec<f32>,
1203 loop_norm_at: &[usize],
1204 steps: usize,
1205 embed: Option<(&GraphW, usize, f32)>,
1206 ids_out: Option<&mut Vec<u32>>,
1207 layers_run: Option<&mut usize>,
1210 layer_base: usize,
1214 hidden_too: bool,
1216) -> bool {
1217 match backend() {
1218 #[cfg(feature = "gpu")]
1219 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1220 model,
1221 kv_id,
1222 layers,
1223 o1,
1224 o1_epoch,
1225 invf,
1226 h,
1227 nh,
1228 nkv,
1229 hd,
1230 rd,
1231 hidden,
1232 inter,
1233 position,
1234 cap,
1235 gemma,
1236 eps,
1237 lm_head,
1238 final_norm,
1239 logits,
1240 loop_norm_at,
1241 steps,
1242 embed,
1243 ids_out,
1244 layers_run,
1245 layer_base,
1246 hidden_too,
1247 ),
1248 #[allow(unused_variables)]
1249 _ => {
1250 let _ = (
1251 lm_head,
1252 final_norm,
1253 logits,
1254 loop_norm_at,
1255 layers_run,
1256 layer_base,
1257 hidden_too,
1258 );
1259 false
1260 }
1261 }
1262}
1263
1264pub struct SpecTail<'a> {
1268 pub lm: GraphW<'a>,
1269 pub lm_rows: usize,
1270 pub final_norm: &'a [f32],
1271 pub logits_out: &'a mut Vec<f32>,
1272}
1273
1274#[allow(clippy::too_many_arguments)]
1278pub fn forward_batch_graph(
1279 model: &Arc<CmfModel>,
1280 kv_id: u64,
1281 layers: &[GraphLayer],
1282 invf: &[f32],
1283 h: &mut [f32],
1284 nh: usize,
1285 nkv: usize,
1286 hd: usize,
1287 rd: usize,
1288 hidden: usize,
1289 inter: usize,
1290 positions: &[usize],
1291 cap: usize,
1292 gemma: bool,
1293 eps: f32,
1294 k: usize,
1295 spec: Option<SpecTail<'_>>,
1296) -> bool {
1297 match backend() {
1298 #[cfg(feature = "gpu")]
1299 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1300 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1301 eps, k, spec,
1302 ),
1303 #[allow(unreachable_patterns)]
1304 _ => {
1305 let _ = spec;
1306 false
1307 }
1308 }
1309}
1310
1311pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1314 #[cfg(feature = "gpu")]
1315 if backend() == Backend::Wgpu {
1316 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1317 }
1318 #[allow(unreachable_code)]
1319 {
1320 let _ = (kv_id, slot);
1321 false
1322 }
1323}
1324
1325pub fn graph_kv_reset(_kv_id: u64) {
1327 #[cfg(feature = "gpu")]
1328 if backend() == Backend::Wgpu {
1329 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1330 }
1331}
1332
1333pub fn q1t_matvec(
1337 model: &Arc<CmfModel>,
1338 idx: usize,
1339 xs: &[f32],
1340 rows: usize,
1341 cols: usize,
1342 out: &mut [f32],
1343) -> bool {
1344 match backend() {
1345 #[cfg(target_os = "macos")]
1346 Backend::Metal => {
1347 if metal_q1t_enabled() {
1348 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1349 } else {
1350 false
1351 }
1352 }
1353 #[cfg(feature = "gpu")]
1354 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1355 Backend::None => false,
1356 }
1357}
1358
1359#[allow(unused_variables)]
1362pub fn q4b_matvec(
1363 model: &Arc<CmfModel>,
1364 idx: usize,
1365 xs: &[f32],
1366 rows: usize,
1367 cols: usize,
1368 out: &mut [f32],
1369) -> bool {
1370 match backend() {
1371 #[cfg(target_os = "macos")]
1372 Backend::Metal => false,
1373 #[cfg(feature = "gpu")]
1374 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1375 Backend::None => false,
1376 }
1377}
1378
1379pub fn q1t_matmat(
1382 model: &Arc<CmfModel>,
1383 idx: usize,
1384 xs: &[f32],
1385 b: usize,
1386 rows: usize,
1387 cols: usize,
1388 out: &mut [f32],
1389) -> bool {
1390 match backend() {
1391 #[cfg(target_os = "macos")]
1392 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1396 #[cfg(feature = "gpu")]
1397 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1398 Backend::None => false,
1399 }
1400}
1401
1402#[cfg(target_os = "macos")]
1406pub(crate) fn metal_q1t_enabled() -> bool {
1407 std::env::var("CMF_METAL_Q1T")
1408 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1409 .unwrap_or(true)
1410}
1411
1412pub fn q1_matmat(
1414 model: &Arc<CmfModel>,
1415 idx: usize,
1416 xs: &[f32],
1417 b: usize,
1418 rows: usize,
1419 cols: usize,
1420 out: &mut [f32],
1421) -> bool {
1422 match backend() {
1423 #[cfg(feature = "gpu")]
1424 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1425 #[allow(unused_variables)]
1426 _ => false,
1427 }
1428}
1429
1430static MM_KILL: AtomicBool = AtomicBool::new(false);
1435pub(crate) fn mm_killed() -> bool {
1436 MM_KILL.load(Ordering::Relaxed)
1437}
1438pub(crate) fn mm_kill() {
1439 MM_KILL.store(true, Ordering::Relaxed);
1440}
1441
1442static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1449const MM_STRIKES_TO_KILL: u32 = 3;
1450static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1457
1458pub fn mm_kill_arm(on: bool) {
1461 MM_ARMED.store(on, Ordering::Relaxed);
1462 if on {
1463 MM_STRIKES.store(0, Ordering::Relaxed);
1464 }
1465}
1466
1467pub(crate) fn mm_budget_check(
1474 what: &str,
1475 el: std::time::Duration,
1476 budget: std::time::Duration,
1477 exempt: bool,
1478) {
1479 if el <= budget {
1480 MM_STRIKES.store(0, Ordering::Relaxed);
1481 return;
1482 }
1483 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1484 return;
1485 }
1486 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1487 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1488 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1489 if !on {
1490 tracing::info!(
1491 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1492 );
1493 return;
1494 }
1495 if n >= MM_STRIKES_TO_KILL {
1496 tracing::warn!(
1497 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1498 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1499 );
1500 mm_kill();
1501 } else {
1502 tracing::info!(
1503 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1504 );
1505 }
1506}
1507
1508#[allow(unused_variables, clippy::too_many_arguments)]
1513pub fn chunk_attend(
1514 q: &[f32],
1515 k: &[&[f32]],
1516 v: &[&[f32]],
1517 b: usize,
1518 s0: usize,
1519 nh: usize,
1520 nkv: usize,
1521 hd: usize,
1522 scale: f32,
1523 out: &mut [f32],
1524) -> bool {
1525 match backend() {
1526 #[cfg(feature = "gpu")]
1527 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1528 #[allow(unreachable_patterns)]
1529 _ => false,
1530 }
1531}
1532
1533#[allow(unused_variables, clippy::too_many_arguments)]
1537pub fn q4t_qkv(
1538 model: &Arc<CmfModel>,
1539 wq: usize,
1540 wk: usize,
1541 wv: usize,
1542 xs: &[f32],
1543 b: usize,
1544 cols: usize,
1545 rq: usize,
1546 rk: usize,
1547 rv: usize,
1548 out: &mut [f32],
1549) -> bool {
1550 match backend() {
1551 #[cfg(feature = "gpu")]
1552 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1553 #[allow(unreachable_patterns)]
1554 _ => false,
1555 }
1556}
1557
1558#[allow(unused_variables, clippy::too_many_arguments)]
1560#[allow(clippy::too_many_arguments, unused_variables)]
1564pub fn q4tp_ffn_packed(
1565 model: &Arc<CmfModel>,
1566 w1: usize,
1567 w2: usize,
1568 xs: &[f32],
1569 b: usize,
1570 hidden: usize,
1571 inter: usize,
1572 bias: Option<&[f32]>,
1573 out: &mut [f32],
1574) -> bool {
1575 match backend() {
1576 #[cfg(feature = "gpu")]
1577 Backend::Wgpu => {
1578 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1579 }
1580 #[allow(unreachable_patterns)]
1581 _ => false,
1582 }
1583}
1584
1585pub fn q4tp_ffn(
1586 model: &Arc<CmfModel>,
1587 w1: usize,
1588 w3: usize,
1589 w2: usize,
1590 xs: &[f32],
1591 b: usize,
1592 hidden: usize,
1593 inter: usize,
1594 out: &mut [f32],
1595) -> bool {
1596 match backend() {
1597 #[cfg(target_os = "macos")]
1598 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1599 #[cfg(feature = "gpu")]
1600 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1601 #[allow(unreachable_patterns)]
1602 _ => false,
1603 }
1604}
1605
1606pub fn q4t_ffn(
1607 model: &Arc<CmfModel>,
1608 w1: usize,
1609 w3: usize,
1610 w2: usize,
1611 xs: &[f32],
1612 b: usize,
1613 hidden: usize,
1614 inter: usize,
1615 out: &mut [f32],
1616) -> bool {
1617 match backend() {
1618 #[cfg(target_os = "macos")]
1619 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1620 #[cfg(feature = "gpu")]
1621 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1622 #[allow(unreachable_patterns)]
1623 _ => false,
1624 }
1625}
1626
1627pub struct DitBlockArgs<'a> {
1632 pub n: usize,
1633 pub hidden: usize,
1634 pub inter: usize,
1635 pub nh: usize,
1636 pub nkv: usize,
1637 pub hd: usize,
1638 pub eps: f32,
1639 pub rope_cos: &'a [f32],
1640 pub rope_sin: &'a [f32],
1641 pub norm1: &'a [f32],
1642 pub norm2: &'a [f32],
1643 pub ffn_norm1: &'a [f32],
1644 pub ffn_norm2: &'a [f32],
1645 pub norm_q: &'a [f32],
1646 pub norm_k: &'a [f32],
1647 pub s_msa: &'a [f32],
1648 pub gate_msa: &'a [f32],
1649 pub s_mlp: &'a [f32],
1650 pub gate_mlp: &'a [f32],
1651 pub wq: usize,
1652 pub wk: usize,
1653 pub wv: usize,
1654 pub wo: usize,
1655 pub w1: usize,
1656 pub w3: usize,
1657 pub w2: usize,
1658 pub q4tp: bool,
1662 pub resident_in: bool,
1665 pub resident_out: bool,
1669}
1670
1671pub fn dit_chain_supported() -> bool {
1675 #[cfg(feature = "gpu")]
1676 {
1677 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1678 }
1679 #[allow(unreachable_code)]
1680 false
1681}
1682
1683pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1686 #[cfg(feature = "gpu")]
1687 {
1688 if matches!(backend(), Backend::Wgpu) {
1689 return crate::gpu_wgpu::dit_state_fetch(_x);
1690 }
1691 }
1692 false
1693}
1694
1695#[allow(unused_variables)]
1699#[allow(unused_variables, clippy::too_many_arguments)]
1703pub fn dit_qkv(
1704 model: &Arc<CmfModel>,
1705 wq: usize,
1706 wk: usize,
1707 wv: usize,
1708 xs: &[f32],
1709 b: usize,
1710 hidden: usize,
1711 qrows: usize,
1712 kvrows: usize,
1713 q_out: &mut [f32],
1714 k_out: &mut [f32],
1715 v_out: &mut [f32],
1716) -> bool {
1717 match backend() {
1718 #[cfg(feature = "gpu")]
1719 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1720 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1721 ),
1722 #[allow(unreachable_patterns)]
1723 _ => false,
1724 }
1725}
1726
1727pub fn fused_dit_block_available() -> bool {
1731 #[cfg(target_os = "macos")]
1732 {
1733 matches!(backend(), Backend::Metal) && fused_block_trusted()
1734 }
1735 #[cfg(not(target_os = "macos"))]
1736 {
1737 false
1738 }
1739}
1740
1741pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1742 dit_block_seg(model, a, &[a.n], x)
1743}
1744
1745pub fn dit_block_seg(
1749 model: &Arc<CmfModel>,
1750 a: &DitBlockArgs,
1751 segs: &[usize],
1752 x: &mut [f32],
1753) -> bool {
1754 match backend() {
1755 #[cfg(target_os = "macos")]
1756 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1757 #[cfg(feature = "gpu")]
1764 Backend::Wgpu
1765 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1766 Some("0") => false,
1767 Some(_) => true,
1768 None => crate::gpu_wgpu::discrete_active(),
1769 } =>
1770 {
1771 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1772 }
1773 #[allow(unreachable_patterns)]
1774 _ => false,
1775 }
1776}
1777
1778pub struct VaeResnetArgs<'a> {
1782 pub groups: usize,
1783 pub ic: usize,
1784 pub oc: usize,
1785 pub h: usize,
1786 pub w: usize,
1787 pub n1w: &'a [f32],
1788 pub n1b: &'a [f32],
1789 pub c1w: &'a [f32],
1790 pub c1b: &'a [f32],
1791 pub c1k: usize,
1792 pub n2w: &'a [f32],
1793 pub n2b: &'a [f32],
1794 pub c2w: &'a [f32],
1795 pub c2b: &'a [f32],
1796 pub c2k: usize,
1797 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1798}
1799
1800#[allow(unused_variables)]
1803pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1804 match backend() {
1805 #[cfg(target_os = "macos")]
1806 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1807 _ => false,
1808 }
1809}
1810
1811#[allow(unused_variables, clippy::too_many_arguments)]
1814pub fn vae_upsample_conv(
1815 w: &[f32],
1816 bias: &[f32],
1817 x: &[f32],
1818 ic: usize,
1819 oc: usize,
1820 h: usize,
1821 w_img: usize,
1822 k: usize,
1823 out: &mut [f32],
1824) -> bool {
1825 match backend() {
1826 #[cfg(target_os = "macos")]
1827 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1828 #[cfg(feature = "gpu")]
1829 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1830 #[allow(unreachable_patterns)]
1831 _ => false,
1832 }
1833}
1834
1835#[allow(unused_variables, clippy::too_many_arguments)]
1838pub fn vae_conv2d(
1839 w: &[f32],
1840 bias: &[f32],
1841 x: &[f32],
1842 ic: usize,
1843 oc: usize,
1844 h: usize,
1845 w_img: usize,
1846 k: usize,
1847 out: &mut [f32],
1848) -> bool {
1849 match backend() {
1850 #[cfg(target_os = "macos")]
1851 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1852 #[cfg(feature = "gpu")]
1853 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1854 #[allow(unreachable_patterns)]
1855 _ => false,
1856 }
1857}
1858
1859#[allow(unused_variables, clippy::too_many_arguments)]
1863#[allow(unused_variables)]
1867#[allow(clippy::too_many_arguments)]
1868#[allow(clippy::too_many_arguments, unused_variables)]
1871pub fn dit_qkv_attention(
1872 model: &Arc<CmfModel>,
1873 qkv_idx: usize,
1874 xn: &[f32],
1875 n: usize,
1876 hidden: usize,
1877 nh: usize,
1878 hd: usize,
1879 scale: f32,
1880 nr: (&[f32], &[f32], &[f32], f32),
1881 out: &mut [f32],
1882) -> bool {
1883 match backend() {
1884 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1885 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1886 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1887 ),
1888 #[allow(unreachable_patterns)]
1889 _ => false,
1890 }
1891}
1892
1893#[allow(clippy::too_many_arguments)]
1896pub fn dit_qkv_attn_out(
1897 model: &Arc<CmfModel>,
1898 qkv_idx: usize,
1899 out_idx: usize,
1900 xn: &[f32],
1901 n: usize,
1902 hidden: usize,
1903 nh: usize,
1904 hd: usize,
1905 scale: f32,
1906 nr: (&[f32], &[f32], &[f32], f32),
1907 proj: &mut [f32],
1908) -> bool {
1909 match backend() {
1910 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1911 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1912 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1913 ),
1914 #[allow(unreachable_patterns)]
1915 _ => false,
1916 }
1917}
1918
1919#[allow(clippy::too_many_arguments)]
1921pub fn vae_qkv_attn_out(
1922 model: &Arc<CmfModel>,
1923 qkv_idx: usize,
1924 out_idx: usize,
1925 xn: &[f32],
1926 n: usize,
1927 dim: usize,
1928 nh: usize,
1929 hd: usize,
1930 scale: f32,
1931 angles: &[f32],
1932 eps: f32,
1933 qkv_bias: &[f32],
1934 proj: &mut [f32],
1935) -> bool {
1936 match backend() {
1937 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1938 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1939 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1940 ),
1941 #[allow(unreachable_patterns)]
1942 _ => false,
1943 }
1944}
1945
1946#[allow(clippy::too_many_arguments)]
1947pub fn vae_attention_packed(
1948 qkv: &[f32],
1949 nh: usize,
1950 n: usize,
1951 hd: usize,
1952 scale: f32,
1953 angles: &[f32],
1954 eps: f32,
1955 out: &mut [f32],
1956) -> bool {
1957 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
1958}
1959
1960#[allow(clippy::too_many_arguments)]
1961pub fn vae_attention_packed_layout(
1962 qkv: &[f32],
1963 nh: usize,
1964 n: usize,
1965 hd: usize,
1966 scale: f32,
1967 angles: &[f32],
1968 eps: f32,
1969 out: &mut [f32],
1970 layout: u32,
1971) -> bool {
1972 match backend() {
1973 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1974 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
1975 qkv, nh, n, hd, scale, angles, eps, out, layout,
1976 ),
1977 #[allow(unreachable_patterns)]
1978 _ => false,
1979 }
1980}
1981
1982#[allow(clippy::too_many_arguments)]
1983pub fn dit_split_only(
1984 qkv: &[f32],
1985 nh: usize,
1986 n: usize,
1987 hd: usize,
1988 layout: u32,
1989 norm: Option<(&[f32], f32)>,
1990 out_q: &mut [f32],
1991) -> bool {
1992 match backend() {
1993 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1994 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
1995 #[allow(unreachable_patterns)]
1996 _ => false,
1997 }
1998}
1999
2000pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2005 match backend() {
2006 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2007 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2008 #[allow(unreachable_patterns)]
2009 _ => false,
2010 }
2011}
2012
2013#[allow(clippy::too_many_arguments)]
2016pub fn music3_ffn(
2017 model: &std::sync::Arc<CmfModel>,
2018 idx_in: usize,
2019 idx_out: usize,
2020 h: &[f32],
2021 bias_in: &[f32],
2022 n: usize,
2023 hs: usize,
2024 inter: usize,
2025 out: &mut [f32],
2026) -> bool {
2027 match backend() {
2028 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2029 Backend::Wgpu => {
2030 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2031 }
2032 #[allow(unreachable_patterns)]
2033 _ => false,
2034 }
2035}
2036
2037#[allow(clippy::too_many_arguments)]
2041pub fn conv1d_gemm(
2042 x: &[f32],
2043 w: &[f32],
2044 ic: usize,
2045 oc: usize,
2046 n: usize,
2047 k: usize,
2048 pad: usize,
2049 dil: usize,
2050 out_n: usize,
2051 yt: &mut [f32],
2052) -> bool {
2053 match backend() {
2054 #[cfg(target_os = "macos")]
2055 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2056 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2057 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2058 #[allow(unreachable_patterns)]
2059 _ => false,
2060 }
2061}
2062
2063#[allow(clippy::too_many_arguments)]
2065pub fn vae_conv2d_coop(
2066 w: &[f32],
2067 bias: Option<&[f32]>,
2068 x: &[f32],
2069 ic: usize,
2070 oc: usize,
2071 h: usize,
2072 wi: usize,
2073 k: usize,
2074 out: &mut [f32],
2075) -> bool {
2076 match backend() {
2077 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2078 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2079 #[allow(unreachable_patterns)]
2080 _ => false,
2081 }
2082}
2083
2084pub fn dit_attention_packed(
2085 qkv: &[f32],
2086 nh: usize,
2087 n: usize,
2088 hd: usize,
2089 scale: f32,
2090 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2093 out: &mut [f32],
2094) -> bool {
2095 match backend() {
2096 #[cfg(feature = "gpu")]
2103 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2104 #[allow(unreachable_patterns)]
2105 _ => false,
2106 }
2107}
2108
2109pub fn dit_attention_packed_available() -> bool {
2117 #[allow(unreachable_patterns)]
2118 match backend() {
2119 #[cfg(feature = "gpu")]
2120 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2121 _ => false,
2122 }
2123}
2124
2125pub fn dit_attention(
2126 qh: &[f32],
2127 kh: &[f32],
2128 vh: &[f32],
2129 nh: usize,
2130 nkv: usize,
2131 n: usize,
2132 hd: usize,
2133 scale: f32,
2134 out: &mut [f32],
2135) -> bool {
2136 match backend() {
2137 #[cfg(target_os = "macos")]
2138 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2139 #[cfg(feature = "gpu")]
2140 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2141 #[allow(unreachable_patterns)]
2142 _ => false,
2143 }
2144}
2145
2146#[allow(unused_variables)]
2151pub fn q4tp_matmat(
2152 model: &Arc<CmfModel>,
2153 idx: usize,
2154 xs: &[f32],
2155 b: usize,
2156 rows: usize,
2157 cols: usize,
2158 out: &mut [f32],
2159) -> bool {
2160 match backend() {
2161 #[cfg(target_os = "macos")]
2162 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2163 #[cfg(feature = "gpu")]
2164 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2165 #[allow(unreachable_patterns)]
2166 _ => false,
2167 }
2168}
2169
2170pub fn q2tp_matmat(
2173 model: &Arc<CmfModel>,
2174 idx: usize,
2175 xs: &[f32],
2176 b: usize,
2177 rows: usize,
2178 cols: usize,
2179 out: &mut [f32],
2180) -> bool {
2181 match backend() {
2182 #[cfg(feature = "gpu")]
2183 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2184 #[allow(unreachable_patterns)]
2185 _ => false,
2186 }
2187}
2188
2189pub fn q4tp_matvec(
2194 model: &Arc<CmfModel>,
2195 idx: usize,
2196 xs: &[f32],
2197 rows: usize,
2198 cols: usize,
2199 out: &mut [f32],
2200) -> bool {
2201 match backend() {
2202 #[cfg(target_os = "macos")]
2203 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2204 #[cfg(feature = "gpu")]
2205 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2206 #[allow(unreachable_patterns)]
2207 _ => false,
2208 }
2209}
2210
2211pub fn q4t_matvec(
2217 model: &Arc<CmfModel>,
2218 idx: usize,
2219 xs: &[f32],
2220 rows: usize,
2221 cols: usize,
2222 out: &mut [f32],
2223) -> bool {
2224 match backend() {
2225 #[cfg(target_os = "macos")]
2226 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2227 #[allow(unreachable_patterns)]
2228 _ => false,
2229 }
2230}
2231
2232pub fn q4t_matmat(
2233 model: &Arc<CmfModel>,
2234 idx: usize,
2235 xs: &[f32],
2236 b: usize,
2237 rows: usize,
2238 cols: usize,
2239 out: &mut [f32],
2240) -> bool {
2241 match backend() {
2242 #[cfg(target_os = "macos")]
2243 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2244 #[cfg(feature = "gpu")]
2245 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2246 #[allow(unreachable_patterns)]
2247 _ => false,
2248 }
2249}
2250
2251#[cfg(target_os = "macos")]
2253pub use crate::gpu_metal::{
2254 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2255 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2256};
2257
2258#[cfg(target_os = "macos")]
2260pub fn gdn_block(
2261 model: &Arc<CmfModel>,
2262 layers: &[GdnGpuLayer],
2263 states: &mut [&mut [f32]],
2264 cfg: &GdnGpuCfg,
2265 h: &mut [f32],
2266) -> bool {
2267 match backend() {
2268 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2269 _ => false,
2270 }
2271}
2272
2273#[allow(unused_variables)]
2275pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2276 match backend() {
2277 #[cfg(target_os = "macos")]
2278 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2279 #[cfg(feature = "gpu")]
2280 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2281 Backend::None => false,
2282 }
2283}
2284
2285#[allow(unused_variables)]
2287pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2288 match backend() {
2289 #[cfg(target_os = "macos")]
2290 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2291 #[cfg(feature = "gpu")]
2292 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2293 Backend::None => false,
2294 }
2295}
2296
2297static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2313static 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)];
2317
2318const GRAPH_RACE_SAMPLES: u32 = 4;
2320
2321static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2331
2332pub fn graph_mark_unsupported() {
2337 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2338 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2339 }
2340}
2341
2342pub fn graph_unsupported() -> bool {
2343 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2344}
2345
2346pub fn graph_unsupported_reset() {
2348 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2349}
2350
2351pub fn graph_race_begin_generation() {
2352 #[cfg(feature = "gpu")]
2357 {
2358 static FLUSHED: std::sync::Once = std::sync::Once::new();
2370 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2371 if FIRST.swap(false, Ordering::Relaxed) {
2372 } else {
2374 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2375 }
2376 }
2377 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2378 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2379 return;
2380 }
2381 let (gn, cn) = (
2382 GRAPH_N[1].load(Ordering::Relaxed),
2383 GRAPH_N[0].load(Ordering::Relaxed),
2384 );
2385 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2386 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2387 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2388 let verdict = if g_avg < c_avg { 1 } else { 2 };
2389 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2390 tracing::info!(
2391 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2392 g_avg as f64 / 1e6,
2393 c_avg as f64 / 1e6,
2394 if verdict == 1 { "graph" } else { "normal path" }
2395 );
2396 return;
2397 }
2398 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2399 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2400}
2401
2402pub fn graph_race_use_graph(trusted: bool) -> bool {
2406 if trusted {
2407 return true;
2408 }
2409 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2410 1 => true,
2411 2 => false,
2412 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2413 }
2414}
2415
2416pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2421 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2422 return false;
2423 }
2424 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2425 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2426 if !first || cn == 0 {
2427 return false;
2428 }
2429 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2430 let ns = dur.as_nanos() as u64;
2431 if ns > 1_000_000_000 && ns > 4 * c_avg {
2432 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2433 tracing::info!(
2434 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2435 ns as f64 / 1e6,
2436 c_avg as f64 / 1e6
2437 );
2438 return true;
2439 }
2440 false
2441}
2442
2443pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2447 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2448 return;
2449 }
2450 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2451 if tok == 0 {
2452 return;
2453 }
2454 let i = used_graph as usize;
2455 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2456 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2457}
2458
2459pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2469 #[inline]
2470 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2471 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2472 for c in chunks.chunks_exact(8) {
2473 h ^= u64::from_le_bytes(c.try_into().unwrap());
2474 h = h.wrapping_mul(0x100_0000_01b3);
2475 }
2476 for &b in tail {
2477 h ^= b as u64;
2478 h = h.wrapping_mul(0x100_0000_01b3);
2479 }
2480 h
2481 }
2482 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2483 if data.len() <= 4096 {
2484 return fnv(h, data);
2485 }
2486 let step = (data.len() - 64) / 63;
2487 for i in 0..64 {
2488 h = fnv(h, &data[i * step..i * step + 64]);
2489 }
2490 h
2491}
2492
2493pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2496 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2497 fp_bytes(bytes)
2498}
2499
2500#[cfg(test)]
2501mod fp_tests {
2502 use super::fp_bytes;
2503
2504 #[test]
2509 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2510 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2512 let h0 = fp_bytes(&base);
2513 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2514 let mut dense = base.clone();
2517 for b in dense.iter_mut() {
2518 *b = b.wrapping_add(1);
2519 }
2520 assert_ne!(
2521 h0,
2522 fp_bytes(&dense),
2523 "a fully different tensor slipped through"
2524 );
2525 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2528 let mut small = vec![3u8; 4096];
2531 let hs = fp_bytes(&small);
2532 small[2048] ^= 1;
2533 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2534 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2536 let v = vec![9u8; n];
2537 let _ = fp_bytes(&v); }
2539 }
2540}
2541
2542pub fn bake_release() {
2546 #[cfg(feature = "gpu")]
2547 crate::gpu_wgpu::bake_release();
2548}
2549
2550pub fn bake_precision_strict(on: bool) {
2554 #[cfg(feature = "gpu")]
2555 crate::gpu_wgpu::bake_precision_strict(on);
2556 #[cfg(not(feature = "gpu"))]
2557 let _ = on;
2558}
2559
2560pub fn hostprof_encode_done(t0: std::time::Instant) {
2566 use std::sync::atomic::{AtomicU64, Ordering};
2567 static ENC: AtomicU64 = AtomicU64::new(0);
2568 static N: AtomicU64 = AtomicU64::new(0);
2569 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2570 return;
2571 }
2572 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2573 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2574 if n % 100 == 0 {
2575 eprintln!(
2576 "hostprof: encode {:.2} ms/token over {n} tokens",
2577 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2578 );
2579 }
2580}
2581
2582pub fn hostprof_total(t0: std::time::Instant) {
2583 use std::sync::atomic::{AtomicU64, Ordering};
2584 static TOT: AtomicU64 = AtomicU64::new(0);
2585 static N: AtomicU64 = AtomicU64::new(0);
2586 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2587 return;
2588 }
2589 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2590 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2591 if n % 100 == 0 {
2592 eprintln!(
2593 "hostprof: total {:.2} ms/token over {n} tokens",
2594 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2595 );
2596 }
2597}
2598
2599pub fn stageprof(stage: u32, dt: std::time::Duration) {
2603 use std::sync::atomic::{AtomicU64, Ordering};
2604 static NS: [AtomicU64; 4] = [
2605 AtomicU64::new(0),
2606 AtomicU64::new(0),
2607 AtomicU64::new(0),
2608 AtomicU64::new(0),
2609 ];
2610 static N: AtomicU64 = AtomicU64::new(0);
2611 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2612 return;
2613 }
2614 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2615 if stage == 1 {
2616 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2617 if n % 200 == 0 {
2618 eprintln!(
2619 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2620 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2621 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2622 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2623 );
2624 }
2625 }
2626}
2627
2628pub fn weight_bytes_dispatched() -> u64 {
2631 let mut total = 0u64;
2632 #[cfg(target_os = "macos")]
2633 {
2634 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2635 }
2636 #[cfg(feature = "gpu")]
2637 {
2638 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2639 }
2640 total
2641}
2642
2643pub fn weight_bytes_by() -> [u64; 6] {
2646 #[cfg(target_os = "macos")]
2647 {
2648 let mut o = [0u64; 6];
2649 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2650 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2651 }
2652 return o;
2653 }
2654 #[allow(unreachable_code)]
2655 [0; 6]
2656}