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)]
1921pub fn music3_ffn(
1922 model: &std::sync::Arc<CmfModel>,
1923 idx_in: usize,
1924 idx_out: usize,
1925 h: &[f32],
1926 bias_in: &[f32],
1927 n: usize,
1928 hs: usize,
1929 inter: usize,
1930 out: &mut [f32],
1931) -> bool {
1932 match backend() {
1933 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1934 Backend::Wgpu => {
1935 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
1936 }
1937 #[allow(unreachable_patterns)]
1938 _ => false,
1939 }
1940}
1941
1942#[allow(clippy::too_many_arguments)]
1946pub fn conv1d_gemm(
1947 x: &[f32],
1948 w: &[f32],
1949 ic: usize,
1950 oc: usize,
1951 n: usize,
1952 k: usize,
1953 pad: usize,
1954 dil: usize,
1955 out_n: usize,
1956 yt: &mut [f32],
1957) -> bool {
1958 match backend() {
1959 #[cfg(target_os = "macos")]
1960 Backend::Metal => {
1961 crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt)
1962 }
1963 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1964 Backend::Wgpu => {
1965 crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt)
1966 }
1967 #[allow(unreachable_patterns)]
1968 _ => false,
1969 }
1970}
1971
1972#[allow(clippy::too_many_arguments)]
1974pub fn vae_conv2d_coop(
1975 w: &[f32],
1976 bias: Option<&[f32]>,
1977 x: &[f32],
1978 ic: usize,
1979 oc: usize,
1980 h: usize,
1981 wi: usize,
1982 k: usize,
1983 out: &mut [f32],
1984) -> bool {
1985 match backend() {
1986 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1987 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
1988 #[allow(unreachable_patterns)]
1989 _ => false,
1990 }
1991}
1992
1993pub fn dit_attention_packed(
1994 qkv: &[f32],
1995 nh: usize,
1996 n: usize,
1997 hd: usize,
1998 scale: f32,
1999 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2002 out: &mut [f32],
2003) -> bool {
2004 match backend() {
2005 #[cfg(feature = "gpu")]
2012 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2013 #[allow(unreachable_patterns)]
2014 _ => false,
2015 }
2016}
2017
2018pub fn dit_attention_packed_available() -> bool {
2026 #[allow(unreachable_patterns)]
2027 match backend() {
2028 #[cfg(feature = "gpu")]
2029 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2030 _ => false,
2031 }
2032}
2033
2034pub fn dit_attention(
2035 qh: &[f32],
2036 kh: &[f32],
2037 vh: &[f32],
2038 nh: usize,
2039 nkv: usize,
2040 n: usize,
2041 hd: usize,
2042 scale: f32,
2043 out: &mut [f32],
2044) -> bool {
2045 match backend() {
2046 #[cfg(target_os = "macos")]
2047 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2048 #[cfg(feature = "gpu")]
2049 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2050 #[allow(unreachable_patterns)]
2051 _ => false,
2052 }
2053}
2054
2055#[allow(unused_variables)]
2060pub fn q4tp_matmat(
2061 model: &Arc<CmfModel>,
2062 idx: usize,
2063 xs: &[f32],
2064 b: usize,
2065 rows: usize,
2066 cols: usize,
2067 out: &mut [f32],
2068) -> bool {
2069 match backend() {
2070 #[cfg(target_os = "macos")]
2071 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2072 #[cfg(feature = "gpu")]
2073 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2074 #[allow(unreachable_patterns)]
2075 _ => false,
2076 }
2077}
2078
2079pub fn q2tp_matmat(
2082 model: &Arc<CmfModel>,
2083 idx: usize,
2084 xs: &[f32],
2085 b: usize,
2086 rows: usize,
2087 cols: usize,
2088 out: &mut [f32],
2089) -> bool {
2090 match backend() {
2091 #[cfg(feature = "gpu")]
2092 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2093 #[allow(unreachable_patterns)]
2094 _ => false,
2095 }
2096}
2097
2098pub fn q4tp_matvec(
2103 model: &Arc<CmfModel>,
2104 idx: usize,
2105 xs: &[f32],
2106 rows: usize,
2107 cols: usize,
2108 out: &mut [f32],
2109) -> bool {
2110 match backend() {
2111 #[cfg(target_os = "macos")]
2112 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2113 #[cfg(feature = "gpu")]
2114 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2115 #[allow(unreachable_patterns)]
2116 _ => false,
2117 }
2118}
2119
2120pub fn q4t_matvec(
2126 model: &Arc<CmfModel>,
2127 idx: usize,
2128 xs: &[f32],
2129 rows: usize,
2130 cols: usize,
2131 out: &mut [f32],
2132) -> bool {
2133 match backend() {
2134 #[cfg(target_os = "macos")]
2135 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2136 #[allow(unreachable_patterns)]
2137 _ => false,
2138 }
2139}
2140
2141pub fn q4t_matmat(
2142 model: &Arc<CmfModel>,
2143 idx: usize,
2144 xs: &[f32],
2145 b: usize,
2146 rows: usize,
2147 cols: usize,
2148 out: &mut [f32],
2149) -> bool {
2150 match backend() {
2151 #[cfg(target_os = "macos")]
2152 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2153 #[cfg(feature = "gpu")]
2154 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2155 #[allow(unreachable_patterns)]
2156 _ => false,
2157 }
2158}
2159
2160#[cfg(target_os = "macos")]
2162pub use crate::gpu_metal::{
2163 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2164 TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2165};
2166
2167#[cfg(target_os = "macos")]
2169pub fn gdn_block(
2170 model: &Arc<CmfModel>,
2171 layers: &[GdnGpuLayer],
2172 states: &mut [&mut [f32]],
2173 cfg: &GdnGpuCfg,
2174 h: &mut [f32],
2175) -> bool {
2176 match backend() {
2177 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2178 _ => false,
2179 }
2180}
2181
2182#[allow(unused_variables)]
2184pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2185 match backend() {
2186 #[cfg(target_os = "macos")]
2187 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2188 #[cfg(feature = "gpu")]
2189 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2190 Backend::None => false,
2191 }
2192}
2193
2194#[allow(unused_variables)]
2196pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2197 match backend() {
2198 #[cfg(target_os = "macos")]
2199 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2200 #[cfg(feature = "gpu")]
2201 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2202 Backend::None => false,
2203 }
2204}
2205
2206static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2222static 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)];
2226
2227const GRAPH_RACE_SAMPLES: u32 = 4;
2229
2230static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2240
2241pub fn graph_mark_unsupported() {
2246 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2247 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2248 }
2249}
2250
2251pub fn graph_unsupported() -> bool {
2252 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2253}
2254
2255pub fn graph_unsupported_reset() {
2257 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2258}
2259
2260pub fn graph_race_begin_generation() {
2261 #[cfg(feature = "gpu")]
2266 {
2267 static FLUSHED: std::sync::Once = std::sync::Once::new();
2279 static FIRST: std::sync::atomic::AtomicBool =
2280 std::sync::atomic::AtomicBool::new(true);
2281 if FIRST.swap(false, Ordering::Relaxed) {
2282 } else {
2284 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2285 }
2286 }
2287 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2288 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2289 return;
2290 }
2291 let (gn, cn) = (
2292 GRAPH_N[1].load(Ordering::Relaxed),
2293 GRAPH_N[0].load(Ordering::Relaxed),
2294 );
2295 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2296 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2297 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2298 let verdict = if g_avg < c_avg { 1 } else { 2 };
2299 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2300 tracing::info!(
2301 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2302 g_avg as f64 / 1e6,
2303 c_avg as f64 / 1e6,
2304 if verdict == 1 { "graph" } else { "normal path" }
2305 );
2306 return;
2307 }
2308 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2309 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2310}
2311
2312pub fn graph_race_use_graph(trusted: bool) -> bool {
2316 if trusted {
2317 return true;
2318 }
2319 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2320 1 => true,
2321 2 => false,
2322 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2323 }
2324}
2325
2326pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2331 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2332 return false;
2333 }
2334 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2335 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2336 if !first || cn == 0 {
2337 return false;
2338 }
2339 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2340 let ns = dur.as_nanos() as u64;
2341 if ns > 1_000_000_000 && ns > 4 * c_avg {
2342 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2343 tracing::info!(
2344 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2345 ns as f64 / 1e6,
2346 c_avg as f64 / 1e6
2347 );
2348 return true;
2349 }
2350 false
2351}
2352
2353pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2357 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2358 return;
2359 }
2360 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2361 if tok == 0 {
2362 return;
2363 }
2364 let i = used_graph as usize;
2365 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2366 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2367}
2368
2369pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2379 #[inline]
2380 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2381 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2382 for c in chunks.chunks_exact(8) {
2383 h ^= u64::from_le_bytes(c.try_into().unwrap());
2384 h = h.wrapping_mul(0x100_0000_01b3);
2385 }
2386 for &b in tail {
2387 h ^= b as u64;
2388 h = h.wrapping_mul(0x100_0000_01b3);
2389 }
2390 h
2391 }
2392 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2393 if data.len() <= 4096 {
2394 return fnv(h, data);
2395 }
2396 let step = (data.len() - 64) / 63;
2397 for i in 0..64 {
2398 h = fnv(h, &data[i * step..i * step + 64]);
2399 }
2400 h
2401}
2402
2403pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2406 let bytes =
2407 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2408 fp_bytes(bytes)
2409}
2410
2411#[cfg(test)]
2412mod fp_tests {
2413 use super::fp_bytes;
2414
2415 #[test]
2420 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2421 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2423 let h0 = fp_bytes(&base);
2424 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2425 let mut dense = base.clone();
2428 for b in dense.iter_mut() {
2429 *b = b.wrapping_add(1);
2430 }
2431 assert_ne!(h0, fp_bytes(&dense), "a fully different tensor slipped through");
2432 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2435 let mut small = vec![3u8; 4096];
2438 let hs = fp_bytes(&small);
2439 small[2048] ^= 1;
2440 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2441 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2443 let v = vec![9u8; n];
2444 let _ = fp_bytes(&v); }
2446 }
2447}
2448
2449pub fn bake_release() {
2453 #[cfg(feature = "gpu")]
2454 crate::gpu_wgpu::bake_release();
2455}
2456
2457pub fn bake_precision_strict(on: bool) {
2461 #[cfg(feature = "gpu")]
2462 crate::gpu_wgpu::bake_precision_strict(on);
2463 #[cfg(not(feature = "gpu"))]
2464 let _ = on;
2465}