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!("{}\t{}\t{}", env!("CARGO_PKG_VERSION"), device_label(), class)
106}
107
108const CLASS_NAMES: [&str; 7] = [
109 "ffn",
110 "matvec",
111 "matmat",
112 "qkv-batch",
113 "matmat-wide",
114 "lm-head",
115 "gemm-nt",
116];
117
118fn probe_cache_load() {
127 static ONCE: std::sync::Once = std::sync::Once::new();
128 ONCE.call_once(|| {
129 let Some(path) = probe_cache_path() else {
130 return;
131 };
132 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
137 return;
138 }
139 let Ok(text) = std::fs::read_to_string(&path) else {
140 return;
141 };
142 probe_cache_adopt(&text);
143 });
144}
145
146fn probe_cache_adopt(text: &str) {
150 for line in text.lines() {
151 let Some((key, verdict)) = line.rsplit_once('\t') else {
152 continue;
153 };
154 let winner = match verdict.trim() {
155 "gpu" => 1u8,
156 "cpu" => 2u8,
157 _ => continue,
158 };
159 for (i, name) in CLASS_NAMES.iter().enumerate() {
160 if probe_cache_key_named(name) == key {
161 let _ =
162 PROBES[i]
163 .state
164 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed);
165 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
166 }
167 }
168 }
169}
170
171fn probe_cache_store(c: OpClass, winner: u8) {
174 let Some(path) = probe_cache_path() else {
175 return;
176 };
177 let line = format!(
178 "{}\t{}\n",
179 probe_cache_key_named(CLASS_NAMES[c as usize]),
180 if winner == 1 { "gpu" } else { "cpu" }
181 );
182 use std::io::Write;
183 if let Ok(mut f) = std::fs::OpenOptions::new()
184 .create(true)
185 .append(true)
186 .open(&path)
187 {
188 let _ = f.write_all(line.as_bytes());
189 }
190}
191
192pub(crate) fn probe_note_cold() {
195 PROBE_COLD.with(|c| c.set(true));
196}
197
198pub(crate) fn probe_was_cold() -> bool {
202 PROBE_COLD.with(|c| c.get())
203}
204
205pub fn set_layer(l: i64) {
207 CUR_LAYER.with(|c| c.set(l));
208}
209
210pub fn cur_layer() -> i64 {
212 CUR_LAYER.with(|c| c.get())
213}
214
215fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
218 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
219 R.get_or_init(|| {
220 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
221 let mut v = Vec::new();
222 for part in s.split(',') {
223 let part = part.trim();
224 match part.split_once('-') {
225 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
226 None => {
227 let x: i64 = part.parse().ok()?;
228 v.push((x, x));
229 }
230 }
231 }
232 Some(v)
233 })
234}
235
236fn layer_allowed() -> bool {
237 match layer_ranges() {
238 None => true,
239 Some(ranges) => {
240 let cur = CUR_LAYER.with(|c| c.get());
241 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
242 }
243 }
244}
245
246pub fn enabled_here() -> bool {
250 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
251}
252
253#[derive(Clone, Copy)]
265pub enum OpClass {
266 Ffn = 0,
268 Matvec = 1,
270 Matmat = 2,
272 Batch = 3,
274 MatmatWide = 4,
280 MatvecHead = 5,
287 GemmNt = 6,
294}
295
296pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
300 if rows * cols >= 67_108_864 {
301 OpClass::MatvecHead
302 } else {
303 OpClass::Matvec
304 }
305}
306
307pub enum ProbeArm {
309 Gpu,
311 CpuTimed,
313 Cpu,
315}
316
317const PROBE_SAMPLES: u32 = 6;
319
320struct Probe {
321 state: AtomicU8,
323 flip: AtomicU32,
324 gpu_ns: AtomicU64,
325 gpu_n: AtomicU32,
326 cpu_ns: AtomicU64,
327 cpu_n: AtomicU32,
328 gpu_min: AtomicU64,
335 cpu_min: AtomicU64,
336}
337
338impl Probe {
339 const fn new() -> Self {
340 Self {
341 state: AtomicU8::new(0),
342 flip: AtomicU32::new(0),
343 gpu_ns: AtomicU64::new(0),
344 gpu_n: AtomicU32::new(0),
345 cpu_ns: AtomicU64::new(0),
346 cpu_n: AtomicU32::new(0),
347 gpu_min: AtomicU64::new(u64::MAX),
348 cpu_min: AtomicU64::new(u64::MAX),
349 }
350 }
351}
352
353static PROBES: [Probe; 7] = [
354 Probe::new(),
355 Probe::new(),
356 Probe::new(),
357 Probe::new(),
358 Probe::new(),
359 Probe::new(),
360 Probe::new(),
361];
362
363fn probe_on() -> bool {
364 static ON: OnceLock<bool> = OnceLock::new();
365 *ON.get_or_init(|| {
366 std::env::var("CMF_GPU_PROBE")
367 .map(|v| v != "0" && v != "off")
368 .unwrap_or(true)
369 })
370}
371
372pub fn q1_force() -> bool {
377 #[cfg(target_os = "macos")]
378 {
379 backend() == Backend::Metal
380 }
381 #[cfg(not(target_os = "macos"))]
382 {
383 false
384 }
385}
386
387pub fn fused_block_trusted() -> bool {
406 #[cfg(target_os = "macos")]
407 if backend() == Backend::Metal {
408 return true;
409 }
410 wgpu_graph_default()
411}
412
413pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
425 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
426 {
427 return crate::gpu_wgpu::weight_is_resident(model, idx);
428 }
429 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
430 {
431 let _ = (model, idx);
432 true
433 }
434}
435
436pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
437 if !weights_resident && probe_deciding(c) {
438 return ProbeArm::Gpu;
439 }
440 probe_arm(c)
441}
442
443pub fn probe_arm(c: OpClass) -> ProbeArm {
444 PROBE_COLD.with(|f| f.set(false));
449 if !probe_on() {
450 return ProbeArm::Gpu;
451 }
452 probe_cache_load();
453 let p = &PROBES[c as usize];
454 match p.state.load(Ordering::Relaxed) {
455 1 => ProbeArm::Gpu,
456 2 => ProbeArm::Cpu,
457 _ => {
458 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
459 ProbeArm::Gpu
460 } else {
461 ProbeArm::CpuTimed
462 }
463 }
464 }
465}
466
467pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
470 let p = &PROBES[c as usize];
471 if p.state.load(Ordering::Relaxed) != 0 {
472 return;
473 }
474 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
475 return; }
477 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
478 if gpu {
479 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
480 p.gpu_n.fetch_add(1, Ordering::Relaxed);
481 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
482 } else {
483 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
484 p.cpu_n.fetch_add(1, Ordering::Relaxed);
485 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
486 }
487 let (gn, cn) = (
488 p.gpu_n.load(Ordering::Relaxed),
489 p.cpu_n.load(Ordering::Relaxed),
490 );
491 if gn >= 2 && cn >= 2 {
492 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
496 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
497 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
507 return;
508 }
509 let winner = if g <= cp { 1 } else { 2 };
510 if p.state
511 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
512 .is_ok()
513 {
514 tracing::info!(
515 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
516 CLASS_NAMES[c as usize],
517 g / 1e6,
518 cp / 1e6,
519 if winner == 1 { "gpu" } else { "cpu" },
520 );
521 probe_cache_store(c, winner);
522 }
523 }
524}
525
526pub fn probe_deciding(c: OpClass) -> bool {
529 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
530}
531
532#[allow(unused_variables)]
542pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
543 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
544 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
545 let resident = match backend() {
546 #[cfg(target_os = "macos")]
547 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
548 #[cfg(feature = "gpu")]
549 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
550 Backend::None => false,
551 };
552 if !resident && may_upload {
553 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
554 }
555 resident
556}
557
558#[cfg(test)]
560pub(crate) fn probe_reset() {
561 for p in &PROBES {
562 p.state.store(0, Ordering::Relaxed);
563 p.flip.store(0, Ordering::Relaxed);
564 p.gpu_ns.store(0, Ordering::Relaxed);
565 p.gpu_n.store(0, Ordering::Relaxed);
566 p.cpu_ns.store(0, Ordering::Relaxed);
567 p.cpu_n.store(0, Ordering::Relaxed);
568 }
569}
570
571#[cfg(test)]
572mod probe_tests {
573 use super::*;
574 use std::time::Duration;
575
576 #[test]
579 fn probe_alternates_discards_cold_and_decides() {
580 probe_reset();
581 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
583 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
584
585 probe_note_cold();
589 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
590 for _ in 0..PROBE_SAMPLES {
591 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
592 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
593 }
594 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
595
596 for _ in 0..PROBE_SAMPLES {
598 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
599 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
600 }
601 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
602
603 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
605 CPU_ONLY.with(|c| assert!(!c.get()));
606 cpu_scope(|| {
607 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
608 CPU_ONLY.with(|c| assert!(c.get()));
609 });
610 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
611 CPU_ONLY.with(|c| assert!(!c.get()));
612 probe_reset();
613 }
614
615 #[test]
616 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
617 let mine = probe_cache_key_named("gemm-nt");
629 let state = || PROBES[OpClass::GemmNt as usize].state.load(Ordering::Relaxed);
630
631 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
633 assert_eq!(state(), 0);
634 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
636 assert_ne!(older, mine);
637 probe_cache_adopt(&format!("{older}\tgpu\n"));
638 assert_eq!(state(), 0);
639 probe_cache_adopt(&format!("{mine}\tcpu\n"));
641 assert_eq!(state(), 2);
642
643 PROBES[OpClass::GemmNt as usize]
644 .state
645 .store(0, Ordering::Relaxed);
646 }
647}
648
649pub const GPU_MIN_ROWS: usize = 65_536;
652
653pub fn min_rows() -> usize {
660 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
661 .ok()
662 .and_then(|v| v.parse().ok())
663 {
664 return v;
665 }
666 if discrete() { 4096 } else { GPU_MIN_ROWS }
667}
668
669pub fn discrete() -> bool {
671 match backend() {
672 #[cfg(feature = "gpu")]
673 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
674 #[cfg(target_os = "macos")]
675 Backend::Metal => false, Backend::None => false,
677 }
678}
679
680pub struct MoeJob<'a> {
684 pub gate: (usize, usize, usize, &'a [f32]),
685 pub up: (usize, usize, usize, &'a [f32]),
686 pub down: (usize, usize, usize, &'a [f32]),
687 pub xs_gate: Vec<f32>,
688 pub xs_up: Vec<f32>,
689 pub down_col: &'a [f32],
690 pub w: f32,
691 pub q1: bool,
694 pub q4t: bool,
697 pub q4tp: bool,
701 pub gu_q2: bool,
705 pub swiglu_limit: f32,
710}
711
712pub struct BatchJob<'a> {
714 pub idx: usize,
715 pub rows: usize,
716 pub cols: usize,
717 pub row_scale: &'a [f32],
718 pub xs: Vec<f32>,
719 pub layout: BatchLayout,
723}
724
725#[derive(Clone, Copy, PartialEq, Eq, Debug)]
728pub enum BatchLayout {
729 Q8,
730 Q1,
731 Q4t,
732 Q4tp,
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736enum Backend {
737 None,
738 #[cfg(target_os = "macos")]
739 Metal,
740 #[cfg(feature = "gpu")]
741 Wgpu,
742}
743
744fn backend() -> Backend {
745 #[cfg(feature = "gpu")]
746 if crate::gpu_wgpu::selected() {
747 return if crate::gpu_wgpu::enabled() {
748 Backend::Wgpu
749 } else {
750 Backend::None
751 };
752 }
753 #[cfg(target_os = "macos")]
754 if crate::gpu_metal::enabled() {
755 return Backend::Metal;
756 }
757 Backend::None
758}
759
760pub fn backend_available() -> bool {
766 #[cfg(target_os = "macos")]
767 {
768 true
770 }
771 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
772 {
773 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
774 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
775 }
776 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
777 {
778 false
779 }
780}
781
782pub fn enabled() -> bool {
783 backend() != Backend::None
784}
785
786pub fn wgpu_active() -> bool {
800 #[cfg(feature = "gpu")]
801 {
802 matches!(backend(), Backend::Wgpu)
803 }
804 #[cfg(not(feature = "gpu"))]
805 {
806 false
807 }
808}
809
810pub fn default_device() -> usize {
817 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
818 *D.get_or_init(|| {
819 std::env::var("CMF_GPU_ADAPTER")
820 .ok()
821 .and_then(|v| v.trim().parse::<usize>().ok())
822 .unwrap_or(0)
823 })
824}
825
826thread_local! {
827 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
828}
829
830pub fn current_device() -> usize {
832 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
833}
834
835pub fn set_current_device(i: usize) {
839 CUR_DEV.with(|c| c.set(Some(i)));
840}
841
842pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
844 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
845 let r = f();
846 CUR_DEV.with(|c| c.set(prev));
847 r
848}
849
850pub fn device_count() -> usize {
853 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
854 {
855 return crate::gpu_wgpu::adapter_count();
856 }
857 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
858 {
859 usize::from(backend_available())
860 }
861}
862
863pub fn vram_budget() -> u64 {
867 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
868 {
869 return crate::gpu_wgpu::device_vram_budget();
870 }
871 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
872 {
873 if backend_available() { u64::MAX } else { 0 }
874 }
875}
876
877pub fn upload_bytes() -> u64 {
881 #[cfg(feature = "gpu")]
882 {
883 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
884 }
885 #[cfg(not(feature = "gpu"))]
886 0
887}
888
889#[derive(Clone, Copy, PartialEq, Eq, Debug)]
901pub enum GraphPhase {
902 Prefill,
903 Decode,
904}
905
906pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
914 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
915 Some("0") => false,
916 Some("prefill") => phase == GraphPhase::Prefill,
917 Some(_) => true,
918 None => {
919 if wgpu_graph_default() {
920 return true;
921 }
922 let _ = phase;
927 false
928 }
929 }
930}
931
932pub fn wgpu_graph_default() -> bool {
933 #[cfg(feature = "gpu")]
934 {
935 matches!(backend(), Backend::Wgpu)
941 && (crate::gpu_wgpu::discrete_active()
942 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
943 }
944 #[cfg(not(feature = "gpu"))]
945 {
946 false
947 }
948}
949
950#[allow(clippy::too_many_arguments, unused_variables)]
952pub fn q8_matvec_range(
953 model: &Arc<CmfModel>,
954 idx: usize,
955 row0: usize,
956 row_scale: &[f32],
957 xs: &[f32],
958 rows: usize,
959 cols: usize,
960 out: &mut [f32],
961) -> bool {
962 match backend() {
963 #[cfg(target_os = "macos")]
964 Backend::Metal => {
965 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
966 }
967 #[cfg(feature = "gpu")]
968 Backend::Wgpu => {
969 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
970 }
971 Backend::None => false,
972 }
973}
974
975#[allow(clippy::too_many_arguments, unused_variables)]
978pub fn q8_matmat(
979 model: &Arc<CmfModel>,
980 idx: usize,
981 row_scale: &[f32],
982 pre: &[f32],
983 b: usize,
984 rows: usize,
985 cols: usize,
986 out: &mut [f32],
987) -> bool {
988 match backend() {
989 #[cfg(target_os = "macos")]
990 Backend::Metal => {
991 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
992 }
993 #[cfg(feature = "gpu")]
994 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
995 Backend::None => false,
996 }
997}
998
999#[allow(unused_variables)]
1002pub fn q1_matvec(
1003 model: &Arc<CmfModel>,
1004 idx: usize,
1005 xs: &[f32],
1006 rows: usize,
1007 cols: usize,
1008 out: &mut [f32],
1009) -> bool {
1010 match backend() {
1011 #[cfg(target_os = "macos")]
1012 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1013 #[cfg(feature = "gpu")]
1014 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1015 Backend::None => false,
1016 }
1017}
1018
1019#[allow(clippy::too_many_arguments)]
1023pub fn attn_dropin(
1024 model: &Arc<CmfModel>,
1025 kv_id: u64,
1026 layer: usize,
1027 normed: &[f32],
1028 wq_idx: usize,
1029 wk_idx: usize,
1030 wv_idx: usize,
1031 wo_idx: usize,
1032 q_norm: Option<&[f32]>,
1033 k_norm: Option<&[f32]>,
1034 invf: &[f32],
1035 nh: usize,
1036 nkv: usize,
1037 hd: usize,
1038 rd: usize,
1039 hidden: usize,
1040 pos: usize,
1041 cap: usize,
1042 gemma: bool,
1043 eps: f32,
1044 cpu_k: &[Vec<f32>],
1045 cpu_v: &[Vec<f32>],
1046 out: &mut [f32],
1047) -> bool {
1048 match backend() {
1049 #[cfg(feature = "gpu")]
1050 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1051 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1052 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1053 ),
1054 #[allow(unused_variables)]
1055 _ => false,
1056 }
1057}
1058
1059pub struct GraphW<'a> {
1063 pub idx: usize,
1064 pub kind: u8,
1065 pub row_scale: &'a [f32],
1066 pub data: &'a [f32],
1067}
1068
1069pub enum GraphAttn<'a> {
1072 Full {
1073 wq: GraphW<'a>,
1074 wk: GraphW<'a>,
1075 wv: GraphW<'a>,
1076 wo: GraphW<'a>,
1077 q_norm: Option<&'a [f32]>,
1078 k_norm: Option<&'a [f32]>,
1079 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1081 output_gate: bool,
1084 cpu_k: &'a [Vec<f32>],
1085 cpu_v: &'a [Vec<f32>],
1086 },
1087 Gdn {
1088 qkv: GraphW<'a>,
1089 z: GraphW<'a>,
1090 a: GraphW<'a>,
1091 b: GraphW<'a>,
1092 out: GraphW<'a>,
1093 conv1d: &'a [f32],
1094 a_log: &'a [f32],
1095 dt_bias: &'a [f32],
1096 norm: &'a [f32],
1097 nv: usize,
1098 nk: usize,
1099 dk: usize,
1100 dv: usize,
1101 kk: usize,
1102 cpu_state: &'a [f32],
1107 },
1108}
1109
1110pub struct GraphLayer<'a> {
1112 pub input_norm: &'a [f32],
1113 pub attn: GraphAttn<'a>,
1114 pub post_norm: &'a [f32],
1115 pub ffn: GraphFfn<'a>,
1116}
1117
1118pub enum GraphFfn<'a> {
1123 Dense {
1124 gate: GraphW<'a>,
1125 up: GraphW<'a>,
1126 down: GraphW<'a>,
1127 },
1128 Moe {
1129 router: GraphW<'a>,
1131 shared_gate: GraphW<'a>,
1133 experts: Vec<(usize, usize, usize)>,
1137 n_exp: usize,
1139 top_k: usize,
1140 inter: usize,
1141 norm_topk: bool,
1142 q4tp: bool,
1148 gu_q2: bool,
1152 },
1153}
1154
1155#[allow(clippy::too_many_arguments)]
1160pub fn forward_token_graph(
1161 model: &Arc<CmfModel>,
1162 kv_id: u64,
1163 layers: &[GraphLayer],
1164 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1167 o1_epoch: u64,
1168 invf: &[f32],
1169 h: &mut [f32],
1170 nh: usize,
1171 nkv: usize,
1172 hd: usize,
1173 rd: usize,
1174 hidden: usize,
1175 inter: usize,
1176 position: usize,
1177 cap: usize,
1178 gemma: bool,
1179 eps: f32,
1180 lm_head: Option<(&GraphW, usize)>,
1181 final_norm: &[f32],
1182 logits: &mut Vec<f32>,
1183 loop_norm_at: &[usize],
1184 steps: usize,
1185 embed: Option<(&GraphW, usize, f32)>,
1186 ids_out: Option<&mut Vec<u32>>,
1187 layers_run: Option<&mut usize>,
1190 layer_base: usize,
1194) -> bool {
1195 match backend() {
1196 #[cfg(feature = "gpu")]
1197 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1198 model,
1199 kv_id,
1200 layers,
1201 o1,
1202 o1_epoch,
1203 invf,
1204 h,
1205 nh,
1206 nkv,
1207 hd,
1208 rd,
1209 hidden,
1210 inter,
1211 position,
1212 cap,
1213 gemma,
1214 eps,
1215 lm_head,
1216 final_norm,
1217 logits,
1218 loop_norm_at,
1219 steps,
1220 embed,
1221 ids_out,
1222 layers_run,
1223 layer_base,
1224 ),
1225 #[allow(unused_variables)]
1226 _ => {
1227 let _ = (lm_head, final_norm, logits, loop_norm_at, layers_run, layer_base);
1228 false
1229 }
1230 }
1231}
1232
1233pub struct SpecTail<'a> {
1237 pub lm: GraphW<'a>,
1238 pub lm_rows: usize,
1239 pub final_norm: &'a [f32],
1240 pub logits_out: &'a mut Vec<f32>,
1241}
1242
1243#[allow(clippy::too_many_arguments)]
1247pub fn forward_batch_graph(
1248 model: &Arc<CmfModel>,
1249 kv_id: u64,
1250 layers: &[GraphLayer],
1251 invf: &[f32],
1252 h: &mut [f32],
1253 nh: usize,
1254 nkv: usize,
1255 hd: usize,
1256 rd: usize,
1257 hidden: usize,
1258 inter: usize,
1259 positions: &[usize],
1260 cap: usize,
1261 gemma: bool,
1262 eps: f32,
1263 k: usize,
1264 spec: Option<SpecTail<'_>>,
1265) -> bool {
1266 match backend() {
1267 #[cfg(feature = "gpu")]
1268 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1269 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1270 eps, k, spec,
1271 ),
1272 #[allow(unreachable_patterns)]
1273 _ => {
1274 let _ = spec;
1275 false
1276 }
1277 }
1278}
1279
1280pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1283 #[cfg(feature = "gpu")]
1284 if backend() == Backend::Wgpu {
1285 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1286 }
1287 #[allow(unreachable_code)]
1288 {
1289 let _ = (kv_id, slot);
1290 false
1291 }
1292}
1293
1294pub fn graph_kv_reset(_kv_id: u64) {
1296 #[cfg(feature = "gpu")]
1297 if backend() == Backend::Wgpu {
1298 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1299 }
1300}
1301
1302pub fn q1t_matvec(
1306 model: &Arc<CmfModel>,
1307 idx: usize,
1308 xs: &[f32],
1309 rows: usize,
1310 cols: usize,
1311 out: &mut [f32],
1312) -> bool {
1313 match backend() {
1314 #[cfg(target_os = "macos")]
1315 Backend::Metal => {
1316 if metal_q1t_enabled() {
1317 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1318 } else {
1319 false
1320 }
1321 }
1322 #[cfg(feature = "gpu")]
1323 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1324 Backend::None => false,
1325 }
1326}
1327
1328#[allow(unused_variables)]
1331pub fn q4b_matvec(
1332 model: &Arc<CmfModel>,
1333 idx: usize,
1334 xs: &[f32],
1335 rows: usize,
1336 cols: usize,
1337 out: &mut [f32],
1338) -> bool {
1339 match backend() {
1340 #[cfg(target_os = "macos")]
1341 Backend::Metal => false,
1342 #[cfg(feature = "gpu")]
1343 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1344 Backend::None => false,
1345 }
1346}
1347
1348pub fn q1t_matmat(
1351 model: &Arc<CmfModel>,
1352 idx: usize,
1353 xs: &[f32],
1354 b: usize,
1355 rows: usize,
1356 cols: usize,
1357 out: &mut [f32],
1358) -> bool {
1359 match backend() {
1360 #[cfg(target_os = "macos")]
1361 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1365 #[cfg(feature = "gpu")]
1366 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1367 Backend::None => false,
1368 }
1369}
1370
1371#[cfg(target_os = "macos")]
1375pub(crate) fn metal_q1t_enabled() -> bool {
1376 std::env::var("CMF_METAL_Q1T")
1377 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1378 .unwrap_or(true)
1379}
1380
1381pub fn q1_matmat(
1383 model: &Arc<CmfModel>,
1384 idx: usize,
1385 xs: &[f32],
1386 b: usize,
1387 rows: usize,
1388 cols: usize,
1389 out: &mut [f32],
1390) -> bool {
1391 match backend() {
1392 #[cfg(feature = "gpu")]
1393 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1394 #[allow(unused_variables)]
1395 _ => false,
1396 }
1397}
1398
1399static MM_KILL: AtomicBool = AtomicBool::new(false);
1404pub(crate) fn mm_killed() -> bool {
1405 MM_KILL.load(Ordering::Relaxed)
1406}
1407pub(crate) fn mm_kill() {
1408 MM_KILL.store(true, Ordering::Relaxed);
1409}
1410
1411#[allow(unused_variables, clippy::too_many_arguments)]
1416pub fn chunk_attend(
1417 q: &[f32],
1418 k: &[&[f32]],
1419 v: &[&[f32]],
1420 b: usize,
1421 s0: usize,
1422 nh: usize,
1423 nkv: usize,
1424 hd: usize,
1425 scale: f32,
1426 out: &mut [f32],
1427) -> bool {
1428 match backend() {
1429 #[cfg(feature = "gpu")]
1430 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1431 #[allow(unreachable_patterns)]
1432 _ => false,
1433 }
1434}
1435
1436#[allow(unused_variables, clippy::too_many_arguments)]
1440pub fn q4t_qkv(
1441 model: &Arc<CmfModel>,
1442 wq: usize,
1443 wk: usize,
1444 wv: usize,
1445 xs: &[f32],
1446 b: usize,
1447 cols: usize,
1448 rq: usize,
1449 rk: usize,
1450 rv: usize,
1451 out: &mut [f32],
1452) -> bool {
1453 match backend() {
1454 #[cfg(feature = "gpu")]
1455 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1456 #[allow(unreachable_patterns)]
1457 _ => false,
1458 }
1459}
1460
1461#[allow(unused_variables, clippy::too_many_arguments)]
1463#[allow(clippy::too_many_arguments, unused_variables)]
1467pub fn q4tp_ffn_packed(
1468 model: &Arc<CmfModel>,
1469 w1: usize,
1470 w2: usize,
1471 xs: &[f32],
1472 b: usize,
1473 hidden: usize,
1474 inter: usize,
1475 bias: Option<&[f32]>,
1476 out: &mut [f32],
1477) -> bool {
1478 match backend() {
1479 #[cfg(feature = "gpu")]
1480 Backend::Wgpu => {
1481 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1482 }
1483 #[allow(unreachable_patterns)]
1484 _ => false,
1485 }
1486}
1487
1488pub fn q4tp_ffn(
1489 model: &Arc<CmfModel>,
1490 w1: usize,
1491 w3: usize,
1492 w2: usize,
1493 xs: &[f32],
1494 b: usize,
1495 hidden: usize,
1496 inter: usize,
1497 out: &mut [f32],
1498) -> bool {
1499 match backend() {
1500 #[cfg(target_os = "macos")]
1501 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1502 #[cfg(feature = "gpu")]
1503 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1504 #[allow(unreachable_patterns)]
1505 _ => false,
1506 }
1507}
1508
1509pub fn q4t_ffn(
1510 model: &Arc<CmfModel>,
1511 w1: usize,
1512 w3: usize,
1513 w2: usize,
1514 xs: &[f32],
1515 b: usize,
1516 hidden: usize,
1517 inter: usize,
1518 out: &mut [f32],
1519) -> bool {
1520 match backend() {
1521 #[cfg(target_os = "macos")]
1522 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1523 #[cfg(feature = "gpu")]
1524 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1525 #[allow(unreachable_patterns)]
1526 _ => false,
1527 }
1528}
1529
1530pub struct DitBlockArgs<'a> {
1535 pub n: usize,
1536 pub hidden: usize,
1537 pub inter: usize,
1538 pub nh: usize,
1539 pub nkv: usize,
1540 pub hd: usize,
1541 pub eps: f32,
1542 pub rope_cos: &'a [f32],
1543 pub rope_sin: &'a [f32],
1544 pub norm1: &'a [f32],
1545 pub norm2: &'a [f32],
1546 pub ffn_norm1: &'a [f32],
1547 pub ffn_norm2: &'a [f32],
1548 pub norm_q: &'a [f32],
1549 pub norm_k: &'a [f32],
1550 pub s_msa: &'a [f32],
1551 pub gate_msa: &'a [f32],
1552 pub s_mlp: &'a [f32],
1553 pub gate_mlp: &'a [f32],
1554 pub wq: usize,
1555 pub wk: usize,
1556 pub wv: usize,
1557 pub wo: usize,
1558 pub w1: usize,
1559 pub w3: usize,
1560 pub w2: usize,
1561 pub q4tp: bool,
1565 pub resident_in: bool,
1568 pub resident_out: bool,
1572}
1573
1574pub fn dit_chain_supported() -> bool {
1578 #[cfg(feature = "gpu")]
1579 {
1580 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1581 }
1582 #[allow(unreachable_code)]
1583 false
1584}
1585
1586pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1589 #[cfg(feature = "gpu")]
1590 {
1591 if matches!(backend(), Backend::Wgpu) {
1592 return crate::gpu_wgpu::dit_state_fetch(_x);
1593 }
1594 }
1595 false
1596}
1597
1598#[allow(unused_variables)]
1602#[allow(unused_variables, clippy::too_many_arguments)]
1606pub fn dit_qkv(
1607 model: &Arc<CmfModel>,
1608 wq: usize,
1609 wk: usize,
1610 wv: usize,
1611 xs: &[f32],
1612 b: usize,
1613 hidden: usize,
1614 qrows: usize,
1615 kvrows: usize,
1616 q_out: &mut [f32],
1617 k_out: &mut [f32],
1618 v_out: &mut [f32],
1619) -> bool {
1620 match backend() {
1621 #[cfg(feature = "gpu")]
1622 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1623 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1624 ),
1625 #[allow(unreachable_patterns)]
1626 _ => false,
1627 }
1628}
1629
1630pub fn fused_dit_block_available() -> bool {
1634 #[cfg(target_os = "macos")]
1635 {
1636 matches!(backend(), Backend::Metal) && fused_block_trusted()
1637 }
1638 #[cfg(not(target_os = "macos"))]
1639 {
1640 false
1641 }
1642}
1643
1644pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1645 dit_block_seg(model, a, &[a.n], x)
1646}
1647
1648pub fn dit_block_seg(
1652 model: &Arc<CmfModel>,
1653 a: &DitBlockArgs,
1654 segs: &[usize],
1655 x: &mut [f32],
1656) -> bool {
1657 match backend() {
1658 #[cfg(target_os = "macos")]
1659 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1660 #[cfg(feature = "gpu")]
1667 Backend::Wgpu
1668 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1669 Some("0") => false,
1670 Some(_) => true,
1671 None => crate::gpu_wgpu::discrete_active(),
1672 } =>
1673 {
1674 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1675 }
1676 #[allow(unreachable_patterns)]
1677 _ => false,
1678 }
1679}
1680
1681pub struct VaeResnetArgs<'a> {
1685 pub groups: usize,
1686 pub ic: usize,
1687 pub oc: usize,
1688 pub h: usize,
1689 pub w: usize,
1690 pub n1w: &'a [f32],
1691 pub n1b: &'a [f32],
1692 pub c1w: &'a [f32],
1693 pub c1b: &'a [f32],
1694 pub c1k: usize,
1695 pub n2w: &'a [f32],
1696 pub n2b: &'a [f32],
1697 pub c2w: &'a [f32],
1698 pub c2b: &'a [f32],
1699 pub c2k: usize,
1700 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1701}
1702
1703#[allow(unused_variables)]
1706pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1707 match backend() {
1708 #[cfg(target_os = "macos")]
1709 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1710 _ => false,
1711 }
1712}
1713
1714#[allow(unused_variables, clippy::too_many_arguments)]
1717pub fn vae_upsample_conv(
1718 w: &[f32],
1719 bias: &[f32],
1720 x: &[f32],
1721 ic: usize,
1722 oc: usize,
1723 h: usize,
1724 w_img: usize,
1725 k: usize,
1726 out: &mut [f32],
1727) -> bool {
1728 match backend() {
1729 #[cfg(target_os = "macos")]
1730 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1731 #[cfg(feature = "gpu")]
1732 Backend::Wgpu => {
1733 crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out)
1734 }
1735 #[allow(unreachable_patterns)]
1736 _ => false,
1737 }
1738}
1739
1740#[allow(unused_variables, clippy::too_many_arguments)]
1743pub fn vae_conv2d(
1744 w: &[f32],
1745 bias: &[f32],
1746 x: &[f32],
1747 ic: usize,
1748 oc: usize,
1749 h: usize,
1750 w_img: usize,
1751 k: usize,
1752 out: &mut [f32],
1753) -> bool {
1754 match backend() {
1755 #[cfg(target_os = "macos")]
1756 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1757 #[cfg(feature = "gpu")]
1758 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1759 #[allow(unreachable_patterns)]
1760 _ => false,
1761 }
1762}
1763
1764#[allow(unused_variables, clippy::too_many_arguments)]
1768#[allow(unused_variables)]
1772#[allow(clippy::too_many_arguments)]
1773#[allow(clippy::too_many_arguments, unused_variables)]
1776pub fn dit_qkv_attention(
1777 model: &Arc<CmfModel>,
1778 qkv_idx: usize,
1779 xn: &[f32],
1780 n: usize,
1781 hidden: usize,
1782 nh: usize,
1783 hd: usize,
1784 scale: f32,
1785 nr: (&[f32], &[f32], &[f32], f32),
1786 out: &mut [f32],
1787) -> bool {
1788 match backend() {
1789 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1790 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1791 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1792 ),
1793 #[allow(unreachable_patterns)]
1794 _ => false,
1795 }
1796}
1797
1798#[allow(clippy::too_many_arguments)]
1801pub fn dit_qkv_attn_out(
1802 model: &Arc<CmfModel>,
1803 qkv_idx: usize,
1804 out_idx: usize,
1805 xn: &[f32],
1806 n: usize,
1807 hidden: usize,
1808 nh: usize,
1809 hd: usize,
1810 scale: f32,
1811 nr: (&[f32], &[f32], &[f32], f32),
1812 proj: &mut [f32],
1813) -> bool {
1814 match backend() {
1815 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1816 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1817 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1818 ),
1819 #[allow(unreachable_patterns)]
1820 _ => false,
1821 }
1822}
1823
1824#[allow(clippy::too_many_arguments)]
1826pub fn vae_qkv_attn_out(
1827 model: &Arc<CmfModel>,
1828 qkv_idx: usize,
1829 out_idx: usize,
1830 xn: &[f32],
1831 n: usize,
1832 dim: usize,
1833 nh: usize,
1834 hd: usize,
1835 scale: f32,
1836 angles: &[f32],
1837 eps: f32,
1838 qkv_bias: &[f32],
1839 proj: &mut [f32],
1840) -> bool {
1841 match backend() {
1842 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1843 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1844 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1845 ),
1846 #[allow(unreachable_patterns)]
1847 _ => false,
1848 }
1849}
1850
1851#[allow(clippy::too_many_arguments)]
1852pub fn vae_attention_packed(
1853 qkv: &[f32],
1854 nh: usize,
1855 n: usize,
1856 hd: usize,
1857 scale: f32,
1858 angles: &[f32],
1859 eps: f32,
1860 out: &mut [f32],
1861) -> bool {
1862 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
1863}
1864
1865#[allow(clippy::too_many_arguments)]
1866pub fn vae_attention_packed_layout(
1867 qkv: &[f32],
1868 nh: usize,
1869 n: usize,
1870 hd: usize,
1871 scale: f32,
1872 angles: &[f32],
1873 eps: f32,
1874 out: &mut [f32],
1875 layout: u32,
1876) -> bool {
1877 match backend() {
1878 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1879 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
1880 qkv, nh, n, hd, scale, angles, eps, out, layout,
1881 ),
1882 #[allow(unreachable_patterns)]
1883 _ => false,
1884 }
1885}
1886
1887#[allow(clippy::too_many_arguments)]
1888pub fn dit_split_only(
1889 qkv: &[f32],
1890 nh: usize,
1891 n: usize,
1892 hd: usize,
1893 layout: u32,
1894 norm: Option<(&[f32], f32)>,
1895 out_q: &mut [f32],
1896) -> bool {
1897 match backend() {
1898 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1899 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
1900 #[allow(unreachable_patterns)]
1901 _ => false,
1902 }
1903}
1904
1905pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
1910 match backend() {
1911 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1912 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
1913 #[allow(unreachable_patterns)]
1914 _ => false,
1915 }
1916}
1917
1918#[allow(clippy::too_many_arguments)]
1920pub fn vae_conv2d_coop(
1921 w: &[f32],
1922 bias: Option<&[f32]>,
1923 x: &[f32],
1924 ic: usize,
1925 oc: usize,
1926 h: usize,
1927 wi: usize,
1928 k: usize,
1929 out: &mut [f32],
1930) -> bool {
1931 match backend() {
1932 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1933 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
1934 #[allow(unreachable_patterns)]
1935 _ => false,
1936 }
1937}
1938
1939pub fn dit_attention_packed(
1940 qkv: &[f32],
1941 nh: usize,
1942 n: usize,
1943 hd: usize,
1944 scale: f32,
1945 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1948 out: &mut [f32],
1949) -> bool {
1950 match backend() {
1951 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1952 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
1953 #[allow(unreachable_patterns)]
1954 _ => false,
1955 }
1956}
1957
1958pub fn dit_attention(
1959 qh: &[f32],
1960 kh: &[f32],
1961 vh: &[f32],
1962 nh: usize,
1963 nkv: usize,
1964 n: usize,
1965 hd: usize,
1966 scale: f32,
1967 out: &mut [f32],
1968) -> bool {
1969 match backend() {
1970 #[cfg(target_os = "macos")]
1971 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1972 #[cfg(feature = "gpu")]
1973 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1974 #[allow(unreachable_patterns)]
1975 _ => false,
1976 }
1977}
1978
1979#[allow(unused_variables)]
1984pub fn q4tp_matmat(
1985 model: &Arc<CmfModel>,
1986 idx: usize,
1987 xs: &[f32],
1988 b: usize,
1989 rows: usize,
1990 cols: usize,
1991 out: &mut [f32],
1992) -> bool {
1993 match backend() {
1994 #[cfg(target_os = "macos")]
1995 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1996 #[cfg(feature = "gpu")]
1997 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1998 #[allow(unreachable_patterns)]
1999 _ => false,
2000 }
2001}
2002
2003pub fn q2tp_matmat(
2006 model: &Arc<CmfModel>,
2007 idx: usize,
2008 xs: &[f32],
2009 b: usize,
2010 rows: usize,
2011 cols: usize,
2012 out: &mut [f32],
2013) -> bool {
2014 match backend() {
2015 #[cfg(feature = "gpu")]
2016 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2017 #[allow(unreachable_patterns)]
2018 _ => false,
2019 }
2020}
2021
2022pub fn q4tp_matvec(
2027 model: &Arc<CmfModel>,
2028 idx: usize,
2029 xs: &[f32],
2030 rows: usize,
2031 cols: usize,
2032 out: &mut [f32],
2033) -> bool {
2034 match backend() {
2035 #[cfg(target_os = "macos")]
2036 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2037 #[cfg(feature = "gpu")]
2038 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2039 #[allow(unreachable_patterns)]
2040 _ => false,
2041 }
2042}
2043
2044pub fn q4t_matvec(
2050 model: &Arc<CmfModel>,
2051 idx: usize,
2052 xs: &[f32],
2053 rows: usize,
2054 cols: usize,
2055 out: &mut [f32],
2056) -> bool {
2057 match backend() {
2058 #[cfg(target_os = "macos")]
2059 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2060 #[allow(unreachable_patterns)]
2061 _ => false,
2062 }
2063}
2064
2065pub fn q4t_matmat(
2066 model: &Arc<CmfModel>,
2067 idx: usize,
2068 xs: &[f32],
2069 b: usize,
2070 rows: usize,
2071 cols: usize,
2072 out: &mut [f32],
2073) -> bool {
2074 match backend() {
2075 #[cfg(target_os = "macos")]
2076 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2077 #[cfg(feature = "gpu")]
2078 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2079 #[allow(unreachable_patterns)]
2080 _ => false,
2081 }
2082}
2083
2084#[cfg(target_os = "macos")]
2086pub use crate::gpu_metal::{
2087 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2088 TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2089};
2090
2091#[cfg(target_os = "macos")]
2093pub fn gdn_block(
2094 model: &Arc<CmfModel>,
2095 layers: &[GdnGpuLayer],
2096 states: &mut [&mut [f32]],
2097 cfg: &GdnGpuCfg,
2098 h: &mut [f32],
2099) -> bool {
2100 match backend() {
2101 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2102 _ => false,
2103 }
2104}
2105
2106#[allow(unused_variables)]
2108pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2109 match backend() {
2110 #[cfg(target_os = "macos")]
2111 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2112 #[cfg(feature = "gpu")]
2113 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2114 Backend::None => false,
2115 }
2116}
2117
2118#[allow(unused_variables)]
2120pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2121 match backend() {
2122 #[cfg(target_os = "macos")]
2123 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2124 #[cfg(feature = "gpu")]
2125 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2126 Backend::None => false,
2127 }
2128}
2129
2130static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2146static 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)];
2150
2151const GRAPH_RACE_SAMPLES: u32 = 4;
2153
2154static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2164
2165pub fn graph_mark_unsupported() {
2170 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2171 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2172 }
2173}
2174
2175pub fn graph_unsupported() -> bool {
2176 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2177}
2178
2179pub fn graph_unsupported_reset() {
2181 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2182}
2183
2184pub fn graph_race_begin_generation() {
2185 #[cfg(feature = "gpu")]
2190 {
2191 static FLUSHED: std::sync::Once = std::sync::Once::new();
2203 static FIRST: std::sync::atomic::AtomicBool =
2204 std::sync::atomic::AtomicBool::new(true);
2205 if FIRST.swap(false, Ordering::Relaxed) {
2206 } else {
2208 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2209 }
2210 }
2211 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2212 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2213 return;
2214 }
2215 let (gn, cn) = (
2216 GRAPH_N[1].load(Ordering::Relaxed),
2217 GRAPH_N[0].load(Ordering::Relaxed),
2218 );
2219 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2220 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2221 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2222 let verdict = if g_avg < c_avg { 1 } else { 2 };
2223 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2224 tracing::info!(
2225 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2226 g_avg as f64 / 1e6,
2227 c_avg as f64 / 1e6,
2228 if verdict == 1 { "graph" } else { "normal path" }
2229 );
2230 return;
2231 }
2232 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2233 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2234}
2235
2236pub fn graph_race_use_graph(trusted: bool) -> bool {
2240 if trusted {
2241 return true;
2242 }
2243 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2244 1 => true,
2245 2 => false,
2246 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2247 }
2248}
2249
2250pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2255 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2256 return false;
2257 }
2258 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2259 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2260 if !first || cn == 0 {
2261 return false;
2262 }
2263 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2264 let ns = dur.as_nanos() as u64;
2265 if ns > 1_000_000_000 && ns > 4 * c_avg {
2266 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2267 tracing::info!(
2268 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2269 ns as f64 / 1e6,
2270 c_avg as f64 / 1e6
2271 );
2272 return true;
2273 }
2274 false
2275}
2276
2277pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2281 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2282 return;
2283 }
2284 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2285 if tok == 0 {
2286 return;
2287 }
2288 let i = used_graph as usize;
2289 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2290 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2291}
2292
2293pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2303 #[inline]
2304 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2305 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2306 for c in chunks.chunks_exact(8) {
2307 h ^= u64::from_le_bytes(c.try_into().unwrap());
2308 h = h.wrapping_mul(0x100_0000_01b3);
2309 }
2310 for &b in tail {
2311 h ^= b as u64;
2312 h = h.wrapping_mul(0x100_0000_01b3);
2313 }
2314 h
2315 }
2316 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2317 if data.len() <= 4096 {
2318 return fnv(h, data);
2319 }
2320 let step = (data.len() - 64) / 63;
2321 for i in 0..64 {
2322 h = fnv(h, &data[i * step..i * step + 64]);
2323 }
2324 h
2325}
2326
2327pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2330 let bytes =
2331 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2332 fp_bytes(bytes)
2333}
2334
2335#[cfg(test)]
2336mod fp_tests {
2337 use super::fp_bytes;
2338
2339 #[test]
2344 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2345 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2347 let h0 = fp_bytes(&base);
2348 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2349 let mut dense = base.clone();
2352 for b in dense.iter_mut() {
2353 *b = b.wrapping_add(1);
2354 }
2355 assert_ne!(h0, fp_bytes(&dense), "a fully different tensor slipped through");
2356 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2359 let mut small = vec![3u8; 4096];
2362 let hs = fp_bytes(&small);
2363 small[2048] ^= 1;
2364 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2365 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2367 let v = vec![9u8; n];
2368 let _ = fp_bytes(&v); }
2370 }
2371}
2372
2373pub fn bake_release() {
2377 #[cfg(feature = "gpu")]
2378 crate::gpu_wgpu::bake_release();
2379}
2380
2381pub fn bake_precision_strict(on: bool) {
2385 #[cfg(feature = "gpu")]
2386 crate::gpu_wgpu::bake_precision_strict(on);
2387 #[cfg(not(feature = "gpu"))]
2388 let _ = on;
2389}