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
889pub fn wgpu_graph_default() -> bool {
890 #[cfg(feature = "gpu")]
891 {
892 matches!(backend(), Backend::Wgpu)
898 && (crate::gpu_wgpu::discrete_active()
899 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
900 }
901 #[cfg(not(feature = "gpu"))]
902 {
903 false
904 }
905}
906
907#[allow(clippy::too_many_arguments, unused_variables)]
909pub fn q8_matvec_range(
910 model: &Arc<CmfModel>,
911 idx: usize,
912 row0: usize,
913 row_scale: &[f32],
914 xs: &[f32],
915 rows: usize,
916 cols: usize,
917 out: &mut [f32],
918) -> bool {
919 match backend() {
920 #[cfg(target_os = "macos")]
921 Backend::Metal => {
922 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
923 }
924 #[cfg(feature = "gpu")]
925 Backend::Wgpu => {
926 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
927 }
928 Backend::None => false,
929 }
930}
931
932#[allow(clippy::too_many_arguments, unused_variables)]
935pub fn q8_matmat(
936 model: &Arc<CmfModel>,
937 idx: usize,
938 row_scale: &[f32],
939 pre: &[f32],
940 b: usize,
941 rows: usize,
942 cols: usize,
943 out: &mut [f32],
944) -> bool {
945 match backend() {
946 #[cfg(target_os = "macos")]
947 Backend::Metal => {
948 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
949 }
950 #[cfg(feature = "gpu")]
951 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
952 Backend::None => false,
953 }
954}
955
956#[allow(unused_variables)]
959pub fn q1_matvec(
960 model: &Arc<CmfModel>,
961 idx: usize,
962 xs: &[f32],
963 rows: usize,
964 cols: usize,
965 out: &mut [f32],
966) -> bool {
967 match backend() {
968 #[cfg(target_os = "macos")]
969 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
970 #[cfg(feature = "gpu")]
971 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
972 Backend::None => false,
973 }
974}
975
976#[allow(clippy::too_many_arguments)]
980pub fn attn_dropin(
981 model: &Arc<CmfModel>,
982 kv_id: u64,
983 layer: usize,
984 normed: &[f32],
985 wq_idx: usize,
986 wk_idx: usize,
987 wv_idx: usize,
988 wo_idx: usize,
989 q_norm: Option<&[f32]>,
990 k_norm: Option<&[f32]>,
991 invf: &[f32],
992 nh: usize,
993 nkv: usize,
994 hd: usize,
995 rd: usize,
996 hidden: usize,
997 pos: usize,
998 cap: usize,
999 gemma: bool,
1000 eps: f32,
1001 cpu_k: &[Vec<f32>],
1002 cpu_v: &[Vec<f32>],
1003 out: &mut [f32],
1004) -> bool {
1005 match backend() {
1006 #[cfg(feature = "gpu")]
1007 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1008 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1009 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1010 ),
1011 #[allow(unused_variables)]
1012 _ => false,
1013 }
1014}
1015
1016pub struct GraphW<'a> {
1020 pub idx: usize,
1021 pub kind: u8,
1022 pub row_scale: &'a [f32],
1023 pub data: &'a [f32],
1024}
1025
1026pub enum GraphAttn<'a> {
1029 Full {
1030 wq: GraphW<'a>,
1031 wk: GraphW<'a>,
1032 wv: GraphW<'a>,
1033 wo: GraphW<'a>,
1034 q_norm: Option<&'a [f32]>,
1035 k_norm: Option<&'a [f32]>,
1036 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1038 output_gate: bool,
1041 cpu_k: &'a [Vec<f32>],
1042 cpu_v: &'a [Vec<f32>],
1043 },
1044 Gdn {
1045 qkv: GraphW<'a>,
1046 z: GraphW<'a>,
1047 a: GraphW<'a>,
1048 b: GraphW<'a>,
1049 out: GraphW<'a>,
1050 conv1d: &'a [f32],
1051 a_log: &'a [f32],
1052 dt_bias: &'a [f32],
1053 norm: &'a [f32],
1054 nv: usize,
1055 nk: usize,
1056 dk: usize,
1057 dv: usize,
1058 kk: usize,
1059 cpu_state: &'a [f32],
1064 },
1065}
1066
1067pub struct GraphLayer<'a> {
1069 pub input_norm: &'a [f32],
1070 pub attn: GraphAttn<'a>,
1071 pub post_norm: &'a [f32],
1072 pub ffn: GraphFfn<'a>,
1073}
1074
1075pub enum GraphFfn<'a> {
1080 Dense {
1081 gate: GraphW<'a>,
1082 up: GraphW<'a>,
1083 down: GraphW<'a>,
1084 },
1085 Moe {
1086 router: GraphW<'a>,
1088 shared_gate: GraphW<'a>,
1090 experts: Vec<(usize, usize, usize)>,
1094 n_exp: usize,
1096 top_k: usize,
1097 inter: usize,
1098 norm_topk: bool,
1099 q4tp: bool,
1105 gu_q2: bool,
1109 },
1110}
1111
1112#[allow(clippy::too_many_arguments)]
1117pub fn forward_token_graph(
1118 model: &Arc<CmfModel>,
1119 kv_id: u64,
1120 layers: &[GraphLayer],
1121 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1124 o1_epoch: u64,
1125 invf: &[f32],
1126 h: &mut [f32],
1127 nh: usize,
1128 nkv: usize,
1129 hd: usize,
1130 rd: usize,
1131 hidden: usize,
1132 inter: usize,
1133 position: usize,
1134 cap: usize,
1135 gemma: bool,
1136 eps: f32,
1137 lm_head: Option<(&GraphW, usize)>,
1138 final_norm: &[f32],
1139 logits: &mut Vec<f32>,
1140 loop_norm_at: &[usize],
1141 steps: usize,
1142 embed: Option<(&GraphW, usize, f32)>,
1143 ids_out: Option<&mut Vec<u32>>,
1144 layers_run: Option<&mut usize>,
1147 layer_base: usize,
1151) -> bool {
1152 match backend() {
1153 #[cfg(feature = "gpu")]
1154 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1155 model,
1156 kv_id,
1157 layers,
1158 o1,
1159 o1_epoch,
1160 invf,
1161 h,
1162 nh,
1163 nkv,
1164 hd,
1165 rd,
1166 hidden,
1167 inter,
1168 position,
1169 cap,
1170 gemma,
1171 eps,
1172 lm_head,
1173 final_norm,
1174 logits,
1175 loop_norm_at,
1176 steps,
1177 embed,
1178 ids_out,
1179 layers_run,
1180 layer_base,
1181 ),
1182 #[allow(unused_variables)]
1183 _ => {
1184 let _ = (lm_head, final_norm, logits, loop_norm_at, layers_run, layer_base);
1185 false
1186 }
1187 }
1188}
1189
1190pub struct SpecTail<'a> {
1194 pub lm: GraphW<'a>,
1195 pub lm_rows: usize,
1196 pub final_norm: &'a [f32],
1197 pub logits_out: &'a mut Vec<f32>,
1198}
1199
1200#[allow(clippy::too_many_arguments)]
1204pub fn forward_batch_graph(
1205 model: &Arc<CmfModel>,
1206 kv_id: u64,
1207 layers: &[GraphLayer],
1208 invf: &[f32],
1209 h: &mut [f32],
1210 nh: usize,
1211 nkv: usize,
1212 hd: usize,
1213 rd: usize,
1214 hidden: usize,
1215 inter: usize,
1216 positions: &[usize],
1217 cap: usize,
1218 gemma: bool,
1219 eps: f32,
1220 k: usize,
1221 spec: Option<SpecTail<'_>>,
1222) -> bool {
1223 match backend() {
1224 #[cfg(feature = "gpu")]
1225 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1226 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1227 eps, k, spec,
1228 ),
1229 #[allow(unreachable_patterns)]
1230 _ => {
1231 let _ = spec;
1232 false
1233 }
1234 }
1235}
1236
1237pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1240 #[cfg(feature = "gpu")]
1241 if backend() == Backend::Wgpu {
1242 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1243 }
1244 #[allow(unreachable_code)]
1245 {
1246 let _ = (kv_id, slot);
1247 false
1248 }
1249}
1250
1251pub fn graph_kv_reset(_kv_id: u64) {
1253 #[cfg(feature = "gpu")]
1254 if backend() == Backend::Wgpu {
1255 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1256 }
1257}
1258
1259pub fn q1t_matvec(
1263 model: &Arc<CmfModel>,
1264 idx: usize,
1265 xs: &[f32],
1266 rows: usize,
1267 cols: usize,
1268 out: &mut [f32],
1269) -> bool {
1270 match backend() {
1271 #[cfg(target_os = "macos")]
1272 Backend::Metal => {
1273 if metal_q1t_enabled() {
1274 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1275 } else {
1276 false
1277 }
1278 }
1279 #[cfg(feature = "gpu")]
1280 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1281 Backend::None => false,
1282 }
1283}
1284
1285#[allow(unused_variables)]
1288pub fn q4b_matvec(
1289 model: &Arc<CmfModel>,
1290 idx: usize,
1291 xs: &[f32],
1292 rows: usize,
1293 cols: usize,
1294 out: &mut [f32],
1295) -> bool {
1296 match backend() {
1297 #[cfg(target_os = "macos")]
1298 Backend::Metal => false,
1299 #[cfg(feature = "gpu")]
1300 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1301 Backend::None => false,
1302 }
1303}
1304
1305pub fn q1t_matmat(
1308 model: &Arc<CmfModel>,
1309 idx: usize,
1310 xs: &[f32],
1311 b: usize,
1312 rows: usize,
1313 cols: usize,
1314 out: &mut [f32],
1315) -> bool {
1316 match backend() {
1317 #[cfg(target_os = "macos")]
1318 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1322 #[cfg(feature = "gpu")]
1323 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1324 Backend::None => false,
1325 }
1326}
1327
1328#[cfg(target_os = "macos")]
1332pub(crate) fn metal_q1t_enabled() -> bool {
1333 std::env::var("CMF_METAL_Q1T")
1334 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1335 .unwrap_or(true)
1336}
1337
1338pub fn q1_matmat(
1340 model: &Arc<CmfModel>,
1341 idx: usize,
1342 xs: &[f32],
1343 b: usize,
1344 rows: usize,
1345 cols: usize,
1346 out: &mut [f32],
1347) -> bool {
1348 match backend() {
1349 #[cfg(feature = "gpu")]
1350 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1351 #[allow(unused_variables)]
1352 _ => false,
1353 }
1354}
1355
1356static MM_KILL: AtomicBool = AtomicBool::new(false);
1361pub(crate) fn mm_killed() -> bool {
1362 MM_KILL.load(Ordering::Relaxed)
1363}
1364pub(crate) fn mm_kill() {
1365 MM_KILL.store(true, Ordering::Relaxed);
1366}
1367
1368#[allow(unused_variables, clippy::too_many_arguments)]
1373pub fn chunk_attend(
1374 q: &[f32],
1375 k: &[&[f32]],
1376 v: &[&[f32]],
1377 b: usize,
1378 s0: usize,
1379 nh: usize,
1380 nkv: usize,
1381 hd: usize,
1382 scale: f32,
1383 out: &mut [f32],
1384) -> bool {
1385 match backend() {
1386 #[cfg(feature = "gpu")]
1387 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1388 #[allow(unreachable_patterns)]
1389 _ => false,
1390 }
1391}
1392
1393#[allow(unused_variables, clippy::too_many_arguments)]
1397pub fn q4t_qkv(
1398 model: &Arc<CmfModel>,
1399 wq: usize,
1400 wk: usize,
1401 wv: usize,
1402 xs: &[f32],
1403 b: usize,
1404 cols: usize,
1405 rq: usize,
1406 rk: usize,
1407 rv: usize,
1408 out: &mut [f32],
1409) -> bool {
1410 match backend() {
1411 #[cfg(feature = "gpu")]
1412 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1413 #[allow(unreachable_patterns)]
1414 _ => false,
1415 }
1416}
1417
1418#[allow(unused_variables, clippy::too_many_arguments)]
1420#[allow(clippy::too_many_arguments, unused_variables)]
1424pub fn q4tp_ffn_packed(
1425 model: &Arc<CmfModel>,
1426 w1: usize,
1427 w2: usize,
1428 xs: &[f32],
1429 b: usize,
1430 hidden: usize,
1431 inter: usize,
1432 bias: Option<&[f32]>,
1433 out: &mut [f32],
1434) -> bool {
1435 match backend() {
1436 #[cfg(feature = "gpu")]
1437 Backend::Wgpu => {
1438 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1439 }
1440 #[allow(unreachable_patterns)]
1441 _ => false,
1442 }
1443}
1444
1445pub fn q4tp_ffn(
1446 model: &Arc<CmfModel>,
1447 w1: usize,
1448 w3: usize,
1449 w2: usize,
1450 xs: &[f32],
1451 b: usize,
1452 hidden: usize,
1453 inter: usize,
1454 out: &mut [f32],
1455) -> bool {
1456 match backend() {
1457 #[cfg(target_os = "macos")]
1458 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1459 #[cfg(feature = "gpu")]
1460 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1461 #[allow(unreachable_patterns)]
1462 _ => false,
1463 }
1464}
1465
1466pub fn q4t_ffn(
1467 model: &Arc<CmfModel>,
1468 w1: usize,
1469 w3: usize,
1470 w2: usize,
1471 xs: &[f32],
1472 b: usize,
1473 hidden: usize,
1474 inter: usize,
1475 out: &mut [f32],
1476) -> bool {
1477 match backend() {
1478 #[cfg(target_os = "macos")]
1479 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1480 #[cfg(feature = "gpu")]
1481 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1482 #[allow(unreachable_patterns)]
1483 _ => false,
1484 }
1485}
1486
1487pub struct DitBlockArgs<'a> {
1492 pub n: usize,
1493 pub hidden: usize,
1494 pub inter: usize,
1495 pub nh: usize,
1496 pub nkv: usize,
1497 pub hd: usize,
1498 pub eps: f32,
1499 pub rope_cos: &'a [f32],
1500 pub rope_sin: &'a [f32],
1501 pub norm1: &'a [f32],
1502 pub norm2: &'a [f32],
1503 pub ffn_norm1: &'a [f32],
1504 pub ffn_norm2: &'a [f32],
1505 pub norm_q: &'a [f32],
1506 pub norm_k: &'a [f32],
1507 pub s_msa: &'a [f32],
1508 pub gate_msa: &'a [f32],
1509 pub s_mlp: &'a [f32],
1510 pub gate_mlp: &'a [f32],
1511 pub wq: usize,
1512 pub wk: usize,
1513 pub wv: usize,
1514 pub wo: usize,
1515 pub w1: usize,
1516 pub w3: usize,
1517 pub w2: usize,
1518 pub q4tp: bool,
1522 pub resident_in: bool,
1525 pub resident_out: bool,
1529}
1530
1531pub fn dit_chain_supported() -> bool {
1535 #[cfg(feature = "gpu")]
1536 {
1537 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1538 }
1539 #[allow(unreachable_code)]
1540 false
1541}
1542
1543pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1546 #[cfg(feature = "gpu")]
1547 {
1548 if matches!(backend(), Backend::Wgpu) {
1549 return crate::gpu_wgpu::dit_state_fetch(_x);
1550 }
1551 }
1552 false
1553}
1554
1555#[allow(unused_variables)]
1559#[allow(unused_variables, clippy::too_many_arguments)]
1563pub fn dit_qkv(
1564 model: &Arc<CmfModel>,
1565 wq: usize,
1566 wk: usize,
1567 wv: usize,
1568 xs: &[f32],
1569 b: usize,
1570 hidden: usize,
1571 qrows: usize,
1572 kvrows: usize,
1573 q_out: &mut [f32],
1574 k_out: &mut [f32],
1575 v_out: &mut [f32],
1576) -> bool {
1577 match backend() {
1578 #[cfg(feature = "gpu")]
1579 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1580 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1581 ),
1582 #[allow(unreachable_patterns)]
1583 _ => false,
1584 }
1585}
1586
1587pub fn fused_dit_block_available() -> bool {
1591 #[cfg(target_os = "macos")]
1592 {
1593 matches!(backend(), Backend::Metal) && fused_block_trusted()
1594 }
1595 #[cfg(not(target_os = "macos"))]
1596 {
1597 false
1598 }
1599}
1600
1601pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1602 dit_block_seg(model, a, &[a.n], x)
1603}
1604
1605pub fn dit_block_seg(
1609 model: &Arc<CmfModel>,
1610 a: &DitBlockArgs,
1611 segs: &[usize],
1612 x: &mut [f32],
1613) -> bool {
1614 match backend() {
1615 #[cfg(target_os = "macos")]
1616 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1617 #[cfg(feature = "gpu")]
1624 Backend::Wgpu
1625 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1626 Some("0") => false,
1627 Some(_) => true,
1628 None => crate::gpu_wgpu::discrete_active(),
1629 } =>
1630 {
1631 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1632 }
1633 #[allow(unreachable_patterns)]
1634 _ => false,
1635 }
1636}
1637
1638pub struct VaeResnetArgs<'a> {
1642 pub groups: usize,
1643 pub ic: usize,
1644 pub oc: usize,
1645 pub h: usize,
1646 pub w: usize,
1647 pub n1w: &'a [f32],
1648 pub n1b: &'a [f32],
1649 pub c1w: &'a [f32],
1650 pub c1b: &'a [f32],
1651 pub c1k: usize,
1652 pub n2w: &'a [f32],
1653 pub n2b: &'a [f32],
1654 pub c2w: &'a [f32],
1655 pub c2b: &'a [f32],
1656 pub c2k: usize,
1657 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1658}
1659
1660#[allow(unused_variables)]
1663pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1664 match backend() {
1665 #[cfg(target_os = "macos")]
1666 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1667 _ => false,
1668 }
1669}
1670
1671#[allow(unused_variables, clippy::too_many_arguments)]
1674pub fn vae_upsample_conv(
1675 w: &[f32],
1676 bias: &[f32],
1677 x: &[f32],
1678 ic: usize,
1679 oc: usize,
1680 h: usize,
1681 w_img: usize,
1682 k: usize,
1683 out: &mut [f32],
1684) -> bool {
1685 match backend() {
1686 #[cfg(target_os = "macos")]
1687 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1688 #[cfg(feature = "gpu")]
1689 Backend::Wgpu => {
1690 crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out)
1691 }
1692 #[allow(unreachable_patterns)]
1693 _ => false,
1694 }
1695}
1696
1697#[allow(unused_variables, clippy::too_many_arguments)]
1700pub fn vae_conv2d(
1701 w: &[f32],
1702 bias: &[f32],
1703 x: &[f32],
1704 ic: usize,
1705 oc: usize,
1706 h: usize,
1707 w_img: usize,
1708 k: usize,
1709 out: &mut [f32],
1710) -> bool {
1711 match backend() {
1712 #[cfg(target_os = "macos")]
1713 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1714 #[cfg(feature = "gpu")]
1715 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1716 #[allow(unreachable_patterns)]
1717 _ => false,
1718 }
1719}
1720
1721#[allow(unused_variables, clippy::too_many_arguments)]
1725#[allow(unused_variables)]
1729#[allow(clippy::too_many_arguments)]
1730#[allow(clippy::too_many_arguments, unused_variables)]
1733pub fn dit_qkv_attention(
1734 model: &Arc<CmfModel>,
1735 qkv_idx: usize,
1736 xn: &[f32],
1737 n: usize,
1738 hidden: usize,
1739 nh: usize,
1740 hd: usize,
1741 scale: f32,
1742 nr: (&[f32], &[f32], &[f32], f32),
1743 out: &mut [f32],
1744) -> bool {
1745 match backend() {
1746 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1747 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1748 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1749 ),
1750 #[allow(unreachable_patterns)]
1751 _ => false,
1752 }
1753}
1754
1755#[allow(clippy::too_many_arguments)]
1758pub fn dit_qkv_attn_out(
1759 model: &Arc<CmfModel>,
1760 qkv_idx: usize,
1761 out_idx: usize,
1762 xn: &[f32],
1763 n: usize,
1764 hidden: usize,
1765 nh: usize,
1766 hd: usize,
1767 scale: f32,
1768 nr: (&[f32], &[f32], &[f32], f32),
1769 proj: &mut [f32],
1770) -> bool {
1771 match backend() {
1772 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1773 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1774 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1775 ),
1776 #[allow(unreachable_patterns)]
1777 _ => false,
1778 }
1779}
1780
1781#[allow(clippy::too_many_arguments)]
1783pub fn vae_qkv_attn_out(
1784 model: &Arc<CmfModel>,
1785 qkv_idx: usize,
1786 out_idx: usize,
1787 xn: &[f32],
1788 n: usize,
1789 dim: usize,
1790 nh: usize,
1791 hd: usize,
1792 scale: f32,
1793 angles: &[f32],
1794 eps: f32,
1795 qkv_bias: &[f32],
1796 proj: &mut [f32],
1797) -> bool {
1798 match backend() {
1799 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1800 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1801 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1802 ),
1803 #[allow(unreachable_patterns)]
1804 _ => false,
1805 }
1806}
1807
1808#[allow(clippy::too_many_arguments)]
1809pub fn vae_attention_packed(
1810 qkv: &[f32],
1811 nh: usize,
1812 n: usize,
1813 hd: usize,
1814 scale: f32,
1815 angles: &[f32],
1816 eps: f32,
1817 out: &mut [f32],
1818) -> bool {
1819 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
1820}
1821
1822#[allow(clippy::too_many_arguments)]
1823pub fn vae_attention_packed_layout(
1824 qkv: &[f32],
1825 nh: usize,
1826 n: usize,
1827 hd: usize,
1828 scale: f32,
1829 angles: &[f32],
1830 eps: f32,
1831 out: &mut [f32],
1832 layout: u32,
1833) -> bool {
1834 match backend() {
1835 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1836 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
1837 qkv, nh, n, hd, scale, angles, eps, out, layout,
1838 ),
1839 #[allow(unreachable_patterns)]
1840 _ => false,
1841 }
1842}
1843
1844#[allow(clippy::too_many_arguments)]
1845pub fn dit_split_only(
1846 qkv: &[f32],
1847 nh: usize,
1848 n: usize,
1849 hd: usize,
1850 layout: u32,
1851 norm: Option<(&[f32], f32)>,
1852 out_q: &mut [f32],
1853) -> bool {
1854 match backend() {
1855 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1856 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
1857 #[allow(unreachable_patterns)]
1858 _ => false,
1859 }
1860}
1861
1862pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
1867 match backend() {
1868 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1869 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
1870 #[allow(unreachable_patterns)]
1871 _ => false,
1872 }
1873}
1874
1875#[allow(clippy::too_many_arguments)]
1877pub fn vae_conv2d_coop(
1878 w: &[f32],
1879 bias: Option<&[f32]>,
1880 x: &[f32],
1881 ic: usize,
1882 oc: usize,
1883 h: usize,
1884 wi: usize,
1885 k: usize,
1886 out: &mut [f32],
1887) -> bool {
1888 match backend() {
1889 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1890 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
1891 #[allow(unreachable_patterns)]
1892 _ => false,
1893 }
1894}
1895
1896pub fn dit_attention_packed(
1897 qkv: &[f32],
1898 nh: usize,
1899 n: usize,
1900 hd: usize,
1901 scale: f32,
1902 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1905 out: &mut [f32],
1906) -> bool {
1907 match backend() {
1908 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1909 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
1910 #[allow(unreachable_patterns)]
1911 _ => false,
1912 }
1913}
1914
1915pub fn dit_attention(
1916 qh: &[f32],
1917 kh: &[f32],
1918 vh: &[f32],
1919 nh: usize,
1920 nkv: usize,
1921 n: usize,
1922 hd: usize,
1923 scale: f32,
1924 out: &mut [f32],
1925) -> bool {
1926 match backend() {
1927 #[cfg(target_os = "macos")]
1928 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1929 #[cfg(feature = "gpu")]
1930 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1931 #[allow(unreachable_patterns)]
1932 _ => false,
1933 }
1934}
1935
1936#[allow(unused_variables)]
1941pub fn q4tp_matmat(
1942 model: &Arc<CmfModel>,
1943 idx: usize,
1944 xs: &[f32],
1945 b: usize,
1946 rows: usize,
1947 cols: usize,
1948 out: &mut [f32],
1949) -> bool {
1950 match backend() {
1951 #[cfg(target_os = "macos")]
1952 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1953 #[cfg(feature = "gpu")]
1954 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1955 #[allow(unreachable_patterns)]
1956 _ => false,
1957 }
1958}
1959
1960pub fn q2tp_matmat(
1963 model: &Arc<CmfModel>,
1964 idx: usize,
1965 xs: &[f32],
1966 b: usize,
1967 rows: usize,
1968 cols: usize,
1969 out: &mut [f32],
1970) -> bool {
1971 match backend() {
1972 #[cfg(feature = "gpu")]
1973 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
1974 #[allow(unreachable_patterns)]
1975 _ => false,
1976 }
1977}
1978
1979pub fn q4tp_matvec(
1984 model: &Arc<CmfModel>,
1985 idx: usize,
1986 xs: &[f32],
1987 rows: usize,
1988 cols: usize,
1989 out: &mut [f32],
1990) -> bool {
1991 match backend() {
1992 #[cfg(target_os = "macos")]
1993 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
1994 #[cfg(feature = "gpu")]
1995 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
1996 #[allow(unreachable_patterns)]
1997 _ => false,
1998 }
1999}
2000
2001pub fn q4t_matvec(
2007 model: &Arc<CmfModel>,
2008 idx: usize,
2009 xs: &[f32],
2010 rows: usize,
2011 cols: usize,
2012 out: &mut [f32],
2013) -> bool {
2014 match backend() {
2015 #[cfg(target_os = "macos")]
2016 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2017 #[allow(unreachable_patterns)]
2018 _ => false,
2019 }
2020}
2021
2022pub fn q4t_matmat(
2023 model: &Arc<CmfModel>,
2024 idx: usize,
2025 xs: &[f32],
2026 b: usize,
2027 rows: usize,
2028 cols: usize,
2029 out: &mut [f32],
2030) -> bool {
2031 match backend() {
2032 #[cfg(target_os = "macos")]
2033 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2034 #[cfg(feature = "gpu")]
2035 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2036 #[allow(unreachable_patterns)]
2037 _ => false,
2038 }
2039}
2040
2041#[cfg(target_os = "macos")]
2043pub use crate::gpu_metal::{
2044 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2045 TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2046};
2047
2048#[cfg(target_os = "macos")]
2050pub fn gdn_block(
2051 model: &Arc<CmfModel>,
2052 layers: &[GdnGpuLayer],
2053 states: &mut [&mut [f32]],
2054 cfg: &GdnGpuCfg,
2055 h: &mut [f32],
2056) -> bool {
2057 match backend() {
2058 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2059 _ => false,
2060 }
2061}
2062
2063#[allow(unused_variables)]
2065pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2066 match backend() {
2067 #[cfg(target_os = "macos")]
2068 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2069 #[cfg(feature = "gpu")]
2070 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2071 Backend::None => false,
2072 }
2073}
2074
2075#[allow(unused_variables)]
2077pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2078 match backend() {
2079 #[cfg(target_os = "macos")]
2080 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2081 #[cfg(feature = "gpu")]
2082 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2083 Backend::None => false,
2084 }
2085}
2086
2087static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2103static 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)];
2107
2108const GRAPH_RACE_SAMPLES: u32 = 4;
2110
2111pub fn graph_race_begin_generation() {
2114 #[cfg(feature = "gpu")]
2119 {
2120 static FLUSHED: std::sync::Once = std::sync::Once::new();
2132 static FIRST: std::sync::atomic::AtomicBool =
2133 std::sync::atomic::AtomicBool::new(true);
2134 if FIRST.swap(false, Ordering::Relaxed) {
2135 } else {
2137 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2138 }
2139 }
2140 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2141 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2142 return;
2143 }
2144 let (gn, cn) = (
2145 GRAPH_N[1].load(Ordering::Relaxed),
2146 GRAPH_N[0].load(Ordering::Relaxed),
2147 );
2148 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2149 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2150 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2151 let verdict = if g_avg < c_avg { 1 } else { 2 };
2152 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2153 tracing::info!(
2154 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2155 g_avg as f64 / 1e6,
2156 c_avg as f64 / 1e6,
2157 if verdict == 1 { "graph" } else { "normal path" }
2158 );
2159 return;
2160 }
2161 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2162 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2163}
2164
2165pub fn graph_race_use_graph(trusted: bool) -> bool {
2169 if trusted {
2170 return true;
2171 }
2172 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2173 1 => true,
2174 2 => false,
2175 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2176 }
2177}
2178
2179pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2184 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2185 return false;
2186 }
2187 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2188 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2189 if !first || cn == 0 {
2190 return false;
2191 }
2192 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2193 let ns = dur.as_nanos() as u64;
2194 if ns > 1_000_000_000 && ns > 4 * c_avg {
2195 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2196 tracing::info!(
2197 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2198 ns as f64 / 1e6,
2199 c_avg as f64 / 1e6
2200 );
2201 return true;
2202 }
2203 false
2204}
2205
2206pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2210 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2211 return;
2212 }
2213 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2214 if tok == 0 {
2215 return;
2216 }
2217 let i = used_graph as usize;
2218 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2219 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2220}
2221
2222pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2232 #[inline]
2233 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2234 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2235 for c in chunks.chunks_exact(8) {
2236 h ^= u64::from_le_bytes(c.try_into().unwrap());
2237 h = h.wrapping_mul(0x100_0000_01b3);
2238 }
2239 for &b in tail {
2240 h ^= b as u64;
2241 h = h.wrapping_mul(0x100_0000_01b3);
2242 }
2243 h
2244 }
2245 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2246 if data.len() <= 4096 {
2247 return fnv(h, data);
2248 }
2249 let step = (data.len() - 64) / 63;
2250 for i in 0..64 {
2251 h = fnv(h, &data[i * step..i * step + 64]);
2252 }
2253 h
2254}
2255
2256pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2259 let bytes =
2260 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2261 fp_bytes(bytes)
2262}
2263
2264#[cfg(test)]
2265mod fp_tests {
2266 use super::fp_bytes;
2267
2268 #[test]
2273 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2274 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2276 let h0 = fp_bytes(&base);
2277 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2278 let mut dense = base.clone();
2281 for b in dense.iter_mut() {
2282 *b = b.wrapping_add(1);
2283 }
2284 assert_ne!(h0, fp_bytes(&dense), "a fully different tensor slipped through");
2285 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2288 let mut small = vec![3u8; 4096];
2291 let hs = fp_bytes(&small);
2292 small[2048] ^= 1;
2293 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2294 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2296 let v = vec![9u8; n];
2297 let _ = fp_bytes(&v); }
2299 }
2300}
2301
2302pub fn bake_release() {
2306 #[cfg(feature = "gpu")]
2307 crate::gpu_wgpu::bake_release();
2308}
2309
2310pub fn bake_precision_strict(on: bool) {
2314 #[cfg(feature = "gpu")]
2315 crate::gpu_wgpu::bake_precision_strict(on);
2316 #[cfg(not(feature = "gpu"))]
2317 let _ = on;
2318}