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 struct CpuScopeGuard(bool);
40
41impl Drop for CpuScopeGuard {
42 fn drop(&mut self) {
43 CPU_ONLY.with(|c| c.set(self.0));
44 }
45}
46
47pub fn enter_cpu_scope() -> CpuScopeGuard {
48 let previous = CPU_ONLY.with(|c| c.replace(true));
49 CpuScopeGuard(previous)
50}
51
52pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
54 let _restore = enter_cpu_scope();
55 f()
56}
57
58pub fn probe_set_device(label: &str) {
63 let _ = DEVICE_LABEL.set(label.to_string());
64}
65
66fn device_label() -> &'static str {
67 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
68}
69
70static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
71
72static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
81
82pub fn set_cache_dir(dir: std::path::PathBuf) {
84 let _ = CACHE_DIR.set(dir);
85}
86
87pub fn cache_dir_pub() -> std::path::PathBuf {
89 cache_dir()
90}
91
92fn cache_dir() -> std::path::PathBuf {
93 if let Some(d) = CACHE_DIR.get() {
94 return d.clone();
95 }
96 match std::env::var_os("TMPDIR") {
97 Some(t) => std::path::PathBuf::from(t),
98 None => std::env::temp_dir(),
99 }
100}
101
102fn probe_cache_path() -> Option<std::path::PathBuf> {
105 match std::env::var("CMF_PROBE_CACHE") {
106 Ok(v) if v == "0" => None,
107 Ok(v) => Some(std::path::PathBuf::from(v)),
108 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
109 }
110}
111
112fn probe_cache_key_named(class: &str) -> String {
116 format!(
117 "{}\t{}\t{}",
118 env!("CARGO_PKG_VERSION"),
119 device_label(),
120 class
121 )
122}
123
124const CLASS_NAMES: [&str; 7] = [
125 "ffn",
126 "matvec",
127 "matmat",
128 "qkv-batch",
129 "matmat-wide",
130 "lm-head",
131 "gemm-nt",
132];
133
134fn probe_cache_load() {
143 static ONCE: std::sync::Once = std::sync::Once::new();
144 ONCE.call_once(|| {
145 let Some(path) = probe_cache_path() else {
146 return;
147 };
148 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
153 return;
154 }
155 let Ok(text) = std::fs::read_to_string(&path) else {
156 return;
157 };
158 probe_cache_adopt(&text);
159 });
160}
161
162fn probe_cache_adopt(text: &str) {
166 for line in text.lines() {
167 let Some((key, verdict)) = line.rsplit_once('\t') else {
168 continue;
169 };
170 let winner = match verdict.trim() {
171 "gpu" => 1u8,
172 "cpu" => 2u8,
173 _ => continue,
174 };
175 for (i, name) in CLASS_NAMES.iter().enumerate() {
176 if probe_cache_key_named(name) == key {
177 let _ = PROBES[i].state.compare_exchange(
178 0,
179 winner,
180 Ordering::Relaxed,
181 Ordering::Relaxed,
182 );
183 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
184 }
185 }
186 }
187}
188
189fn probe_cache_store(c: OpClass, winner: u8) {
192 let Some(path) = probe_cache_path() else {
193 return;
194 };
195 let line = format!(
196 "{}\t{}\n",
197 probe_cache_key_named(CLASS_NAMES[c as usize]),
198 if winner == 1 { "gpu" } else { "cpu" }
199 );
200 use std::io::Write;
201 if let Ok(mut f) = std::fs::OpenOptions::new()
202 .create(true)
203 .append(true)
204 .open(&path)
205 {
206 let _ = f.write_all(line.as_bytes());
207 }
208}
209
210pub fn cold_epoch() -> u64 {
216 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
217}
218static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
219
220pub(crate) fn probe_note_cold() {
221 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
222 PROBE_COLD.with(|c| c.set(true));
223}
224
225pub(crate) fn probe_was_cold() -> bool {
229 PROBE_COLD.with(|c| c.get())
230}
231
232pub fn set_layer(l: i64) {
234 CUR_LAYER.with(|c| c.set(l));
235}
236
237pub fn cur_layer() -> i64 {
239 CUR_LAYER.with(|c| c.get())
240}
241
242pub fn automatic_layer_prefix(
245 model: &Arc<CmfModel>,
246 num_layers: usize,
247 physical_layers: usize,
248) -> Option<usize> {
249 match backend() {
250 #[cfg(feature = "gpu")]
251 Backend::Wgpu => {
252 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
253 }
254 _ => None,
255 }
256}
257
258fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
261 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
262 R.get_or_init(|| {
263 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
264 let mut v = Vec::new();
265 for part in s.split(',') {
266 let part = part.trim();
267 match part.split_once('-') {
268 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
269 None => {
270 let x: i64 = part.parse().ok()?;
271 v.push((x, x));
272 }
273 }
274 }
275 Some(v)
276 })
277}
278
279fn layer_allowed() -> bool {
280 match layer_ranges() {
281 None => true,
282 Some(ranges) => {
283 let cur = CUR_LAYER.with(|c| c.get());
284 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
285 }
286 }
287}
288
289pub fn enabled_here() -> bool {
293 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
294}
295
296pub fn q2tp_gpu_opt_in() -> bool {
302 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
303}
304
305#[derive(Clone, Copy)]
317pub enum OpClass {
318 Ffn = 0,
320 Matvec = 1,
322 Matmat = 2,
324 Batch = 3,
326 MatmatWide = 4,
332 MatvecHead = 5,
339 GemmNt = 6,
346}
347
348pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
352 if rows * cols >= 67_108_864 {
353 OpClass::MatvecHead
354 } else {
355 OpClass::Matvec
356 }
357}
358
359pub enum ProbeArm {
361 Gpu,
363 CpuTimed,
365 Cpu,
367}
368
369const PROBE_SAMPLES: u32 = 6;
371
372const PROBE_DECLINE_LIMIT: u32 = 16;
376
377const PROBE_WARMUP: u32 = 1;
379
380struct Probe {
381 state: AtomicU8,
383 flip: AtomicU32,
384 gpu_ns: AtomicU64,
385 gpu_n: AtomicU32,
386 declines: AtomicU32,
396 gpu_burn: AtomicU32,
409 cpu_ns: AtomicU64,
410 cpu_n: AtomicU32,
411 gpu_min: AtomicU64,
418 cpu_min: AtomicU64,
419}
420
421impl Probe {
422 const fn new() -> Self {
423 Self {
424 state: AtomicU8::new(0),
425 flip: AtomicU32::new(0),
426 gpu_ns: AtomicU64::new(0),
427 gpu_n: AtomicU32::new(0),
428 declines: AtomicU32::new(0),
429 gpu_burn: AtomicU32::new(PROBE_WARMUP),
430 cpu_ns: AtomicU64::new(0),
431 cpu_n: AtomicU32::new(0),
432 gpu_min: AtomicU64::new(u64::MAX),
433 cpu_min: AtomicU64::new(u64::MAX),
434 }
435 }
436}
437
438static PROBES: [Probe; 7] = [
439 Probe::new(),
440 Probe::new(),
441 Probe::new(),
442 Probe::new(),
443 Probe::new(),
444 Probe::new(),
445 Probe::new(),
446];
447
448static TRUST_GPU: AtomicBool = AtomicBool::new(false);
455
456pub fn trust_gpu() -> GpuTrust {
458 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
459 GpuTrust(was)
460}
461
462pub struct GpuTrust(bool);
463
464impl Drop for GpuTrust {
465 fn drop(&mut self) {
466 TRUST_GPU.store(self.0, Ordering::Relaxed);
467 }
468}
469
470fn probe_on_for(c: OpClass) -> bool {
471 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
477 return false;
478 }
479 probe_on()
480}
481
482pub fn probe_enabled() -> bool {
486 probe_on()
487}
488
489fn probe_on() -> bool {
490 static ON: OnceLock<bool> = OnceLock::new();
491 *ON.get_or_init(|| {
492 std::env::var("CMF_GPU_PROBE")
493 .map(|v| v != "0" && v != "off")
494 .unwrap_or(true)
495 })
496}
497
498pub fn q1_force() -> bool {
503 #[cfg(target_os = "macos")]
504 {
505 backend() == Backend::Metal
506 }
507 #[cfg(not(target_os = "macos"))]
508 {
509 false
510 }
511}
512
513pub fn fused_block_trusted() -> bool {
532 #[cfg(target_os = "macos")]
533 if backend() == Backend::Metal {
534 return true;
535 }
536 wgpu_graph_default()
537}
538
539pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
551 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
552 {
553 return crate::gpu_wgpu::weight_is_resident(model, idx);
554 }
555 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
556 {
557 let _ = (model, idx);
558 true
559 }
560}
561
562pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
563 if !weights_resident && probe_deciding(c) {
564 return ProbeArm::Gpu;
565 }
566 probe_arm(c)
567}
568
569pub fn probe_arm(c: OpClass) -> ProbeArm {
570 PROBE_COLD.with(|f| f.set(false));
575 if !probe_on_for(c) {
576 return ProbeArm::Gpu;
577 }
578 probe_cache_load();
579 let p = &PROBES[c as usize];
580 match p.state.load(Ordering::Relaxed) {
581 1 => ProbeArm::Gpu,
582 2 => ProbeArm::Cpu,
583 _ => {
584 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
585 ProbeArm::Gpu
586 } else {
587 ProbeArm::CpuTimed
588 }
589 }
590 }
591}
592
593pub fn probe_note_decline(c: OpClass) {
597 let p = &PROBES[c as usize];
598 if p.state.load(Ordering::Relaxed) != 0 {
599 return;
600 }
601 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
602 if n >= PROBE_DECLINE_LIMIT
603 && p.state
604 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
605 .is_ok()
606 {
607 tracing::info!(
608 "gpu probe [{}]: device declined {n} times → cpu",
609 CLASS_NAMES[c as usize]
610 );
611 }
612}
613
614pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
617 probe_record_into(
618 &PROBES[c as usize],
619 CLASS_NAMES[c as usize],
620 Some(c),
621 gpu,
622 dur,
623 )
624}
625
626fn probe_record_into(
629 p: &Probe,
630 class_name: &str,
631 cache: Option<OpClass>,
632 gpu: bool,
633 dur: std::time::Duration,
634) {
635 if p.state.load(Ordering::Relaxed) != 0 {
636 return;
637 }
638 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
639 return; }
641 if gpu {
642 let left = p.gpu_burn.load(Ordering::Relaxed);
646 if left > 0 {
647 p.gpu_burn.store(left - 1, Ordering::Relaxed);
648 return; }
650 }
651 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
652 if gpu {
653 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
654 p.gpu_n.fetch_add(1, Ordering::Relaxed);
655 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
656 } else {
657 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
658 p.cpu_n.fetch_add(1, Ordering::Relaxed);
659 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
660 }
661 let (gn, cn) = (
662 p.gpu_n.load(Ordering::Relaxed),
663 p.cpu_n.load(Ordering::Relaxed),
664 );
665 if gn >= 2 && cn >= 2 {
666 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
670 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
671 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
681 return;
682 }
683 let winner = if g <= cp { 1 } else { 2 };
684 if p.state
685 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
686 .is_ok()
687 {
688 tracing::info!(
689 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
690 class_name,
691 g / 1e6,
692 cp / 1e6,
693 if winner == 1 { "gpu" } else { "cpu" },
694 );
695 if let Some(c) = cache {
696 probe_cache_store(c, winner);
697 }
698 }
699 }
700}
701
702pub fn probe_deciding(c: OpClass) -> bool {
705 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
706}
707
708#[allow(unused_variables)]
718pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
719 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
720 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
721 let resident = match backend() {
722 #[cfg(target_os = "macos")]
723 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
724 #[cfg(feature = "gpu")]
725 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
726 Backend::None => false,
727 };
728 if !resident && may_upload {
729 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
730 }
731 resident
732}
733
734#[cfg(test)]
736pub(crate) fn probe_reset() {
737 for p in &PROBES {
738 p.state.store(0, Ordering::Relaxed);
739 p.flip.store(0, Ordering::Relaxed);
740 p.gpu_ns.store(0, Ordering::Relaxed);
741 p.gpu_n.store(0, Ordering::Relaxed);
742 p.cpu_ns.store(0, Ordering::Relaxed);
743 p.cpu_n.store(0, Ordering::Relaxed);
744 }
745}
746
747#[cfg(test)]
751static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
752
753#[cfg(test)]
754fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
755 PROBE_TEST_LOCK
756 .lock()
757 .unwrap_or_else(std::sync::PoisonError::into_inner)
758}
759
760#[cfg(test)]
761mod probe_tests {
762 use super::*;
763 use std::time::Duration;
764
765 #[test]
768 fn probe_alternates_discards_cold_and_decides() {
769 let _probe_guard = probe_test_guard();
770 probe_reset();
771 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
773 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
774
775 probe_note_cold();
779 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
780 for _ in 0..PROBE_SAMPLES {
781 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
782 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
783 }
784 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
785
786 for _ in 0..PROBE_SAMPLES {
788 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
789 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
790 }
791 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
792
793 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
795 CPU_ONLY.with(|c| assert!(!c.get()));
796 cpu_scope(|| {
797 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
798 CPU_ONLY.with(|c| assert!(c.get()));
799 });
800 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
801 CPU_ONLY.with(|c| assert!(!c.get()));
802 probe_reset();
803 }
804
805 #[test]
806 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
807 let _probe_guard = probe_test_guard();
808 let mine = probe_cache_key_named("gemm-nt");
820 let state = || {
821 PROBES[OpClass::GemmNt as usize]
822 .state
823 .load(Ordering::Relaxed)
824 };
825
826 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
828 assert_eq!(state(), 0);
829 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
831 assert_ne!(older, mine);
832 probe_cache_adopt(&format!("{older}\tgpu\n"));
833 assert_eq!(state(), 0);
834 probe_cache_adopt(&format!("{mine}\tcpu\n"));
836 assert_eq!(state(), 2);
837
838 PROBES[OpClass::GemmNt as usize]
839 .state
840 .store(0, Ordering::Relaxed);
841 }
842}
843
844pub const GPU_MIN_ROWS: usize = 65_536;
847
848pub fn min_rows() -> usize {
855 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
856 .ok()
857 .and_then(|v| v.parse().ok())
858 {
859 return v;
860 }
861 if discrete() { 4096 } else { GPU_MIN_ROWS }
862}
863
864pub fn discrete() -> bool {
866 match backend() {
867 #[cfg(feature = "gpu")]
868 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
869 #[cfg(target_os = "macos")]
870 Backend::Metal => false, Backend::None => false,
872 }
873}
874
875pub struct MoeJob<'a> {
879 pub gate: (usize, usize, usize, &'a [f32]),
880 pub up: (usize, usize, usize, &'a [f32]),
881 pub down: (usize, usize, usize, &'a [f32]),
882 pub xs_gate: Vec<f32>,
883 pub xs_up: Vec<f32>,
884 pub down_col: &'a [f32],
885 pub w: f32,
886 pub q1: bool,
889 pub q4t: bool,
892 pub q4tp: bool,
896 pub gu_q2: bool,
900 pub swiglu_limit: f32,
905}
906
907pub struct BatchJob<'a> {
909 pub idx: usize,
910 pub rows: usize,
911 pub cols: usize,
912 pub row_scale: &'a [f32],
913 pub xs: Vec<f32>,
914 pub layout: BatchLayout,
918}
919
920#[derive(Clone, Copy, PartialEq, Eq, Debug)]
923pub enum BatchLayout {
924 Q8,
925 Q1,
926 Q4t,
927 Q4tp,
928}
929
930#[derive(Clone, Copy, PartialEq, Eq)]
931enum Backend {
932 None,
933 #[cfg(target_os = "macos")]
934 Metal,
935 #[cfg(feature = "gpu")]
936 Wgpu,
937}
938
939fn backend() -> Backend {
940 #[cfg(feature = "gpu")]
941 if crate::gpu_wgpu::selected() {
942 return if crate::gpu_wgpu::enabled() {
943 Backend::Wgpu
944 } else {
945 Backend::None
946 };
947 }
948 #[cfg(target_os = "macos")]
949 if crate::gpu_metal::enabled() {
950 return Backend::Metal;
951 }
952 Backend::None
953}
954
955pub fn backend_available() -> bool {
961 #[cfg(target_os = "macos")]
962 {
963 true
965 }
966 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
967 {
968 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
969 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
970 }
971 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
972 {
973 false
974 }
975}
976
977static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
983
984pub fn pause_gpu() -> GpuPause {
986 GPU_PAUSED.store(true, Ordering::Relaxed);
987 GpuPause(())
988}
989
990pub struct GpuPause(());
991
992impl Drop for GpuPause {
993 fn drop(&mut self) {
994 GPU_PAUSED.store(false, Ordering::Relaxed);
995 }
996}
997
998pub fn enabled() -> bool {
999 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
1000}
1001
1002pub fn wgpu_active() -> bool {
1016 #[cfg(feature = "gpu")]
1017 {
1018 matches!(backend(), Backend::Wgpu)
1019 }
1020 #[cfg(not(feature = "gpu"))]
1021 {
1022 false
1023 }
1024}
1025
1026pub fn default_device() -> usize {
1033 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1034 *D.get_or_init(|| {
1035 std::env::var("CMF_GPU_ADAPTER")
1036 .ok()
1037 .and_then(|v| v.trim().parse::<usize>().ok())
1038 .unwrap_or(0)
1039 })
1040}
1041
1042thread_local! {
1043 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1044}
1045
1046pub fn current_device() -> usize {
1048 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1049}
1050
1051pub fn set_current_device(i: usize) {
1055 CUR_DEV.with(|c| c.set(Some(i)));
1056}
1057
1058pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1060 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1061 let r = f();
1062 CUR_DEV.with(|c| c.set(prev));
1063 r
1064}
1065
1066pub fn device_count() -> usize {
1069 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1070 {
1071 return crate::gpu_wgpu::adapter_count();
1072 }
1073 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1074 {
1075 usize::from(backend_available())
1076 }
1077}
1078
1079pub fn vram_budget() -> u64 {
1083 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1084 {
1085 return crate::gpu_wgpu::device_vram_budget();
1086 }
1087 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1088 {
1089 if backend_available() { u64::MAX } else { 0 }
1090 }
1091}
1092
1093pub fn resident_bytes() -> u64 {
1097 #[cfg(feature = "gpu")]
1098 {
1099 if backend() == Backend::Wgpu {
1100 return crate::gpu_wgpu::resident_bytes();
1101 }
1102 }
1103 0
1104}
1105
1106pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1110 #[cfg(feature = "gpu")]
1111 {
1112 if backend() == Backend::Wgpu {
1113 return crate::gpu_wgpu::o1_device_stats(kv_id);
1114 }
1115 }
1116 let _ = kv_id;
1117 (0, 0)
1118}
1119
1120pub fn upload_bytes() -> u64 {
1124 #[cfg(feature = "gpu")]
1125 {
1126 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1127 }
1128 #[cfg(not(feature = "gpu"))]
1129 0
1130}
1131
1132pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1136 #[cfg(feature = "gpu")]
1137 {
1138 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1139 }
1140 let _ = (block, rounds);
1141 None
1142}
1143
1144#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1156pub enum GraphPhase {
1157 Prefill,
1158 Decode,
1159}
1160
1161pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1169 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1170 Some("0") => false,
1171 Some("prefill") => phase == GraphPhase::Prefill,
1172 Some(_) => true,
1173 None => {
1174 if wgpu_graph_default() {
1175 return true;
1176 }
1177 let _ = phase;
1182 false
1183 }
1184 }
1185}
1186
1187pub fn wgpu_graph_default() -> bool {
1188 #[cfg(feature = "gpu")]
1189 {
1190 matches!(backend(), Backend::Wgpu)
1196 && (crate::gpu_wgpu::discrete_active()
1197 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1198 }
1199 #[cfg(not(feature = "gpu"))]
1200 {
1201 false
1202 }
1203}
1204
1205#[allow(clippy::too_many_arguments, unused_variables)]
1207pub fn q8_matvec_range(
1208 model: &Arc<CmfModel>,
1209 idx: usize,
1210 row0: usize,
1211 row_scale: &[f32],
1212 xs: &[f32],
1213 rows: usize,
1214 cols: usize,
1215 out: &mut [f32],
1216) -> bool {
1217 match backend() {
1218 #[cfg(target_os = "macos")]
1219 Backend::Metal => {
1220 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1221 }
1222 #[cfg(feature = "gpu")]
1223 Backend::Wgpu => {
1224 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1225 }
1226 Backend::None => false,
1227 }
1228}
1229
1230#[allow(clippy::too_many_arguments, unused_variables)]
1233#[allow(clippy::too_many_arguments)]
1237pub fn q8_matmat_2f(
1238 model: &Arc<CmfModel>,
1239 idx: usize,
1240 row_scale: &[f32],
1241 col_field: &[f32],
1242 xs: &[f32],
1243 b: usize,
1244 rows: usize,
1245 cols: usize,
1246 out: &mut [f32],
1247) -> bool {
1248 #[allow(unreachable_patterns)]
1249 match backend() {
1250 #[cfg(feature = "gpu")]
1251 Backend::Wgpu => {
1252 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1253 }
1254 _ => false,
1255 }
1256}
1257
1258pub fn q8_matmat(
1259 model: &Arc<CmfModel>,
1260 idx: usize,
1261 row_scale: &[f32],
1262 pre: &[f32],
1263 b: usize,
1264 rows: usize,
1265 cols: usize,
1266 out: &mut [f32],
1267) -> bool {
1268 match backend() {
1269 #[cfg(target_os = "macos")]
1270 Backend::Metal => {
1271 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1272 }
1273 #[cfg(feature = "gpu")]
1274 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1275 Backend::None => false,
1276 }
1277}
1278
1279#[allow(unused_variables)]
1282pub fn q1_matvec(
1283 model: &Arc<CmfModel>,
1284 idx: usize,
1285 xs: &[f32],
1286 rows: usize,
1287 cols: usize,
1288 out: &mut [f32],
1289) -> bool {
1290 match backend() {
1291 #[cfg(target_os = "macos")]
1292 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1293 #[cfg(feature = "gpu")]
1294 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1295 Backend::None => false,
1296 }
1297}
1298
1299#[allow(clippy::too_many_arguments)]
1303pub fn attn_dropin(
1304 model: &Arc<CmfModel>,
1305 kv_id: u64,
1306 layer: usize,
1307 normed: &[f32],
1308 wq_idx: usize,
1309 wk_idx: usize,
1310 wv_idx: usize,
1311 wo_idx: usize,
1312 q_norm: Option<&[f32]>,
1313 k_norm: Option<&[f32]>,
1314 late_qk_norm: bool,
1315 invf: &[f32],
1316 nh: usize,
1317 nkv: usize,
1318 hd: usize,
1319 rd: usize,
1320 hidden: usize,
1321 pos: usize,
1322 cap: usize,
1323 gemma: bool,
1324 eps: f32,
1325 cpu_k: &[Vec<f32>],
1326 cpu_v: &[Vec<f32>],
1327 out: &mut [f32],
1328) -> bool {
1329 match backend() {
1330 #[cfg(feature = "gpu")]
1331 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1332 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1333 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1334 ),
1335 #[allow(unused_variables)]
1336 _ => false,
1337 }
1338}
1339
1340#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1344pub enum GraphPrismOp {
1345 None,
1346 Forward,
1347 InverseEmbedding,
1348}
1349
1350pub struct GraphW<'a> {
1354 pub idx: usize,
1355 pub kind: u8,
1356 pub row_scale: &'a [f32],
1357 pub data: &'a [f32],
1358 pub prism: GraphPrismOp,
1359 pub affine: bool,
1360}
1361
1362pub enum GraphAttn<'a> {
1365 Full {
1366 wq: GraphW<'a>,
1367 wk: GraphW<'a>,
1368 wv: GraphW<'a>,
1369 wo: GraphW<'a>,
1370 q_norm: Option<&'a [f32]>,
1371 k_norm: Option<&'a [f32]>,
1372 late_qk_norm: bool,
1374 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1376 output_gate: bool,
1379 cpu_k: &'a [Vec<f32>],
1380 cpu_v: &'a [Vec<f32>],
1381 },
1382 Gdn {
1383 qkv: GraphW<'a>,
1384 z: GraphW<'a>,
1385 a: GraphW<'a>,
1386 b: GraphW<'a>,
1387 out: GraphW<'a>,
1388 conv1d: &'a [f32],
1389 a_log: &'a [f32],
1390 dt_bias: &'a [f32],
1391 norm: &'a [f32],
1392 nv: usize,
1393 nk: usize,
1394 dk: usize,
1395 dv: usize,
1396 kk: usize,
1397 cpu_state: &'a [f32],
1402 },
1403 ShortConv {
1410 inp: GraphW<'a>,
1412 out: GraphW<'a>,
1414 taps: &'a [f32],
1417 kernel: usize,
1418 cpu_state: &'a [f32],
1422 },
1423}
1424
1425pub struct GraphLayer<'a> {
1427 pub input_norm: &'a [f32],
1428 pub attn: GraphAttn<'a>,
1429 pub post_norm: &'a [f32],
1430 pub ffn: GraphFfn<'a>,
1431}
1432
1433pub enum GraphFfn<'a> {
1438 Dense {
1439 gate: GraphW<'a>,
1440 up: GraphW<'a>,
1441 down: GraphW<'a>,
1442 },
1443 Moe {
1444 router: GraphW<'a>,
1446 shared_gate: GraphW<'a>,
1448 experts: Vec<(usize, usize, usize)>,
1452 n_exp: usize,
1454 top_k: usize,
1455 inter: usize,
1456 norm_topk: bool,
1457 q4tp: bool,
1463 gu_q2: bool,
1467 sigmoid: bool,
1471 bias: Option<&'a [f32]>,
1474 has_shared: bool,
1478 shared_gated: bool,
1483 route_scale: f32,
1486 },
1487}
1488
1489#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1492pub enum TokenGraphOutcome {
1493 Declined,
1495 Completed,
1497 Failed,
1499}
1500
1501#[allow(clippy::too_many_arguments)]
1506pub fn forward_token_graph(
1507 model: &Arc<CmfModel>,
1508 kv_id: u64,
1509 layers: &[GraphLayer],
1510 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1513 o1_epoch: u64,
1514 invf: &[f32],
1515 h: &mut [f32],
1516 nh: usize,
1517 nkv: usize,
1518 hd: usize,
1519 attn_scale: f32,
1520 rd: usize,
1521 hidden: usize,
1522 inter: usize,
1523 position: usize,
1524 cap: usize,
1525 gemma: bool,
1526 eps: f32,
1527 lm_head: Option<(&GraphW, usize)>,
1528 final_norm: &[f32],
1529 logits: &mut Vec<f32>,
1530 loop_norm_at: &[usize],
1531 steps: usize,
1532 embed: Option<(&GraphW, usize, f32)>,
1533 ids_out: Option<&mut Vec<u32>>,
1534 layers_run: Option<&mut usize>,
1537 layer_base: usize,
1541 hidden_too: bool,
1543) -> TokenGraphOutcome {
1544 match backend() {
1545 #[cfg(feature = "gpu")]
1546 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1547 model,
1548 kv_id,
1549 layers,
1550 o1,
1551 o1_epoch,
1552 invf,
1553 h,
1554 nh,
1555 nkv,
1556 hd,
1557 attn_scale,
1558 rd,
1559 hidden,
1560 inter,
1561 position,
1562 cap,
1563 gemma,
1564 eps,
1565 lm_head,
1566 final_norm,
1567 logits,
1568 loop_norm_at,
1569 steps,
1570 embed,
1571 ids_out,
1572 layers_run,
1573 layer_base,
1574 hidden_too,
1575 ),
1576 #[allow(unused_variables)]
1577 _ => {
1578 let _ = (
1579 attn_scale,
1580 lm_head,
1581 final_norm,
1582 logits,
1583 loop_norm_at,
1584 layers_run,
1585 layer_base,
1586 hidden_too,
1587 );
1588 TokenGraphOutcome::Declined
1589 }
1590 }
1591}
1592
1593#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1597pub enum BatchGraphOutcome {
1598 Declined,
1601 Completed,
1603 Failed,
1607}
1608
1609pub struct SpecTail<'a> {
1610 pub lm: GraphW<'a>,
1611 pub lm_rows: usize,
1612 pub final_norm: &'a [f32],
1613 pub logits_out: &'a mut Vec<f32>,
1614}
1615
1616#[allow(clippy::too_many_arguments)]
1620pub fn forward_batch_graph(
1621 model: &Arc<CmfModel>,
1622 kv_id: u64,
1623 layers: &[GraphLayer],
1624 invf: &[f32],
1625 h: &mut [f32],
1626 nh: usize,
1627 nkv: usize,
1628 hd: usize,
1629 rd: usize,
1630 hidden: usize,
1631 inter: usize,
1632 positions: &[usize],
1633 cap: usize,
1634 gemma: bool,
1635 eps: f32,
1636 attn_scale: f32,
1637 k: usize,
1638 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1641 o1_epoch: u64,
1642 spec: Option<SpecTail<'_>>,
1643) -> BatchGraphOutcome {
1644 match backend() {
1645 #[cfg(feature = "gpu")]
1646 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1647 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1648 eps, attn_scale, k, o1, o1_epoch, spec,
1649 ),
1650 #[allow(unreachable_patterns)]
1651 _ => {
1652 let _ = (o1, o1_epoch, spec);
1653 BatchGraphOutcome::Declined
1654 }
1655 }
1656}
1657
1658pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
1663 #[cfg(feature = "gpu")]
1664 if backend() == Backend::Wgpu {
1665 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
1666 }
1667 #[allow(unreachable_code)]
1668 {
1669 let _ = (kv_id, slot, base_pos, expected_layers);
1670 false
1671 }
1672}
1673
1674pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
1680 #[cfg(feature = "gpu")]
1681 if backend() == Backend::Wgpu {
1682 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
1683 }
1684 #[cfg(target_os = "macos")]
1685 if backend() == Backend::Metal {
1686 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
1687 return true;
1688 }
1689 false
1690}
1691
1692pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
1696 #[cfg(feature = "gpu")]
1697 if backend() == Backend::Wgpu {
1698 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
1699 }
1700 None
1701}
1702
1703pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
1706 #[cfg(feature = "gpu")]
1707 if backend() == Backend::Wgpu {
1708 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
1709 }
1710 false
1711}
1712
1713pub fn graph_kv_read_rows(
1717 _kv_id: u64,
1718 _reqs: &[(usize, usize, usize)],
1719 _nkv: usize,
1720 _hd: usize,
1721) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
1722 #[cfg(feature = "gpu")]
1723 if backend() == Backend::Wgpu {
1724 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
1725 }
1726 None
1727}
1728
1729pub fn graph_kv_reset(_kv_id: u64) {
1731 #[cfg(feature = "gpu")]
1732 if backend() == Backend::Wgpu {
1733 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1734 }
1735}
1736
1737pub fn q1t_matvec(
1741 model: &Arc<CmfModel>,
1742 idx: usize,
1743 xs: &[f32],
1744 rows: usize,
1745 cols: usize,
1746 out: &mut [f32],
1747) -> bool {
1748 match backend() {
1749 #[cfg(target_os = "macos")]
1750 Backend::Metal => {
1751 if metal_q1t_enabled() {
1752 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1753 } else {
1754 false
1755 }
1756 }
1757 #[cfg(feature = "gpu")]
1758 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1759 Backend::None => false,
1760 }
1761}
1762
1763#[allow(unused_variables)]
1766pub fn q4b_matvec(
1767 model: &Arc<CmfModel>,
1768 idx: usize,
1769 xs: &[f32],
1770 rows: usize,
1771 cols: usize,
1772 out: &mut [f32],
1773) -> bool {
1774 match backend() {
1775 #[cfg(target_os = "macos")]
1776 Backend::Metal => false,
1777 #[cfg(feature = "gpu")]
1778 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1779 Backend::None => false,
1780 }
1781}
1782
1783pub fn q1t_matmat(
1786 model: &Arc<CmfModel>,
1787 idx: usize,
1788 xs: &[f32],
1789 b: usize,
1790 rows: usize,
1791 cols: usize,
1792 out: &mut [f32],
1793) -> bool {
1794 match backend() {
1795 #[cfg(target_os = "macos")]
1796 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1800 #[cfg(feature = "gpu")]
1801 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1802 Backend::None => false,
1803 }
1804}
1805
1806#[cfg(target_os = "macos")]
1810pub(crate) fn metal_q1t_enabled() -> bool {
1811 std::env::var("CMF_METAL_Q1T")
1812 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1813 .unwrap_or(true)
1814}
1815
1816pub fn q1_matmat(
1818 model: &Arc<CmfModel>,
1819 idx: usize,
1820 xs: &[f32],
1821 b: usize,
1822 rows: usize,
1823 cols: usize,
1824 out: &mut [f32],
1825) -> bool {
1826 match backend() {
1827 #[cfg(feature = "gpu")]
1828 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1829 #[allow(unused_variables)]
1830 _ => false,
1831 }
1832}
1833
1834static MM_KILL: AtomicBool = AtomicBool::new(false);
1839pub(crate) fn mm_killed() -> bool {
1840 MM_KILL.load(Ordering::Relaxed)
1841}
1842pub(crate) fn mm_kill() {
1843 MM_KILL.store(true, Ordering::Relaxed);
1844}
1845
1846static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1853const MM_STRIKES_TO_KILL: u32 = 3;
1854static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1861
1862pub fn mm_kill_arm(on: bool) {
1865 MM_ARMED.store(on, Ordering::Relaxed);
1866 if on {
1867 MM_STRIKES.store(0, Ordering::Relaxed);
1868 }
1869}
1870
1871pub(crate) fn mm_budget_check(
1878 what: &str,
1879 el: std::time::Duration,
1880 budget: std::time::Duration,
1881 exempt: bool,
1882) {
1883 if el <= budget {
1884 MM_STRIKES.store(0, Ordering::Relaxed);
1885 return;
1886 }
1887 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1888 return;
1889 }
1890 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1891 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1892 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1893 if !on {
1894 tracing::info!(
1895 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1896 );
1897 return;
1898 }
1899 if n >= MM_STRIKES_TO_KILL {
1900 tracing::warn!(
1901 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1902 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1903 );
1904 mm_kill();
1905 } else {
1906 tracing::info!(
1907 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1908 );
1909 }
1910}
1911
1912#[allow(unused_variables, clippy::too_many_arguments)]
1917pub fn chunk_attend(
1918 q: &[f32],
1919 k: &[&[f32]],
1920 v: &[&[f32]],
1921 b: usize,
1922 s0: usize,
1923 nh: usize,
1924 nkv: usize,
1925 hd: usize,
1926 scale: f32,
1927 out: &mut [f32],
1928) -> bool {
1929 match backend() {
1930 #[cfg(feature = "gpu")]
1931 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1932 #[allow(unreachable_patterns)]
1933 _ => false,
1934 }
1935}
1936
1937#[allow(unused_variables, clippy::too_many_arguments)]
1941pub fn q4t_qkv(
1942 model: &Arc<CmfModel>,
1943 wq: usize,
1944 wk: usize,
1945 wv: usize,
1946 xs: &[f32],
1947 b: usize,
1948 cols: usize,
1949 rq: usize,
1950 rk: usize,
1951 rv: usize,
1952 out: &mut [f32],
1953) -> bool {
1954 match backend() {
1955 #[cfg(feature = "gpu")]
1956 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1957 #[allow(unreachable_patterns)]
1958 _ => false,
1959 }
1960}
1961
1962#[allow(unused_variables, clippy::too_many_arguments)]
1964#[allow(clippy::too_many_arguments, unused_variables)]
1968pub fn q4tp_ffn_packed(
1969 model: &Arc<CmfModel>,
1970 w1: usize,
1971 w2: usize,
1972 xs: &[f32],
1973 b: usize,
1974 hidden: usize,
1975 inter: usize,
1976 bias: Option<&[f32]>,
1977 out: &mut [f32],
1978) -> bool {
1979 match backend() {
1980 #[cfg(feature = "gpu")]
1981 Backend::Wgpu => {
1982 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1983 }
1984 #[allow(unreachable_patterns)]
1985 _ => false,
1986 }
1987}
1988
1989pub fn q4tp_ffn(
1990 model: &Arc<CmfModel>,
1991 w1: usize,
1992 w3: usize,
1993 w2: usize,
1994 xs: &[f32],
1995 b: usize,
1996 hidden: usize,
1997 inter: usize,
1998 out: &mut [f32],
1999) -> bool {
2000 match backend() {
2001 #[cfg(target_os = "macos")]
2002 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2003 #[cfg(feature = "gpu")]
2004 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2005 #[allow(unreachable_patterns)]
2006 _ => false,
2007 }
2008}
2009
2010#[allow(clippy::too_many_arguments, unused_variables)]
2015pub fn q4tp_gelu_ffn(
2016 model: &Arc<CmfModel>,
2017 w_in: usize,
2018 w_out: usize,
2019 xs: &[f32],
2020 b: usize,
2021 hidden: usize,
2022 inter: usize,
2023 bias_in: &[f32],
2024 bias_out: &[f32],
2025 out: &mut [f32],
2026) -> bool {
2027 match backend() {
2028 #[cfg(feature = "gpu")]
2029 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2030 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2031 ),
2032 #[allow(unreachable_patterns)]
2033 _ => false,
2034 }
2035}
2036
2037pub fn q4t_ffn(
2038 model: &Arc<CmfModel>,
2039 w1: usize,
2040 w3: usize,
2041 w2: usize,
2042 xs: &[f32],
2043 b: usize,
2044 hidden: usize,
2045 inter: usize,
2046 out: &mut [f32],
2047) -> bool {
2048 match backend() {
2049 #[cfg(target_os = "macos")]
2050 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2051 #[cfg(feature = "gpu")]
2052 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2053 #[allow(unreachable_patterns)]
2054 _ => false,
2055 }
2056}
2057
2058pub struct DitBlockArgs<'a> {
2063 pub n: usize,
2064 pub hidden: usize,
2065 pub inter: usize,
2066 pub nh: usize,
2067 pub nkv: usize,
2068 pub hd: usize,
2069 pub eps: f32,
2070 pub rope_cos: &'a [f32],
2071 pub rope_sin: &'a [f32],
2072 pub norm1: &'a [f32],
2073 pub norm2: &'a [f32],
2074 pub ffn_norm1: &'a [f32],
2075 pub ffn_norm2: &'a [f32],
2076 pub norm_q: &'a [f32],
2077 pub norm_k: &'a [f32],
2078 pub s_msa: &'a [f32],
2079 pub gate_msa: &'a [f32],
2080 pub s_mlp: &'a [f32],
2081 pub gate_mlp: &'a [f32],
2082 pub wq: usize,
2083 pub wk: usize,
2084 pub wv: usize,
2085 pub wo: usize,
2086 pub w1: usize,
2087 pub w3: usize,
2088 pub w2: usize,
2089 pub q4tp: bool,
2093 pub resident_in: bool,
2096 pub resident_out: bool,
2100}
2101
2102pub fn dit_chain_supported() -> bool {
2106 #[cfg(feature = "gpu")]
2107 {
2108 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2109 }
2110 #[allow(unreachable_code)]
2111 false
2112}
2113
2114pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2117 #[cfg(feature = "gpu")]
2118 {
2119 if matches!(backend(), Backend::Wgpu) {
2120 return crate::gpu_wgpu::dit_state_fetch(_x);
2121 }
2122 }
2123 false
2124}
2125
2126#[allow(unused_variables)]
2130#[allow(unused_variables, clippy::too_many_arguments)]
2134pub fn dit_qkv(
2135 model: &Arc<CmfModel>,
2136 wq: usize,
2137 wk: usize,
2138 wv: usize,
2139 xs: &[f32],
2140 b: usize,
2141 hidden: usize,
2142 qrows: usize,
2143 kvrows: usize,
2144 q_out: &mut [f32],
2145 k_out: &mut [f32],
2146 v_out: &mut [f32],
2147) -> bool {
2148 match backend() {
2149 #[cfg(feature = "gpu")]
2150 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2151 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2152 ),
2153 #[allow(unreachable_patterns)]
2154 _ => false,
2155 }
2156}
2157
2158pub struct QwenImageAttentionArgs<'a> {
2166 pub image: &'a [f32],
2167 pub text: &'a [f32],
2168 pub image_tokens: usize,
2169 pub text_tokens: usize,
2170 pub heads: usize,
2171 pub head_dim: usize,
2172 pub image_q: usize,
2173 pub image_k: usize,
2174 pub image_v: usize,
2175 pub text_q: usize,
2176 pub text_k: usize,
2177 pub text_v: usize,
2178 pub image_out: usize,
2179 pub text_out: usize,
2180 pub image_q_norm: &'a [f32],
2181 pub image_k_norm: &'a [f32],
2182 pub text_q_norm: &'a [f32],
2183 pub text_k_norm: &'a [f32],
2184 pub image_cos: &'a [f32],
2185 pub image_sin: &'a [f32],
2186 pub text_cos: &'a [f32],
2187 pub text_sin: &'a [f32],
2188 pub image_q_bias: &'a [f32],
2189 pub image_k_bias: &'a [f32],
2190 pub image_v_bias: &'a [f32],
2191 pub text_q_bias: &'a [f32],
2192 pub text_k_bias: &'a [f32],
2193 pub text_v_bias: &'a [f32],
2194 pub image_out_bias: &'a [f32],
2195 pub text_out_bias: &'a [f32],
2196 pub image_proj: &'a mut [f32],
2197 pub text_proj: &'a mut [f32],
2198}
2199
2200#[allow(clippy::too_many_fields)]
2205pub struct QwenImageChainBlock<'a> {
2206 pub image_mod: &'a [f32],
2207 pub text_mod: &'a [f32],
2208 pub image_q: usize,
2209 pub image_k: usize,
2210 pub image_v: usize,
2211 pub text_q: usize,
2212 pub text_k: usize,
2213 pub text_v: usize,
2214 pub image_out: usize,
2215 pub text_out: usize,
2216 pub image_q_norm: &'a [f32],
2217 pub image_k_norm: &'a [f32],
2218 pub text_q_norm: &'a [f32],
2219 pub text_k_norm: &'a [f32],
2220 pub image_q_bias: &'a [f32],
2221 pub image_k_bias: &'a [f32],
2222 pub image_v_bias: &'a [f32],
2223 pub text_q_bias: &'a [f32],
2224 pub text_k_bias: &'a [f32],
2225 pub text_v_bias: &'a [f32],
2226 pub image_out_bias: &'a [f32],
2227 pub text_out_bias: &'a [f32],
2228 pub image_attn_gate: &'a [f32],
2229 pub text_attn_gate: &'a [f32],
2230 pub image_mlp_in: usize,
2231 pub image_mlp_out: usize,
2232 pub text_mlp_in: usize,
2233 pub text_mlp_out: usize,
2234 pub image_mlp_in_bias: &'a [f32],
2235 pub image_mlp_out_bias: &'a [f32],
2236 pub text_mlp_in_bias: &'a [f32],
2237 pub text_mlp_out_bias: &'a [f32],
2238}
2239
2240#[allow(clippy::too_many_fields)]
2246pub struct QwenImageBlockArgs<'a> {
2247 pub image: &'a mut [f32],
2250 pub text: &'a mut [f32],
2251 pub image_norm: &'a [f32],
2252 pub text_norm: &'a [f32],
2253 pub image_tokens: usize,
2254 pub text_tokens: usize,
2255 pub heads: usize,
2256 pub head_dim: usize,
2257 pub image_cos: &'a [f32],
2258 pub image_sin: &'a [f32],
2259 pub text_cos: &'a [f32],
2260 pub text_sin: &'a [f32],
2261 pub image_q: usize,
2262 pub image_k: usize,
2263 pub image_v: usize,
2264 pub text_q: usize,
2265 pub text_k: usize,
2266 pub text_v: usize,
2267 pub image_out: usize,
2268 pub text_out: usize,
2269 pub image_q_norm: &'a [f32],
2270 pub image_k_norm: &'a [f32],
2271 pub text_q_norm: &'a [f32],
2272 pub text_k_norm: &'a [f32],
2273 pub image_q_bias: &'a [f32],
2274 pub image_k_bias: &'a [f32],
2275 pub image_v_bias: &'a [f32],
2276 pub text_q_bias: &'a [f32],
2277 pub text_k_bias: &'a [f32],
2278 pub text_v_bias: &'a [f32],
2279 pub image_out_bias: &'a [f32],
2280 pub text_out_bias: &'a [f32],
2281 pub image_attn_gate: &'a [f32],
2282 pub text_attn_gate: &'a [f32],
2283 pub image_mlp_in: usize,
2284 pub image_mlp_out: usize,
2285 pub text_mlp_in: usize,
2286 pub text_mlp_out: usize,
2287 pub image_mlp_in_bias: &'a [f32],
2288 pub image_mlp_out_bias: &'a [f32],
2289 pub text_mlp_in_bias: &'a [f32],
2290 pub text_mlp_out_bias: &'a [f32],
2291 pub image_mlp_mod: &'a [f32],
2292 pub text_mlp_mod: &'a [f32],
2293 pub image_mlp_gate: &'a [f32],
2294 pub text_mlp_gate: &'a [f32],
2295}
2296
2297pub struct QwenImageChainArgs<'a> {
2302 pub image: &'a mut [f32],
2303 pub text: &'a mut [f32],
2304 pub image_tokens: usize,
2305 pub text_tokens: usize,
2306 pub heads: usize,
2307 pub head_dim: usize,
2308 pub image_cos: &'a [f32],
2309 pub image_sin: &'a [f32],
2310 pub text_cos: &'a [f32],
2311 pub text_sin: &'a [f32],
2312 pub blocks: &'a [QwenImageChainBlock<'a>],
2313}
2314
2315#[allow(unused_variables)]
2316pub fn qwen_image_attention(
2317 model: &Arc<CmfModel>,
2318 args: &mut QwenImageAttentionArgs<'_>,
2319) -> bool {
2320 match backend() {
2321 #[cfg(feature = "gpu")]
2322 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
2323 #[allow(unreachable_patterns)]
2324 _ => false,
2325 }
2326}
2327
2328#[allow(unused_variables)]
2329pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
2330 match backend() {
2331 #[cfg(feature = "gpu")]
2332 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
2333 #[allow(unreachable_patterns)]
2334 _ => false,
2335 }
2336}
2337
2338#[allow(unused_variables)]
2342pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
2343 match backend() {
2344 #[cfg(feature = "gpu")]
2345 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
2346 #[allow(unreachable_patterns)]
2347 _ => false,
2348 }
2349}
2350
2351#[allow(unused_variables, clippy::too_many_arguments)]
2358pub fn qwen_image_mlp_inplace(
2359 model: &Arc<CmfModel>,
2360 w_in: usize,
2361 w_out: usize,
2362 data: &mut [f32],
2363 batch: usize,
2364 hidden: usize,
2365 inter: usize,
2366 bias_in: &[f32],
2367 bias_out: &[f32],
2368 modulation: &[f32],
2369 gate: &[f32],
2370) -> bool {
2371 match backend() {
2372 #[cfg(feature = "gpu")]
2373 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
2374 model,
2375 w_in,
2376 w_out,
2377 data,
2378 batch,
2379 hidden,
2380 inter,
2381 bias_in,
2382 bias_out,
2383 modulation,
2384 gate,
2385 ),
2386 #[allow(unreachable_patterns)]
2387 _ => false,
2388 }
2389}
2390
2391pub fn fused_dit_block_available() -> bool {
2395 #[cfg(target_os = "macos")]
2396 {
2397 matches!(backend(), Backend::Metal) && fused_block_trusted()
2398 }
2399 #[cfg(not(target_os = "macos"))]
2400 {
2401 false
2402 }
2403}
2404
2405pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
2406 dit_block_seg(model, a, &[a.n], x)
2407}
2408
2409pub fn dit_block_seg(
2413 model: &Arc<CmfModel>,
2414 a: &DitBlockArgs,
2415 segs: &[usize],
2416 x: &mut [f32],
2417) -> bool {
2418 match backend() {
2419 #[cfg(target_os = "macos")]
2420 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
2421 #[cfg(feature = "gpu")]
2428 Backend::Wgpu
2429 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
2430 Some("0") => false,
2431 Some(_) => true,
2432 None => crate::gpu_wgpu::discrete_active(),
2433 } =>
2434 {
2435 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
2436 }
2437 #[allow(unreachable_patterns)]
2438 _ => false,
2439 }
2440}
2441
2442pub struct VaeResnetArgs<'a> {
2446 pub groups: usize,
2447 pub ic: usize,
2448 pub oc: usize,
2449 pub h: usize,
2450 pub w: usize,
2451 pub n1w: &'a [f32],
2452 pub n1b: &'a [f32],
2453 pub c1w: &'a [f32],
2454 pub c1b: &'a [f32],
2455 pub c1k: usize,
2456 pub n2w: &'a [f32],
2457 pub n2b: &'a [f32],
2458 pub c2w: &'a [f32],
2459 pub c2b: &'a [f32],
2460 pub c2k: usize,
2461 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
2462}
2463
2464#[allow(unused_variables)]
2467pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
2468 match backend() {
2469 #[cfg(target_os = "macos")]
2470 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
2471 _ => false,
2472 }
2473}
2474
2475#[allow(unused_variables, clippy::too_many_arguments)]
2478pub fn vae_upsample_conv(
2479 w: &[f32],
2480 bias: &[f32],
2481 x: &[f32],
2482 ic: usize,
2483 oc: usize,
2484 h: usize,
2485 w_img: usize,
2486 k: usize,
2487 out: &mut [f32],
2488) -> bool {
2489 match backend() {
2490 #[cfg(target_os = "macos")]
2491 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2492 #[cfg(feature = "gpu")]
2493 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
2494 #[allow(unreachable_patterns)]
2495 _ => false,
2496 }
2497}
2498
2499#[allow(unused_variables, clippy::too_many_arguments)]
2502pub fn vae_conv2d(
2503 w: &[f32],
2504 bias: &[f32],
2505 x: &[f32],
2506 ic: usize,
2507 oc: usize,
2508 h: usize,
2509 w_img: usize,
2510 k: usize,
2511 out: &mut [f32],
2512) -> bool {
2513 match backend() {
2514 #[cfg(target_os = "macos")]
2515 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2516 #[cfg(feature = "gpu")]
2517 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
2518 #[allow(unreachable_patterns)]
2519 _ => false,
2520 }
2521}
2522
2523#[allow(unused_variables, clippy::too_many_arguments)]
2527#[allow(unused_variables)]
2531#[allow(clippy::too_many_arguments)]
2532#[allow(clippy::too_many_arguments, unused_variables)]
2535pub fn dit_qkv_attention(
2536 model: &Arc<CmfModel>,
2537 qkv_idx: usize,
2538 xn: &[f32],
2539 n: usize,
2540 hidden: usize,
2541 nh: usize,
2542 hd: usize,
2543 scale: f32,
2544 nr: (&[f32], &[f32], &[f32], f32),
2545 out: &mut [f32],
2546) -> bool {
2547 match backend() {
2548 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2549 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
2550 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
2551 ),
2552 #[allow(unreachable_patterns)]
2553 _ => false,
2554 }
2555}
2556
2557#[allow(clippy::too_many_arguments)]
2560pub fn dit_qkv_attn_out(
2561 model: &Arc<CmfModel>,
2562 qkv_idx: usize,
2563 out_idx: usize,
2564 xn: &[f32],
2565 n: usize,
2566 hidden: usize,
2567 nh: usize,
2568 hd: usize,
2569 scale: f32,
2570 nr: (&[f32], &[f32], &[f32], f32),
2571 proj: &mut [f32],
2572) -> bool {
2573 match backend() {
2574 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2575 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
2576 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
2577 ),
2578 #[allow(unreachable_patterns)]
2579 _ => false,
2580 }
2581}
2582
2583#[allow(clippy::too_many_arguments)]
2585pub fn vae_qkv_attn_out(
2586 model: &Arc<CmfModel>,
2587 qkv_idx: usize,
2588 out_idx: usize,
2589 xn: &[f32],
2590 n: usize,
2591 dim: usize,
2592 nh: usize,
2593 hd: usize,
2594 scale: f32,
2595 angles: &[f32],
2596 eps: f32,
2597 qkv_bias: &[f32],
2598 proj: &mut [f32],
2599) -> bool {
2600 match backend() {
2601 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2602 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
2603 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
2604 ),
2605 #[allow(unreachable_patterns)]
2606 _ => false,
2607 }
2608}
2609
2610#[allow(clippy::too_many_arguments)]
2611pub fn vae_attention_packed(
2612 qkv: &[f32],
2613 nh: usize,
2614 n: usize,
2615 hd: usize,
2616 scale: f32,
2617 angles: &[f32],
2618 eps: f32,
2619 out: &mut [f32],
2620) -> bool {
2621 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2622}
2623
2624#[allow(clippy::too_many_arguments)]
2625pub fn vae_attention_packed_layout(
2626 qkv: &[f32],
2627 nh: usize,
2628 n: usize,
2629 hd: usize,
2630 scale: f32,
2631 angles: &[f32],
2632 eps: f32,
2633 out: &mut [f32],
2634 layout: u32,
2635) -> bool {
2636 match backend() {
2637 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2638 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2639 qkv, nh, n, hd, scale, angles, eps, out, layout,
2640 ),
2641 #[allow(unreachable_patterns)]
2642 _ => false,
2643 }
2644}
2645
2646#[allow(clippy::too_many_arguments)]
2647pub fn dit_split_only(
2648 qkv: &[f32],
2649 nh: usize,
2650 n: usize,
2651 hd: usize,
2652 layout: u32,
2653 norm: Option<(&[f32], f32)>,
2654 out_q: &mut [f32],
2655) -> bool {
2656 match backend() {
2657 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2658 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2659 #[allow(unreachable_patterns)]
2660 _ => false,
2661 }
2662}
2663
2664pub fn gemm_nt_f32_transient(
2672 x: &[f32],
2673 w: &[f32],
2674 y: &mut [f32],
2675 n: usize,
2676 k: usize,
2677 m: usize,
2678) -> bool {
2679 match backend() {
2680 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2681 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
2682 #[allow(unreachable_patterns)]
2683 _ => false,
2684 }
2685}
2686
2687pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2688 match backend() {
2689 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2690 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2691 #[allow(unreachable_patterns)]
2692 _ => false,
2693 }
2694}
2695
2696#[allow(clippy::too_many_arguments)]
2699pub fn music3_ffn(
2700 model: &std::sync::Arc<CmfModel>,
2701 idx_in: usize,
2702 idx_out: usize,
2703 h: &[f32],
2704 bias_in: &[f32],
2705 n: usize,
2706 hs: usize,
2707 inter: usize,
2708 out: &mut [f32],
2709) -> bool {
2710 match backend() {
2711 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2712 Backend::Wgpu => {
2713 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2714 }
2715 #[allow(unreachable_patterns)]
2716 _ => false,
2717 }
2718}
2719
2720#[allow(clippy::too_many_arguments)]
2724pub fn conv1d_gemm(
2725 x: &[f32],
2726 w: &[f32],
2727 ic: usize,
2728 oc: usize,
2729 n: usize,
2730 k: usize,
2731 pad: usize,
2732 dil: usize,
2733 out_n: usize,
2734 yt: &mut [f32],
2735) -> bool {
2736 match backend() {
2737 #[cfg(target_os = "macos")]
2738 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2739 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2740 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2741 #[allow(unreachable_patterns)]
2742 _ => false,
2743 }
2744}
2745
2746#[allow(clippy::too_many_arguments)]
2748pub fn vae_conv2d_coop(
2749 w: &[f32],
2750 bias: Option<&[f32]>,
2751 x: &[f32],
2752 ic: usize,
2753 oc: usize,
2754 h: usize,
2755 wi: usize,
2756 k: usize,
2757 out: &mut [f32],
2758) -> bool {
2759 match backend() {
2760 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2761 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2762 #[allow(unreachable_patterns)]
2763 _ => false,
2764 }
2765}
2766
2767pub fn dit_attention_packed(
2768 qkv: &[f32],
2769 nh: usize,
2770 n: usize,
2771 hd: usize,
2772 scale: f32,
2773 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2776 out: &mut [f32],
2777) -> bool {
2778 match backend() {
2779 #[cfg(feature = "gpu")]
2786 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2787 #[allow(unreachable_patterns)]
2788 _ => false,
2789 }
2790}
2791
2792pub fn dit_attention_packed_available() -> bool {
2800 #[allow(unreachable_patterns)]
2801 match backend() {
2802 #[cfg(feature = "gpu")]
2803 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2804 _ => false,
2805 }
2806}
2807
2808pub fn dit_attention(
2809 qh: &[f32],
2810 kh: &[f32],
2811 vh: &[f32],
2812 nh: usize,
2813 nkv: usize,
2814 n: usize,
2815 hd: usize,
2816 scale: f32,
2817 out: &mut [f32],
2818) -> bool {
2819 match backend() {
2820 #[cfg(target_os = "macos")]
2821 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2822 #[cfg(feature = "gpu")]
2823 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2824 #[allow(unreachable_patterns)]
2825 _ => false,
2826 }
2827}
2828
2829#[allow(unused_variables)]
2834pub fn q4tp_matmat(
2835 model: &Arc<CmfModel>,
2836 idx: usize,
2837 xs: &[f32],
2838 b: usize,
2839 rows: usize,
2840 cols: usize,
2841 out: &mut [f32],
2842) -> bool {
2843 match backend() {
2844 #[cfg(target_os = "macos")]
2845 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2846 #[cfg(feature = "gpu")]
2847 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2848 #[allow(unreachable_patterns)]
2849 _ => false,
2850 }
2851}
2852
2853pub fn q2tp_matmat(
2856 model: &Arc<CmfModel>,
2857 idx: usize,
2858 xs: &[f32],
2859 b: usize,
2860 rows: usize,
2861 cols: usize,
2862 out: &mut [f32],
2863) -> bool {
2864 match backend() {
2865 #[cfg(target_os = "macos")]
2866 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2867 #[cfg(feature = "gpu")]
2868 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2869 #[allow(unreachable_patterns)]
2870 _ => false,
2871 }
2872}
2873
2874pub fn q2tp_affine_matmat(
2877 model: &Arc<CmfModel>,
2878 idx: usize,
2879 xs: &[f32],
2880 b: usize,
2881 rows: usize,
2882 cols: usize,
2883 out: &mut [f32],
2884) -> bool {
2885 match backend() {
2886 #[cfg(target_os = "macos")]
2887 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2888 #[cfg(feature = "gpu")]
2889 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
2890 #[allow(unreachable_patterns)]
2891 _ => false,
2892 }
2893}
2894
2895pub fn q2tp_matvec(
2897 model: &Arc<CmfModel>,
2898 idx: usize,
2899 xs: &[f32],
2900 rows: usize,
2901 cols: usize,
2902 out: &mut [f32],
2903) -> bool {
2904 match backend() {
2905 #[cfg(target_os = "macos")]
2906 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
2907 #[cfg(feature = "gpu")]
2908 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
2909 #[allow(unreachable_patterns)]
2910 _ => false,
2911 }
2912}
2913
2914pub fn q2tp_affine_matvec(
2918 model: &Arc<CmfModel>,
2919 idx: usize,
2920 xs: &[f32],
2921 rows: usize,
2922 cols: usize,
2923 out: &mut [f32],
2924) -> bool {
2925 match backend() {
2926 #[cfg(target_os = "macos")]
2927 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2928 #[cfg(feature = "gpu")]
2929 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
2930 #[allow(unreachable_patterns)]
2931 _ => false,
2932 }
2933}
2934
2935pub fn q4tp_matvec(
2940 model: &Arc<CmfModel>,
2941 idx: usize,
2942 xs: &[f32],
2943 rows: usize,
2944 cols: usize,
2945 out: &mut [f32],
2946) -> bool {
2947 match backend() {
2948 #[cfg(target_os = "macos")]
2949 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2950 #[cfg(feature = "gpu")]
2951 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2952 #[allow(unreachable_patterns)]
2953 _ => false,
2954 }
2955}
2956
2957pub fn q4t_matvec(
2963 model: &Arc<CmfModel>,
2964 idx: usize,
2965 xs: &[f32],
2966 rows: usize,
2967 cols: usize,
2968 out: &mut [f32],
2969) -> bool {
2970 match backend() {
2971 #[cfg(target_os = "macos")]
2972 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2973 #[allow(unreachable_patterns)]
2974 _ => false,
2975 }
2976}
2977
2978pub fn q4t_matmat(
2979 model: &Arc<CmfModel>,
2980 idx: usize,
2981 xs: &[f32],
2982 b: usize,
2983 rows: usize,
2984 cols: usize,
2985 out: &mut [f32],
2986) -> bool {
2987 match backend() {
2988 #[cfg(target_os = "macos")]
2989 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2990 #[cfg(feature = "gpu")]
2991 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2992 #[allow(unreachable_patterns)]
2993 _ => false,
2994 }
2995}
2996
2997#[cfg(target_os = "macos")]
2999pub use crate::gpu_metal::{
3000 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
3001 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
3002};
3003
3004#[cfg(target_os = "macos")]
3006pub fn gdn_block(
3007 model: &Arc<CmfModel>,
3008 layers: &[GdnGpuLayer],
3009 states: &mut [&mut [f32]],
3010 cfg: &GdnGpuCfg,
3011 h: &mut [f32],
3012) -> bool {
3013 match backend() {
3014 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
3015 _ => false,
3016 }
3017}
3018
3019#[allow(unused_variables)]
3021pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
3022 match backend() {
3023 #[cfg(target_os = "macos")]
3024 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
3025 #[cfg(feature = "gpu")]
3026 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
3027 Backend::None => false,
3028 }
3029}
3030
3031#[allow(unused_variables)]
3033pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
3034 match backend() {
3035 #[cfg(target_os = "macos")]
3036 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
3037 #[cfg(feature = "gpu")]
3038 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
3039 Backend::None => false,
3040 }
3041}
3042
3043static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
3059static 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)];
3063
3064const GRAPH_RACE_SAMPLES: u32 = 4;
3066
3067static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
3077
3078pub fn graph_mark_unsupported() {
3083 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
3084 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
3085 }
3086}
3087
3088pub fn graph_unsupported() -> bool {
3089 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
3090}
3091
3092pub fn graph_unsupported_reset() {
3094 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
3095}
3096
3097pub fn graph_race_begin_generation() {
3098 #[cfg(feature = "gpu")]
3103 {
3104 static FLUSHED: std::sync::Once = std::sync::Once::new();
3116 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
3117 if FIRST.swap(false, Ordering::Relaxed) {
3118 } else {
3120 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
3121 }
3122 }
3123 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
3124 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3125 return;
3126 }
3127 let (gn, cn) = (
3128 GRAPH_N[1].load(Ordering::Relaxed),
3129 GRAPH_N[0].load(Ordering::Relaxed),
3130 );
3131 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
3132 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
3133 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3134 let verdict = if g_avg < c_avg { 1 } else { 2 };
3135 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
3136 tracing::info!(
3137 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
3138 g_avg as f64 / 1e6,
3139 c_avg as f64 / 1e6,
3140 if verdict == 1 { "graph" } else { "normal path" }
3141 );
3142 return;
3143 }
3144 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
3145 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
3146}
3147
3148pub fn graph_race_use_graph(trusted: bool) -> bool {
3152 if trusted {
3153 return true;
3154 }
3155 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
3156 1 => true,
3157 2 => false,
3158 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
3159 }
3160}
3161
3162pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
3167 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3168 return false;
3169 }
3170 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
3171 let cn = GRAPH_N[0].load(Ordering::Relaxed);
3172 if !first || cn == 0 {
3173 return false;
3174 }
3175 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
3176 let ns = dur.as_nanos() as u64;
3177 if ns > 1_000_000_000 && ns > 4 * c_avg {
3178 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
3179 tracing::info!(
3180 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
3181 ns as f64 / 1e6,
3182 c_avg as f64 / 1e6
3183 );
3184 return true;
3185 }
3186 false
3187}
3188
3189pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
3193 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
3194 return;
3195 }
3196 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
3197 if tok == 0 {
3198 return;
3199 }
3200 let i = used_graph as usize;
3201 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
3202 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
3203}
3204
3205pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
3215 #[inline]
3216 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
3217 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
3218 for c in chunks.chunks_exact(8) {
3219 h ^= u64::from_le_bytes(c.try_into().unwrap());
3220 h = h.wrapping_mul(0x100_0000_01b3);
3221 }
3222 for &b in tail {
3223 h ^= b as u64;
3224 h = h.wrapping_mul(0x100_0000_01b3);
3225 }
3226 h
3227 }
3228 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
3229 if data.len() <= 4096 {
3230 return fnv(h, data);
3231 }
3232 let step = (data.len() - 64) / 63;
3233 for i in 0..64 {
3234 h = fnv(h, &data[i * step..i * step + 64]);
3235 }
3236 h
3237}
3238
3239pub(crate) fn fp_f32(data: &[f32]) -> u64 {
3242 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
3243 fp_bytes(bytes)
3244}
3245
3246#[cfg(test)]
3247mod fp_tests {
3248 use super::fp_bytes;
3249
3250 #[test]
3255 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
3256 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
3258 let h0 = fp_bytes(&base);
3259 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
3260 let mut dense = base.clone();
3263 for b in dense.iter_mut() {
3264 *b = b.wrapping_add(1);
3265 }
3266 assert_ne!(
3267 h0,
3268 fp_bytes(&dense),
3269 "a fully different tensor slipped through"
3270 );
3271 assert_ne!(h0, fp_bytes(&base[..n - 64]));
3274 let mut small = vec![3u8; 4096];
3277 let hs = fp_bytes(&small);
3278 small[2048] ^= 1;
3279 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
3280 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
3282 let v = vec![9u8; n];
3283 let _ = fp_bytes(&v); }
3285 }
3286}
3287
3288pub fn bake_release() {
3292 #[cfg(feature = "gpu")]
3293 crate::gpu_wgpu::bake_release();
3294}
3295
3296pub fn bake_precision_strict(on: bool) {
3300 #[cfg(feature = "gpu")]
3301 crate::gpu_wgpu::bake_precision_strict(on);
3302 #[cfg(not(feature = "gpu"))]
3303 let _ = on;
3304}
3305
3306pub fn hostprof_encode_done(t0: std::time::Instant) {
3312 use std::sync::atomic::{AtomicU64, Ordering};
3313 static ENC: AtomicU64 = AtomicU64::new(0);
3314 static N: AtomicU64 = AtomicU64::new(0);
3315 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3316 return;
3317 }
3318 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3319 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3320 if n % 100 == 0 {
3321 eprintln!(
3322 "hostprof: encode {:.2} ms/token over {n} tokens",
3323 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3324 );
3325 }
3326}
3327
3328pub fn hostprof_total(t0: std::time::Instant) {
3329 use std::sync::atomic::{AtomicU64, Ordering};
3330 static TOT: AtomicU64 = AtomicU64::new(0);
3331 static N: AtomicU64 = AtomicU64::new(0);
3332 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3333 return;
3334 }
3335 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
3336 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3337 if n % 100 == 0 {
3338 eprintln!(
3339 "hostprof: total {:.2} ms/token over {n} tokens",
3340 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3341 );
3342 }
3343}
3344
3345pub fn stageprof(stage: u32, dt: std::time::Duration) {
3349 use std::sync::atomic::{AtomicU64, Ordering};
3350 static NS: [AtomicU64; 4] = [
3351 AtomicU64::new(0),
3352 AtomicU64::new(0),
3353 AtomicU64::new(0),
3354 AtomicU64::new(0),
3355 ];
3356 static N: AtomicU64 = AtomicU64::new(0);
3357 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
3358 return;
3359 }
3360 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
3361 if stage == 1 {
3362 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
3363 if n % 200 == 0 {
3364 eprintln!(
3365 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
3366 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3367 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
3368 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
3369 );
3370 }
3371 }
3372}
3373
3374pub fn weight_bytes_dispatched() -> u64 {
3377 let mut total = 0u64;
3378 #[cfg(target_os = "macos")]
3379 {
3380 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3381 }
3382 #[cfg(feature = "gpu")]
3383 {
3384 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
3385 }
3386 total
3387}
3388
3389pub fn weight_bytes_by() -> [u64; 6] {
3392 #[cfg(target_os = "macos")]
3393 {
3394 let mut o = [0u64; 6];
3395 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
3396 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
3397 }
3398 return o;
3399 }
3400 #[allow(unreachable_code)]
3401 [0; 6]
3402}
3403
3404#[cfg(test)]
3405mod probe_warmup_tests {
3406 use super::*;
3407 use std::time::Duration;
3408
3409 fn ms(v: f64) -> Duration {
3410 Duration::from_nanos((v * 1e6) as u64)
3411 }
3412
3413 #[test]
3418 fn one_cold_first_sample_does_not_lose_the_class() {
3419 let p = Probe::new();
3420 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
3422 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
3423 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
3424 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
3425 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
3426 assert_eq!(
3427 p.state.load(Ordering::Relaxed),
3428 1,
3429 "the device is 3x faster once warm and must win"
3430 );
3431 }
3432
3433 #[test]
3437 fn the_warmup_is_spent_once_and_never_underflows() {
3438 let p = Probe::new();
3439 for _ in 0..8 {
3440 probe_record_into(&p, "matmat", None, true, ms(10.0));
3441 }
3442 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
3443 assert_eq!(
3444 p.gpu_n.load(Ordering::Relaxed),
3445 7,
3446 "one sample burned, the rest counted"
3447 );
3448 }
3449
3450 #[test]
3456 fn a_class_whose_device_always_declines_settles_on_the_host() {
3457 let _probe_guard = probe_test_guard();
3458 let c = OpClass::MatmatWide;
3462 let p = &PROBES[c as usize];
3463 p.state.store(0, Ordering::Relaxed);
3464 p.declines.store(0, Ordering::Relaxed);
3465 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
3466 probe_note_decline(c);
3467 }
3468 assert_eq!(
3469 p.state.load(Ordering::Relaxed),
3470 0,
3471 "one short of the limit is still a question, not an answer"
3472 );
3473 probe_note_decline(c);
3474 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
3475 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
3476 p.state.store(0, Ordering::Relaxed);
3477 p.declines.store(0, Ordering::Relaxed);
3478 }
3479
3480 #[test]
3483 fn a_slow_device_still_loses_after_the_warmup() {
3484 let p = Probe::new();
3485 for _ in 0..4 {
3486 probe_record_into(&p, "matvec", None, true, ms(40.0));
3487 }
3488 for _ in 0..4 {
3489 probe_record_into(&p, "matvec", None, false, ms(2.0));
3490 }
3491 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
3492 }
3493}
3494
3495pub(crate) struct ImageStageGuard {
3498 #[cfg(target_os = "macos")]
3499 metal: Option<crate::gpu_metal::ImageStageGuard>,
3500 #[cfg(feature = "gpu")]
3501 wgpu: crate::gpu_wgpu::ImageStageGuard,
3502}
3503
3504pub(crate) fn image_stage_scope() -> ImageStageGuard {
3505 ImageStageGuard {
3506 #[cfg(target_os = "macos")]
3507 metal: if matches!(backend(), Backend::Metal) {
3508 Some(crate::gpu_metal::image_stage_scope())
3509 } else {
3510 None
3511 },
3512 #[cfg(feature = "gpu")]
3513 wgpu: crate::gpu_wgpu::image_stage_scope(),
3514 }
3515}
3516
3517impl ImageStageGuard {
3518 pub(crate) fn track_model(&mut self, uid: u64) {
3519 #[cfg(target_os = "macos")]
3520 if let Some(metal) = &mut self.metal {
3521 metal.track_model(uid);
3522 }
3523 #[cfg(feature = "gpu")]
3524 self.wgpu.track_model(uid);
3525 #[cfg(not(target_os = "macos"))]
3526 let _ = uid;
3527 }
3528}
3529
3530#[derive(Clone, Copy)]
3554pub struct ZBlockRef<'a> {
3555 pub wq: usize,
3557 pub wk: usize,
3558 pub wv: usize,
3559 pub wo: usize,
3560 pub w1: usize,
3563 pub w3: usize,
3564 pub w2: usize,
3565 pub norm1: &'a [f32],
3567 pub norm2: &'a [f32],
3568 pub ffn_norm1: &'a [f32],
3570 pub ffn_norm2: &'a [f32],
3571 pub norm_q: &'a [f32],
3573 pub norm_k: &'a [f32],
3574}
3575
3576#[derive(Clone, Copy, Debug, PartialEq)]
3580pub struct ZGeom {
3581 pub hidden: usize,
3582 pub nh: usize,
3583 pub hd: usize,
3584 pub inter: usize,
3585 pub eps: f32,
3586 pub final_eps: f32,
3587 pub patch_dim: usize,
3588}
3589
3590pub struct ZPrepareArgs<'a> {
3594 pub model: &'a Arc<CmfModel>,
3595 pub geom: ZGeom,
3596 pub key: u64,
3599 pub n_img: usize,
3602 pub n_img_p: usize,
3603 pub n_cap_p: usize,
3604 pub grid: (usize, usize),
3607 pub cap: &'a [f32],
3609 pub rope_img: (&'a [f32], &'a [f32]),
3612 pub rope_joint: (&'a [f32], &'a [f32]),
3614 pub x_emb_w: &'a [f32],
3617 pub x_emb_b: &'a [f32],
3618 pub x_pad: &'a [f32],
3619 pub final_w: &'a [f32],
3621 pub final_b: &'a [f32],
3622 pub noise_refiner: &'a [ZBlockRef<'a>],
3624 pub layers: &'a [ZBlockRef<'a>],
3625 pub mods_all: Option<&'a [f32]>,
3631 pub final_scale_all: Option<&'a [f32]>,
3632 pub neg: Option<ZNegArgs<'a>>,
3638}
3639
3640pub struct ZNegArgs<'a> {
3644 pub cap: &'a [f32],
3646 pub n_cap_p: usize,
3647 pub rope_img: (&'a [f32], &'a [f32]),
3649 pub rope_joint: (&'a [f32], &'a [f32]),
3651}
3652
3653pub struct ZStepArgs<'a> {
3655 pub key: u64,
3658 pub step: usize,
3661 pub x_tok: &'a [f32],
3665 pub mods: &'a [f32],
3669 pub final_scale: &'a [f32],
3671 pub out: &'a mut [f32],
3674 pub out_neg: Option<&'a mut [f32]>,
3677}
3678
3679#[allow(unused_variables)]
3683pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
3684 match backend() {
3685 #[cfg(target_os = "macos")]
3686 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
3687 #[cfg(feature = "gpu")]
3688 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
3689 #[allow(unreachable_patterns)]
3690 _ => false,
3691 }
3692}
3693
3694#[allow(unused_variables)]
3698pub fn zimage_step(a: &mut ZStepArgs) -> bool {
3699 match backend() {
3700 #[cfg(target_os = "macos")]
3701 Backend::Metal => crate::gpu_metal::zimage::step(a),
3702 #[cfg(feature = "gpu")]
3703 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
3704 #[allow(unreachable_patterns)]
3705 _ => false,
3706 }
3707}
3708
3709#[allow(unused_variables)]
3714pub fn zimage_preload(
3715 model: &Arc<CmfModel>,
3716 geom: &ZGeom,
3717 noise_refiner: &[ZBlockRef],
3718 layers: &[ZBlockRef],
3719 context_refiner: &[ZBlockRef],
3720) -> bool {
3721 match backend() {
3722 #[cfg(target_os = "macos")]
3723 Backend::Metal => {
3724 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
3725 }
3726 #[cfg(feature = "gpu")]
3727 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
3728 #[allow(unreachable_patterns)]
3729 _ => false,
3730 }
3731}
3732
3733pub fn zimage_flush_pipelines() {
3737 #[cfg(feature = "gpu")]
3738 if matches!(backend(), Backend::Wgpu) {
3739 crate::gpu_wgpu::pipeline_cache_flush();
3740 }
3741}
3742
3743pub fn zimage_warmup() -> bool {
3748 match backend() {
3749 #[cfg(target_os = "macos")]
3750 Backend::Metal => crate::gpu_metal::zimage::warmup(),
3751 #[cfg(feature = "gpu")]
3752 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
3753 #[allow(unreachable_patterns)]
3754 _ => false,
3755 }
3756}
3757
3758#[allow(unused_variables)]
3762pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
3763 match backend() {
3764 #[cfg(target_os = "macos")]
3765 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
3766 #[cfg(feature = "gpu")]
3767 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
3768 #[allow(unreachable_patterns)]
3769 _ => false,
3770 }
3771}
3772
3773pub fn zimage_release_dit() {
3776 #[cfg(target_os = "macos")]
3777 crate::gpu_metal::zimage::release_dit();
3778 #[cfg(feature = "gpu")]
3779 crate::gpu_wgpu::zimage::release_dit();
3780}
3781
3782pub fn zimage_release() {
3787 #[cfg(target_os = "macos")]
3788 crate::gpu_metal::zimage::release();
3789 #[cfg(feature = "gpu")]
3790 crate::gpu_wgpu::zimage::release();
3791}
3792
3793#[allow(unused_variables)]
3799pub fn zimage_refine_caption(
3800 model: &Arc<CmfModel>,
3801 geom: &ZGeom,
3802 blocks: &[ZBlockRef],
3803 rope_cap: (&[f32], &[f32]),
3804 cap: &mut [f32],
3805) -> bool {
3806 match backend() {
3807 #[cfg(target_os = "macos")]
3808 Backend::Metal => {
3809 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
3810 }
3811 #[cfg(feature = "gpu")]
3812 Backend::Wgpu => {
3813 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
3814 }
3815 #[allow(unreachable_patterns)]
3816 _ => false,
3817 }
3818}
3819
3820#[allow(unused_variables)]
3826pub fn vae_decode_chain(
3827 a: &crate::vae::VaeChainArgs,
3828 z: &[f32],
3829 h: usize,
3830 w: usize,
3831 out: &mut [f32],
3832) -> bool {
3833 match backend() {
3834 #[cfg(target_os = "macos")]
3835 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
3836 #[cfg(feature = "gpu")]
3837 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
3838 #[allow(unreachable_patterns)]
3839 _ => false,
3840 }
3841}