1use cortiq_core::CmfModel;
14use std::cell::Cell;
15use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock};
17
18pub struct EmbryoGraphModel {
27 pub id: u64,
28 pub hidden: usize,
29 pub intermediate: usize,
30 pub vocab: usize,
31 pub layers: usize,
32 pub phase_heads: usize,
33 pub nphase: usize,
34 pub phase_dv: usize,
35 pub anchor_q_heads: usize,
36 pub anchor_kv_heads: usize,
37 pub anchor_head_dim: usize,
38 pub rotary_dim: usize,
39 pub max_seq: usize,
40 pub cluster_count: usize,
41 pub cluster_size: usize,
42 pub phase_state_len: usize,
43 pub state_stride: usize,
44 pub kv_stride: usize,
45 pub norm_gemma: bool,
46 pub phase_mass: f32,
47 pub weights: Vec<f32>,
48 pub meta: Vec<u32>,
49 pub lm_head: Vec<f32>,
50 pub clusters: Vec<f32>,
51 pub final_norm: Vec<f32>,
52 pub inv_freq: Vec<f32>,
53 pub bounded: bool,
58 pub kv_layers: usize,
62 pub state_layers: usize,
65 pub anchor_window: usize,
66 pub anchor_sink: usize,
67 pub phase_layers: usize,
69 pub gdn_layers: usize,
73 pub gdn_heads: usize,
74 pub gdn_k_heads: usize,
75 pub gdn_dk: usize,
76 pub gdn_dv: usize,
77 pub gdn_kk: usize,
78}
79
80impl EmbryoGraphModel {
81 pub fn gdn_c_dim(&self) -> usize {
83 2 * self.gdn_k_heads * self.gdn_dk + self.gdn_heads * self.gdn_dv
84 }
85}
86
87pub const EMBRYO_META_HEADER: usize = 32;
90
91pub const EMBRYO_CHUNK_MAX: usize = 64;
95
96pub fn forward_embryo_graph_chunk(
101 model: &Arc<EmbryoGraphModel>,
102 kv_id: u64,
103 rows: &[f32],
104 position: usize,
105 n: usize,
106 logits: &mut Vec<f32>,
107) -> bool {
108 #[cfg(feature = "gpu")]
109 if backend() == Backend::Wgpu {
110 return crate::gpu_wgpu::forward_embryo_graph_chunk(model, kv_id, rows, position, n, logits);
111 }
112 let _ = (model, kv_id, rows, position, n, logits);
113 false
114}
115
116pub fn embryo_device_state_bytes(kv_id: u64) -> Option<(u64, u64)> {
122 #[cfg(feature = "gpu")]
123 if backend() == Backend::Wgpu {
124 return crate::gpu_wgpu::embryo_device_state_bytes(kv_id);
125 }
126 let _ = kv_id;
127 None
128}
129
130pub fn counts_as_placement_weight(model: &CmfModel, name: &str) -> bool {
137 model.header.genome.is_none() || cortiq_core::knowledge::is_trunk_tensor(name)
138}
139
140pub fn placement_weight_bytes(model: &CmfModel) -> u64 {
144 if model.header.genome.is_none() {
145 return model.primary_bytes().len() as u64;
146 }
147 model
148 .tensors
149 .iter()
150 .filter(|t| counts_as_placement_weight(model, &t.name))
151 .map(|t| t.nbytes)
152 .sum()
153}
154
155pub fn embryo_device_next_position(kv_id: u64) -> Option<usize> {
159 #[cfg(feature = "gpu")]
160 if backend() == Backend::Wgpu {
161 return crate::gpu_wgpu::embryo_device_next_position(kv_id);
162 }
163 let _ = kv_id;
164 None
165}
166
167thread_local! {
168 static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
172 static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
177 static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
181}
182
183pub struct CpuScopeGuard(bool);
189
190impl Drop for CpuScopeGuard {
191 fn drop(&mut self) {
192 CPU_ONLY.with(|c| c.set(self.0));
193 }
194}
195
196pub fn enter_cpu_scope() -> CpuScopeGuard {
197 let previous = CPU_ONLY.with(|c| c.replace(true));
198 CpuScopeGuard(previous)
199}
200
201pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
203 let _restore = enter_cpu_scope();
204 f()
205}
206
207pub(crate) fn inherit_cpu_scope() -> impl Fn() -> Option<CpuScopeGuard> + Copy {
212 let on = CPU_ONLY.get();
213 move || on.then(enter_cpu_scope)
214}
215
216pub fn probe_set_device(label: &str) {
221 let _ = DEVICE_LABEL.set(label.to_string());
222}
223
224fn device_label() -> &'static str {
225 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
226}
227
228static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
229
230static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
239
240pub fn set_cache_dir(dir: std::path::PathBuf) {
242 let _ = CACHE_DIR.set(dir);
243}
244
245pub fn cache_dir_pub() -> std::path::PathBuf {
247 cache_dir()
248}
249
250fn cache_dir() -> std::path::PathBuf {
251 if let Some(d) = CACHE_DIR.get() {
252 return d.clone();
253 }
254 match std::env::var_os("TMPDIR") {
255 Some(t) => std::path::PathBuf::from(t),
256 None => std::env::temp_dir(),
257 }
258}
259
260fn probe_cache_path() -> Option<std::path::PathBuf> {
263 match std::env::var("CMF_PROBE_CACHE") {
264 Ok(v) if v == "0" => None,
265 Ok(v) => Some(std::path::PathBuf::from(v)),
266 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
267 }
268}
269
270fn probe_cache_key_named(class: &str) -> String {
274 format!(
275 "{}\t{}\t{}",
276 env!("CARGO_PKG_VERSION"),
277 device_label(),
278 class
279 )
280}
281
282const CLASS_NAMES: [&str; 8] = [
283 "ffn",
284 "matvec",
285 "matmat",
286 "qkv-batch",
287 "matmat-wide",
288 "lm-head",
289 "gemm-nt",
290 "moe",
295];
296
297fn probe_cache_load() {
306 static ONCE: std::sync::Once = std::sync::Once::new();
307 ONCE.call_once(|| {
308 let Some(path) = probe_cache_path() else {
309 return;
310 };
311 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
316 return;
317 }
318 let Ok(text) = std::fs::read_to_string(&path) else {
319 return;
320 };
321 probe_cache_adopt(&text);
322 });
323}
324
325fn probe_cache_adopt(text: &str) {
329 for line in text.lines() {
330 let Some((key, verdict)) = line.rsplit_once('\t') else {
331 continue;
332 };
333 let winner = match verdict.trim() {
334 "gpu" => 1u8,
335 "cpu" => 2u8,
336 _ => continue,
337 };
338 for (i, name) in CLASS_NAMES.iter().enumerate() {
339 if probe_cache_key_named(name) == key {
340 let _ = PROBES[i].state.compare_exchange(
341 0,
342 winner,
343 Ordering::Relaxed,
344 Ordering::Relaxed,
345 );
346 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
347 }
348 }
349 }
350}
351
352fn probe_cache_store(c: OpClass, winner: u8) {
355 let Some(path) = probe_cache_path() else {
356 return;
357 };
358 let line = format!(
359 "{}\t{}\n",
360 probe_cache_key_named(CLASS_NAMES[c as usize]),
361 if winner == 1 { "gpu" } else { "cpu" }
362 );
363 use std::io::Write;
364 if let Ok(mut f) = std::fs::OpenOptions::new()
365 .create(true)
366 .append(true)
367 .open(&path)
368 {
369 let _ = f.write_all(line.as_bytes());
370 }
371}
372
373pub fn cold_epoch() -> u64 {
379 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
380}
381static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
382
383pub(crate) fn probe_note_cold() {
384 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
385 PROBE_COLD.with(|c| c.set(true));
386}
387
388pub(crate) fn probe_was_cold() -> bool {
392 PROBE_COLD.with(|c| c.get())
393}
394
395pub fn set_layer(l: i64) {
397 CUR_LAYER.with(|c| c.set(l));
398}
399
400pub fn cur_layer() -> i64 {
402 CUR_LAYER.with(|c| c.get())
403}
404
405pub fn automatic_layer_prefix(
408 model: &Arc<CmfModel>,
409 num_layers: usize,
410 physical_layers: usize,
411) -> Option<usize> {
412 match backend() {
413 #[cfg(feature = "gpu")]
414 Backend::Wgpu => {
415 crate::gpu_wgpu::automatic_layer_prefix(model, num_layers, physical_layers)
416 }
417 _ => None,
418 }
419}
420
421fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
424 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
425 R.get_or_init(|| {
426 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
427 let mut v = Vec::new();
428 for part in s.split(',') {
429 let part = part.trim();
430 match part.split_once('-') {
431 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
432 None => {
433 let x: i64 = part.parse().ok()?;
434 v.push((x, x));
435 }
436 }
437 }
438 Some(v)
439 })
440}
441
442fn layer_allowed() -> bool {
443 match layer_ranges() {
444 None => true,
445 Some(ranges) => {
446 let cur = CUR_LAYER.with(|c| c.get());
447 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
448 }
449 }
450}
451
452pub fn enabled_here() -> bool {
456 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
457}
458
459pub fn q2tp_gpu_opt_in() -> bool {
465 std::env::var("CMF_Q2TP_GPU").as_deref() == Ok("1")
466}
467
468#[derive(Clone, Copy)]
480pub enum OpClass {
481 Ffn = 0,
483 Matvec = 1,
485 Matmat = 2,
487 Batch = 3,
489 MatmatWide = 4,
495 MatvecHead = 5,
502 GemmNt = 6,
509 Moe = 7,
512}
513
514pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
518 if rows * cols >= 67_108_864 {
519 OpClass::MatvecHead
520 } else {
521 OpClass::Matvec
522 }
523}
524
525pub enum ProbeArm {
527 Gpu,
529 CpuTimed,
531 Cpu,
533}
534
535const PROBE_SAMPLES: u32 = 6;
537
538const PROBE_DECLINE_LIMIT: u32 = 16;
542
543const PROBE_WARMUP: u32 = 1;
545
546struct Probe {
547 state: AtomicU8,
549 flip: AtomicU32,
550 gpu_ns: AtomicU64,
551 gpu_n: AtomicU32,
552 declines: AtomicU32,
562 gpu_burn: AtomicU32,
575 cpu_ns: AtomicU64,
576 cpu_n: AtomicU32,
577 gpu_min: AtomicU64,
584 cpu_min: AtomicU64,
585}
586
587impl Probe {
588 const fn new() -> Self {
589 Self {
590 state: AtomicU8::new(0),
591 flip: AtomicU32::new(0),
592 gpu_ns: AtomicU64::new(0),
593 gpu_n: AtomicU32::new(0),
594 declines: AtomicU32::new(0),
595 gpu_burn: AtomicU32::new(PROBE_WARMUP),
596 cpu_ns: AtomicU64::new(0),
597 cpu_n: AtomicU32::new(0),
598 gpu_min: AtomicU64::new(u64::MAX),
599 cpu_min: AtomicU64::new(u64::MAX),
600 }
601 }
602}
603
604static PROBES: [Probe; 8] = [
605 Probe::new(),
606 Probe::new(),
607 Probe::new(),
608 Probe::new(),
609 Probe::new(),
610 Probe::new(),
611 Probe::new(),
612 Probe::new(),
613];
614
615static TRUST_GPU: AtomicBool = AtomicBool::new(false);
622
623pub fn trust_gpu() -> GpuTrust {
625 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
626 GpuTrust(was)
627}
628
629pub struct GpuTrust(bool);
630
631impl Drop for GpuTrust {
632 fn drop(&mut self) {
633 TRUST_GPU.store(self.0, Ordering::Relaxed);
634 }
635}
636
637fn probe_on_for(c: OpClass) -> bool {
638 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
644 return false;
645 }
646 probe_on()
647}
648
649pub fn probe_enabled() -> bool {
653 probe_on()
654}
655
656fn probe_on() -> bool {
657 static ON: OnceLock<bool> = OnceLock::new();
658 *ON.get_or_init(|| {
659 std::env::var("CMF_GPU_PROBE")
660 .map(|v| v != "0" && v != "off")
661 .unwrap_or(true)
662 })
663}
664
665pub fn q1_force() -> bool {
670 #[cfg(target_os = "macos")]
671 {
672 backend() == Backend::Metal
673 }
674 #[cfg(not(target_os = "macos"))]
675 {
676 false
677 }
678}
679
680pub fn fused_block_trusted() -> bool {
699 #[cfg(target_os = "macos")]
700 if backend() == Backend::Metal {
701 return true;
702 }
703 wgpu_graph_default()
704}
705
706pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
718 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
719 {
720 return crate::gpu_wgpu::weight_is_resident(model, idx);
721 }
722 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
723 {
724 let _ = (model, idx);
725 true
726 }
727}
728
729pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
730 if !weights_resident && probe_deciding(c) {
731 return ProbeArm::Gpu;
732 }
733 probe_arm(c)
734}
735
736pub fn probe_arm(c: OpClass) -> ProbeArm {
737 PROBE_COLD.with(|f| f.set(false));
742 if !probe_on_for(c) {
743 return ProbeArm::Gpu;
744 }
745 probe_cache_load();
746 let p = &PROBES[c as usize];
747 match p.state.load(Ordering::Relaxed) {
748 1 => ProbeArm::Gpu,
749 2 => ProbeArm::Cpu,
750 _ => {
751 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
752 ProbeArm::Gpu
753 } else {
754 ProbeArm::CpuTimed
755 }
756 }
757 }
758}
759
760pub fn probe_note_decline(c: OpClass) {
764 let p = &PROBES[c as usize];
765 if p.state.load(Ordering::Relaxed) != 0 {
766 return;
767 }
768 let n = p.declines.fetch_add(1, Ordering::Relaxed) + 1;
769 if n >= PROBE_DECLINE_LIMIT
770 && p.state
771 .compare_exchange(0, 2, Ordering::Relaxed, Ordering::Relaxed)
772 .is_ok()
773 {
774 tracing::info!(
775 "gpu probe [{}]: device declined {n} times → cpu",
776 CLASS_NAMES[c as usize]
777 );
778 }
779}
780
781pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
784 probe_record_into(
785 &PROBES[c as usize],
786 CLASS_NAMES[c as usize],
787 Some(c),
788 gpu,
789 dur,
790 )
791}
792
793fn probe_record_into(
796 p: &Probe,
797 class_name: &str,
798 cache: Option<OpClass>,
799 gpu: bool,
800 dur: std::time::Duration,
801) {
802 if p.state.load(Ordering::Relaxed) != 0 {
803 return;
804 }
805 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
806 return; }
808 if gpu {
809 let left = p.gpu_burn.load(Ordering::Relaxed);
813 if left > 0 {
814 p.gpu_burn.store(left - 1, Ordering::Relaxed);
815 return; }
817 }
818 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
819 if gpu {
820 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
821 p.gpu_n.fetch_add(1, Ordering::Relaxed);
822 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
823 } else {
824 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
825 p.cpu_n.fetch_add(1, Ordering::Relaxed);
826 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
827 }
828 let (gn, cn) = (
829 p.gpu_n.load(Ordering::Relaxed),
830 p.cpu_n.load(Ordering::Relaxed),
831 );
832 if gn >= 2 && cn >= 2 {
833 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
837 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
838 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
848 return;
849 }
850 let winner = if g <= cp { 1 } else { 2 };
851 if p.state
852 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
853 .is_ok()
854 {
855 tracing::info!(
856 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
857 class_name,
858 g / 1e6,
859 cp / 1e6,
860 if winner == 1 { "gpu" } else { "cpu" },
861 );
862 if let Some(c) = cache {
863 probe_cache_store(c, winner);
864 }
865 }
866 }
867}
868
869pub fn probe_deciding(c: OpClass) -> bool {
872 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
873}
874
875#[allow(unused_variables)]
885pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
886 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
887 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
888 let resident = match backend() {
889 #[cfg(target_os = "macos")]
890 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
891 #[cfg(feature = "gpu")]
892 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
893 Backend::None => false,
894 };
895 if !resident && may_upload {
896 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
897 }
898 resident
899}
900
901#[cfg(test)]
903pub(crate) fn probe_reset() {
904 for p in &PROBES {
905 p.state.store(0, Ordering::Relaxed);
906 p.flip.store(0, Ordering::Relaxed);
907 p.gpu_ns.store(0, Ordering::Relaxed);
908 p.gpu_n.store(0, Ordering::Relaxed);
909 p.cpu_ns.store(0, Ordering::Relaxed);
910 p.cpu_n.store(0, Ordering::Relaxed);
911 }
912}
913
914#[cfg(test)]
918static PROBE_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
919
920#[cfg(test)]
921fn probe_test_guard() -> std::sync::MutexGuard<'static, ()> {
922 PROBE_TEST_LOCK
923 .lock()
924 .unwrap_or_else(std::sync::PoisonError::into_inner)
925}
926
927#[cfg(test)]
928mod probe_tests {
929 use super::*;
930 use std::time::Duration;
931
932 #[test]
933 fn cpu_only_whole_operator_dispatch_inherits_and_restores_scope() {
934 let pool = crate::pool::Pool::with_spin(3, 0);
935 cpu_scope(|| {
936 let inherit = inherit_cpu_scope();
937 pool.run_rows(64, &|_, _| {
938 let _guard = inherit();
939 assert!(CPU_ONLY.get());
940 });
941 });
942 pool.run_rows(64, &|_, _| assert!(!CPU_ONLY.get()));
943 }
944
945 #[test]
948 fn probe_alternates_discards_cold_and_decides() {
949 let _probe_guard = probe_test_guard();
950 probe_reset();
951 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
953 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
954
955 probe_note_cold();
959 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
960 for _ in 0..PROBE_SAMPLES {
961 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
962 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
963 }
964 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
965
966 for _ in 0..PROBE_SAMPLES {
968 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
969 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
970 }
971 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
972
973 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
975 CPU_ONLY.with(|c| assert!(!c.get()));
976 cpu_scope(|| {
977 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
978 CPU_ONLY.with(|c| assert!(c.get()));
979 });
980 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
981 CPU_ONLY.with(|c| assert!(!c.get()));
982 probe_reset();
983 }
984
985 #[test]
986 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
987 let _probe_guard = probe_test_guard();
988 let mine = probe_cache_key_named("gemm-nt");
1000 let state = || {
1001 PROBES[OpClass::GemmNt as usize]
1002 .state
1003 .load(Ordering::Relaxed)
1004 };
1005
1006 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
1008 assert_eq!(state(), 0);
1009 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
1011 assert_ne!(older, mine);
1012 probe_cache_adopt(&format!("{older}\tgpu\n"));
1013 assert_eq!(state(), 0);
1014 probe_cache_adopt(&format!("{mine}\tcpu\n"));
1016 assert_eq!(state(), 2);
1017
1018 PROBES[OpClass::GemmNt as usize]
1019 .state
1020 .store(0, Ordering::Relaxed);
1021 }
1022}
1023
1024pub const GPU_MIN_ROWS: usize = 65_536;
1027
1028pub fn min_rows() -> usize {
1035 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
1036 .ok()
1037 .and_then(|v| v.parse().ok())
1038 {
1039 return v;
1040 }
1041 if discrete() { 4096 } else { GPU_MIN_ROWS }
1042}
1043
1044pub fn discrete() -> bool {
1046 match backend() {
1047 #[cfg(feature = "gpu")]
1048 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
1049 #[cfg(target_os = "macos")]
1050 Backend::Metal => false, Backend::None => false,
1052 }
1053}
1054
1055pub struct MoeJob<'a> {
1059 pub gate: (usize, usize, usize, &'a [f32]),
1060 pub up: (usize, usize, usize, &'a [f32]),
1061 pub down: (usize, usize, usize, &'a [f32]),
1062 pub xs_gate: Vec<f32>,
1063 pub xs_up: Vec<f32>,
1064 pub down_col: &'a [f32],
1065 pub w: f32,
1066 pub q1: bool,
1069 pub q4t: bool,
1072 pub q4tp: bool,
1076 pub gu_q2: bool,
1080 pub swiglu_limit: f32,
1085 pub act: u32,
1089}
1090
1091pub struct BatchJob<'a> {
1093 pub idx: usize,
1094 pub rows: usize,
1095 pub cols: usize,
1096 pub row_scale: &'a [f32],
1097 pub xs: Vec<f32>,
1098 pub layout: BatchLayout,
1102}
1103
1104#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1107pub enum BatchLayout {
1108 Q8,
1109 Q1,
1110 Q1g,
1112 Q4t,
1113 Q4tp,
1114}
1115
1116#[derive(Clone, Copy, PartialEq, Eq)]
1117enum Backend {
1118 None,
1119 #[cfg(target_os = "macos")]
1120 Metal,
1121 #[cfg(feature = "gpu")]
1122 Wgpu,
1123}
1124
1125fn backend() -> Backend {
1126 #[cfg(feature = "gpu")]
1127 if crate::gpu_wgpu::selected() {
1128 return if crate::gpu_wgpu::enabled() {
1129 Backend::Wgpu
1130 } else {
1131 Backend::None
1132 };
1133 }
1134 #[cfg(target_os = "macos")]
1135 if crate::gpu_metal::enabled() {
1136 return Backend::Metal;
1137 }
1138 Backend::None
1139}
1140
1141pub fn backend_available() -> bool {
1147 #[cfg(target_os = "macos")]
1148 {
1149 true
1151 }
1152 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1153 {
1154 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1155 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
1156 }
1157 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
1158 {
1159 false
1160 }
1161}
1162
1163static GPU_PAUSED: PauseGate = PauseGate::new();
1169
1170struct PauseGate(std::sync::atomic::AtomicUsize);
1175
1176impl PauseGate {
1177 const fn new() -> Self {
1178 Self(std::sync::atomic::AtomicUsize::new(0))
1179 }
1180
1181 fn enter(&self) {
1182 self.0.fetch_add(1, Ordering::Relaxed);
1183 }
1184
1185 fn leave(&self) {
1186 self.0.fetch_sub(1, Ordering::Relaxed);
1187 }
1188
1189 fn paused(&self) -> bool {
1190 self.0.load(Ordering::Relaxed) != 0
1191 }
1192}
1193
1194pub fn pause_gpu() -> GpuPause {
1197 GPU_PAUSED.enter();
1198 GpuPause(())
1199}
1200
1201pub struct GpuPause(());
1202
1203impl Drop for GpuPause {
1204 fn drop(&mut self) {
1205 GPU_PAUSED.leave();
1206 }
1207}
1208
1209pub fn enabled() -> bool {
1210 !GPU_PAUSED.paused() && backend() != Backend::None
1211}
1212
1213#[cfg(test)]
1214mod pause_gate_tests {
1215 use super::PauseGate;
1216
1217 #[test]
1220 fn pause_holds_until_the_last_guard() {
1221 let g = PauseGate::new();
1222 assert!(!g.paused());
1223 g.enter();
1224 g.enter();
1225 g.leave();
1226 assert!(g.paused(), "one holder left, the other's pause was lifted");
1227 g.leave();
1228 assert!(!g.paused());
1229 }
1230}
1231
1232pub fn wgpu_active() -> bool {
1246 #[cfg(feature = "gpu")]
1247 {
1248 matches!(backend(), Backend::Wgpu)
1249 }
1250 #[cfg(not(feature = "gpu"))]
1251 {
1252 false
1253 }
1254}
1255
1256pub fn default_device() -> usize {
1263 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1264 *D.get_or_init(|| {
1265 std::env::var("CMF_GPU_ADAPTER")
1266 .ok()
1267 .and_then(|v| v.trim().parse::<usize>().ok())
1268 .unwrap_or(0)
1269 })
1270}
1271
1272thread_local! {
1273 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1274}
1275
1276pub fn current_device() -> usize {
1278 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1279}
1280
1281pub fn set_current_device(i: usize) {
1285 CUR_DEV.with(|c| c.set(Some(i)));
1286}
1287
1288pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1290 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1291 let r = f();
1292 CUR_DEV.with(|c| c.set(prev));
1293 r
1294}
1295
1296pub fn device_count() -> usize {
1299 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1300 {
1301 return crate::gpu_wgpu::adapter_count();
1302 }
1303 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1304 {
1305 usize::from(backend_available())
1306 }
1307}
1308
1309pub fn vram_budget() -> u64 {
1313 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1314 {
1315 return crate::gpu_wgpu::device_vram_budget();
1316 }
1317 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1318 {
1319 if backend_available() { u64::MAX } else { 0 }
1320 }
1321}
1322
1323pub fn resident_bytes() -> u64 {
1327 #[cfg(feature = "gpu")]
1328 {
1329 if backend() == Backend::Wgpu {
1330 return crate::gpu_wgpu::resident_bytes();
1331 }
1332 }
1333 0
1334}
1335
1336pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1340 #[cfg(feature = "gpu")]
1341 {
1342 if backend() == Backend::Wgpu {
1343 return crate::gpu_wgpu::o1_device_stats(kv_id);
1344 }
1345 }
1346 let _ = kv_id;
1347 (0, 0)
1348}
1349
1350pub fn upload_bytes() -> u64 {
1354 #[cfg(feature = "gpu")]
1355 {
1356 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1357 }
1358 #[cfg(not(feature = "gpu"))]
1359 0
1360}
1361
1362pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1366 #[cfg(feature = "gpu")]
1367 {
1368 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1369 }
1370 let _ = (block, rounds);
1371 None
1372}
1373
1374#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1386pub enum GraphPhase {
1387 Prefill,
1388 Decode,
1389}
1390
1391pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1399 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1400 Some("0") => false,
1401 Some("prefill") => phase == GraphPhase::Prefill,
1402 Some(_) => true,
1403 None => {
1404 if wgpu_graph_default() {
1405 return true;
1406 }
1407 let _ = phase;
1412 false
1413 }
1414 }
1415}
1416
1417pub fn wgpu_graph_default() -> bool {
1418 #[cfg(feature = "gpu")]
1419 {
1420 matches!(backend(), Backend::Wgpu)
1426 && (crate::gpu_wgpu::discrete_active()
1427 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1428 }
1429 #[cfg(not(feature = "gpu"))]
1430 {
1431 false
1432 }
1433}
1434
1435#[allow(clippy::too_many_arguments, unused_variables)]
1437pub fn q8_matvec_range(
1438 model: &Arc<CmfModel>,
1439 idx: usize,
1440 row0: usize,
1441 row_scale: &[f32],
1442 xs: &[f32],
1443 rows: usize,
1444 cols: usize,
1445 out: &mut [f32],
1446) -> bool {
1447 match backend() {
1448 #[cfg(target_os = "macos")]
1449 Backend::Metal => {
1450 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1451 }
1452 #[cfg(feature = "gpu")]
1453 Backend::Wgpu => {
1454 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1455 }
1456 Backend::None => false,
1457 }
1458}
1459
1460pub(crate) fn q82_short_rows(
1462 model: &Arc<CmfModel>,
1463 idx: usize,
1464 xs: &[f32],
1465 b: usize,
1466 rows: usize,
1467 cols: usize,
1468 out: &mut [f32],
1469) -> bool {
1470 #[cfg(feature = "gpu")]
1471 if enabled_here() && backend() == Backend::Wgpu {
1472 return crate::gpu_wgpu::q82_short_rows(model, idx, xs, b, rows, cols, out);
1473 }
1474 let _ = (model, idx, xs, b, rows, cols, out);
1475 false
1476}
1477
1478#[allow(clippy::too_many_arguments, unused_variables)]
1481#[allow(clippy::too_many_arguments)]
1485pub fn q8_matmat_2f(
1486 model: &Arc<CmfModel>,
1487 idx: usize,
1488 row_scale: &[f32],
1489 col_field: &[f32],
1490 xs: &[f32],
1491 b: usize,
1492 rows: usize,
1493 cols: usize,
1494 out: &mut [f32],
1495) -> bool {
1496 #[allow(unreachable_patterns)]
1497 match backend() {
1498 #[cfg(feature = "gpu")]
1499 Backend::Wgpu => {
1500 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1501 }
1502 _ => false,
1503 }
1504}
1505
1506pub fn q8_matmat(
1507 model: &Arc<CmfModel>,
1508 idx: usize,
1509 row_scale: &[f32],
1510 pre: &[f32],
1511 b: usize,
1512 rows: usize,
1513 cols: usize,
1514 out: &mut [f32],
1515) -> bool {
1516 match backend() {
1517 #[cfg(target_os = "macos")]
1518 Backend::Metal => {
1519 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1520 }
1521 #[cfg(feature = "gpu")]
1522 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1523 Backend::None => false,
1524 }
1525}
1526
1527#[allow(unused_variables)]
1530pub fn q1_matvec(
1531 model: &Arc<CmfModel>,
1532 idx: usize,
1533 xs: &[f32],
1534 rows: usize,
1535 cols: usize,
1536 out: &mut [f32],
1537) -> bool {
1538 match backend() {
1539 #[cfg(target_os = "macos")]
1540 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1541 #[cfg(feature = "gpu")]
1542 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1543 Backend::None => false,
1544 }
1545}
1546
1547#[allow(unused_variables)]
1551pub fn q1g_matvec(
1552 model: &Arc<CmfModel>,
1553 idx: usize,
1554 xs: &[f32],
1555 rows: usize,
1556 cols: usize,
1557 out: &mut [f32],
1558) -> bool {
1559 match backend() {
1560 #[cfg(target_os = "macos")]
1561 Backend::Metal => crate::gpu_metal::q1g_matvec(model, idx, xs, rows, cols, out),
1562 #[cfg(feature = "gpu")]
1563 Backend::Wgpu => crate::gpu_wgpu::q1g_matvec(model, idx, xs, rows, cols, out),
1564 Backend::None => false,
1565 }
1566}
1567
1568#[allow(clippy::too_many_arguments)]
1572pub fn attn_dropin(
1573 model: &Arc<CmfModel>,
1574 kv_id: u64,
1575 layer: usize,
1576 normed: &[f32],
1577 wq_idx: usize,
1578 wk_idx: usize,
1579 wv_idx: usize,
1580 wo_idx: usize,
1581 q_norm: Option<&[f32]>,
1582 k_norm: Option<&[f32]>,
1583 late_qk_norm: bool,
1584 invf: &[f32],
1585 nh: usize,
1586 nkv: usize,
1587 hd: usize,
1588 rd: usize,
1589 hidden: usize,
1590 pos: usize,
1591 cap: usize,
1592 gemma: bool,
1593 eps: f32,
1594 cpu_k: &[Vec<f32>],
1595 cpu_v: &[Vec<f32>],
1596 out: &mut [f32],
1597) -> bool {
1598 match backend() {
1599 #[cfg(feature = "gpu")]
1600 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1601 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1602 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1603 ),
1604 #[allow(unused_variables)]
1605 _ => false,
1606 }
1607}
1608
1609#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1613pub enum GraphPrismOp {
1614 None,
1615 Forward,
1616 InverseEmbedding,
1617}
1618
1619pub const GRAPH_KIND_BLR: u8 = 11;
1623
1624pub struct GraphW<'a> {
1628 pub idx: usize,
1629 pub kind: u8,
1630 pub row_scale: &'a [f32],
1631 pub data: &'a [f32],
1632 pub prism: GraphPrismOp,
1633 pub affine: bool,
1634}
1635
1636pub enum GraphAttn<'a> {
1639 Full {
1640 wq: GraphW<'a>,
1641 wk: GraphW<'a>,
1642 wv: GraphW<'a>,
1643 wo: GraphW<'a>,
1644 q_norm: Option<&'a [f32]>,
1645 k_norm: Option<&'a [f32]>,
1646 late_qk_norm: bool,
1648 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1650 output_gate: bool,
1653 cpu_k: &'a [Vec<f32>],
1654 cpu_v: &'a [Vec<f32>],
1655 cpu_base: usize,
1659 geom: Option<GraphAttnGeom<'a>>,
1666 head_gate: Option<GraphW<'a>>,
1671 },
1672 Gdn {
1673 qkv: GraphW<'a>,
1674 z: GraphW<'a>,
1675 a: GraphW<'a>,
1676 b: GraphW<'a>,
1677 out: GraphW<'a>,
1678 conv1d: &'a [f32],
1679 a_log: &'a [f32],
1680 dt_bias: &'a [f32],
1681 norm: &'a [f32],
1682 nv: usize,
1683 nk: usize,
1684 dk: usize,
1685 dv: usize,
1686 kk: usize,
1687 cpu_state: &'a [f32],
1692 },
1693 ShortConv {
1700 inp: GraphW<'a>,
1702 out: GraphW<'a>,
1704 taps: &'a [f32],
1707 kernel: usize,
1708 cpu_state: &'a [f32],
1712 },
1713}
1714
1715#[derive(Clone, Copy)]
1720pub struct GraphAttnGeom<'a> {
1721 pub nkv: usize,
1723 pub hd: usize,
1727 pub dv: usize,
1729 pub rd: usize,
1731 pub invf: &'a [f32],
1733 pub rope_scale: f32,
1738 pub window: Option<usize>,
1741 pub sink: Option<&'a [f32]>,
1744 pub v_norm: bool,
1747}
1748
1749#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1753pub enum GraphAct {
1754 Silu,
1755 GeluErf,
1758 GeluTanh,
1761}
1762
1763impl GraphAct {
1764 pub fn code(self) -> u32 {
1765 match self {
1766 Self::Silu => 0,
1767 Self::GeluErf => 1,
1768 Self::GeluTanh => 2,
1769 }
1770 }
1771}
1772
1773pub struct GraphLayer<'a> {
1775 pub input_norm: &'a [f32],
1776 pub attn: GraphAttn<'a>,
1777 pub post_norm: &'a [f32],
1778 pub ffn: GraphFfn<'a>,
1779 pub attn_out_norm: Option<&'a [f32]>,
1782 pub ffn_out_norm: Option<&'a [f32]>,
1783 pub layer_scale: Option<f32>,
1786}
1787
1788pub enum GraphFfn<'a> {
1793 AttentionOnly,
1796 Dense {
1797 gate: GraphW<'a>,
1798 up: GraphW<'a>,
1799 down: GraphW<'a>,
1800 act: GraphAct,
1803 },
1804 Moe {
1805 router: GraphW<'a>,
1807 shared_gate: GraphW<'a>,
1809 experts: Vec<(usize, usize, usize)>,
1813 n_exp: usize,
1815 top_k: usize,
1816 inter: usize,
1817 norm_topk: bool,
1818 q4tp: bool,
1824 gu_q2: bool,
1828 q82: bool,
1833 sigmoid: bool,
1837 bias: Option<&'a [f32]>,
1840 has_shared: bool,
1844 shared_gated: bool,
1849 route_scale: f32,
1852 },
1853}
1854
1855#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1858pub enum TokenGraphOutcome {
1859 Declined,
1861 Completed,
1863 Failed,
1865}
1866
1867#[allow(clippy::too_many_arguments)]
1872pub fn forward_token_graph(
1873 model: &Arc<CmfModel>,
1874 kv_id: u64,
1875 layers: &[GraphLayer],
1876 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1879 o1_epoch: u64,
1880 invf: &[f32],
1881 h: &mut [f32],
1882 nh: usize,
1883 nkv: usize,
1884 hd: usize,
1885 attn_scale: f32,
1886 rd: usize,
1887 hidden: usize,
1888 inter: usize,
1889 position: usize,
1890 cap: usize,
1891 gemma: bool,
1892 eps: f32,
1893 lm_head: Option<(&GraphW, usize)>,
1894 final_norm: &[f32],
1895 logits: &mut Vec<f32>,
1896 loop_norm_at: &[usize],
1897 steps: usize,
1898 embed: Option<(&GraphW, usize, f32)>,
1899 ids_out: Option<&mut Vec<u32>>,
1900 layers_run: Option<&mut usize>,
1903 layer_base: usize,
1907 hidden_too: bool,
1909) -> TokenGraphOutcome {
1910 match backend() {
1911 #[cfg(feature = "gpu")]
1912 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1913 model,
1914 kv_id,
1915 layers,
1916 o1,
1917 o1_epoch,
1918 invf,
1919 h,
1920 nh,
1921 nkv,
1922 hd,
1923 attn_scale,
1924 rd,
1925 hidden,
1926 inter,
1927 position,
1928 cap,
1929 gemma,
1930 eps,
1931 lm_head,
1932 final_norm,
1933 logits,
1934 loop_norm_at,
1935 steps,
1936 embed,
1937 ids_out,
1938 layers_run,
1939 layer_base,
1940 hidden_too,
1941 ),
1942 #[allow(unused_variables)]
1943 _ => {
1944 let _ = (
1945 attn_scale,
1946 lm_head,
1947 final_norm,
1948 logits,
1949 loop_norm_at,
1950 layers_run,
1951 layer_base,
1952 hidden_too,
1953 );
1954 TokenGraphOutcome::Declined
1955 }
1956 }
1957}
1958
1959pub fn forward_embryo_graph(
1964 model: &Arc<EmbryoGraphModel>,
1965 kv_id: u64,
1966 hidden: &[f32],
1967 position: usize,
1968 logits: &mut Vec<f32>,
1969) -> bool {
1970 #[cfg(feature = "gpu")]
1971 if backend() == Backend::Wgpu {
1972 return crate::gpu_wgpu::forward_embryo_graph(model, kv_id, hidden, position, logits);
1973 }
1974 let _ = (model, kv_id, hidden, position, logits);
1975 false
1976}
1977
1978#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1982pub enum BatchGraphOutcome {
1983 Declined,
1986 Completed,
1988 Failed,
1992}
1993
1994pub struct SpecTail<'a> {
1995 pub lm: GraphW<'a>,
1996 pub lm_rows: usize,
1997 pub final_norm: &'a [f32],
1998 pub logits_out: &'a mut Vec<f32>,
1999}
2000
2001#[allow(clippy::too_many_arguments)]
2005pub fn forward_batch_graph(
2006 model: &Arc<CmfModel>,
2007 kv_id: u64,
2008 layers: &[GraphLayer],
2009 invf: &[f32],
2010 h: &mut [f32],
2011 nh: usize,
2012 nkv: usize,
2013 hd: usize,
2014 rd: usize,
2015 hidden: usize,
2016 inter: usize,
2017 positions: &[usize],
2018 cap: usize,
2019 gemma: bool,
2020 eps: f32,
2021 attn_scale: f32,
2022 k: usize,
2023 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
2026 o1_epoch: u64,
2027 spec: Option<SpecTail<'_>>,
2028 layers_run: Option<&mut usize>,
2034) -> BatchGraphOutcome {
2035 forward_batch_graph_at(model, kv_id, 0, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run)
2036}
2037
2038thread_local! {
2039 static MIMO_ATTN_SCRATCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
2040}
2041
2042pub(crate) fn mimo_attention_scratch_enabled() -> bool {
2043 MIMO_ATTN_SCRATCH.with(std::cell::Cell::get)
2044}
2045
2046#[doc(hidden)]
2049pub fn mimo_attention_scratch_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2050 struct Restore(bool);
2051 impl Drop for Restore {
2052 fn drop(&mut self) {
2053 MIMO_ATTN_SCRATCH.with(|v| v.set(self.0));
2054 }
2055 }
2056 let _restore = Restore(MIMO_ATTN_SCRATCH.with(|v| v.replace(enabled)));
2057 f()
2058}
2059
2060pub fn g4_batch_on() -> bool {
2063 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2064 *ON.get_or_init(|| std::env::var("CMF_G4_BATCH").as_deref() != Ok("0"))
2065}
2066
2067pub(crate) fn moe_q82_graph_on() -> bool {
2070 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2071 *ON.get_or_init(|| std::env::var("CMF_MOE_Q82").as_deref() != Ok("0"))
2072}
2073
2074thread_local! {
2075 static MIMO_Q8_SHORT: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
2076}
2077
2078pub(crate) fn mimo_q8_short_enabled() -> bool {
2079 MIMO_Q8_SHORT.with(std::cell::Cell::get)
2080}
2081
2082#[doc(hidden)]
2084pub fn mimo_q8_short_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2085 struct Restore(bool);
2086 impl Drop for Restore {
2087 fn drop(&mut self) {
2088 MIMO_Q8_SHORT.with(|v| v.set(self.0));
2089 }
2090 }
2091 let _restore = Restore(MIMO_Q8_SHORT.with(|v| v.replace(enabled)));
2092 f()
2093}
2094
2095#[allow(clippy::too_many_arguments)]
2097pub fn forward_batch_graph_at(
2098 model: &Arc<CmfModel>,
2099 kv_id: u64,
2100 layer_base: usize,
2101 layers: &[GraphLayer],
2102 invf: &[f32],
2103 h: &mut [f32],
2104 nh: usize,
2105 nkv: usize,
2106 hd: usize,
2107 rd: usize,
2108 hidden: usize,
2109 inter: usize,
2110 positions: &[usize],
2111 cap: usize,
2112 gemma: bool,
2113 eps: f32,
2114 attn_scale: f32,
2115 k: usize,
2116 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
2119 o1_epoch: u64,
2120 spec: Option<SpecTail<'_>>,
2121 layers_run: Option<&mut usize>,
2127) -> BatchGraphOutcome {
2128 match backend() {
2129 #[cfg(feature = "gpu")]
2130 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph_at(
2131 model, kv_id, layer_base, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions,
2132 cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run,
2133 ),
2134 #[allow(unreachable_patterns)]
2135 _ => {
2136 let _ = (o1, o1_epoch, spec, layers_run);
2137 BatchGraphOutcome::Declined
2138 }
2139 }
2140}
2141
2142pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
2147 #[cfg(feature = "gpu")]
2148 if backend() == Backend::Wgpu {
2149 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
2150 }
2151 #[allow(unreachable_code)]
2152 {
2153 let _ = (kv_id, slot, base_pos, expected_layers);
2154 false
2155 }
2156}
2157
2158pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
2164 #[cfg(feature = "gpu")]
2165 if backend() == Backend::Wgpu {
2166 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
2167 }
2168 #[cfg(target_os = "macos")]
2169 if backend() == Backend::Metal {
2170 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
2171 return true;
2172 }
2173 false
2174}
2175
2176pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
2180 #[cfg(feature = "gpu")]
2181 if backend() == Backend::Wgpu {
2182 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
2183 }
2184 None
2185}
2186
2187pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
2190 #[cfg(feature = "gpu")]
2191 if backend() == Backend::Wgpu {
2192 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
2193 }
2194 false
2195}
2196
2197pub fn graph_kv_read_rows(
2201 _kv_id: u64,
2202 _reqs: &[(usize, usize, usize)],
2203 _nkv: usize,
2204 _hd: usize,
2205) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
2206 #[cfg(feature = "gpu")]
2207 if backend() == Backend::Wgpu {
2208 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
2209 }
2210 None
2211}
2212
2213pub fn graph_kv_pull_host(
2219 _kv_id: u64,
2220 _layer: usize,
2221 _from: usize,
2222 _to: usize,
2223 _nkv: usize,
2224 _hd: usize,
2225) -> Option<(Vec<f32>, Vec<f32>, usize)> {
2226 #[cfg(feature = "gpu")]
2227 if backend() == Backend::Wgpu {
2228 return crate::gpu_wgpu::kv_mirror_pull_host(_kv_id, _layer, _from, _to, _nkv, _hd);
2229 }
2230 None
2231}
2232
2233pub fn graph_kv_reset(_kv_id: u64) {
2235 #[cfg(feature = "gpu")]
2236 if backend() == Backend::Wgpu {
2237 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
2238 crate::gpu_wgpu::embryo_graph_reset(_kv_id);
2239 }
2240}
2241
2242pub fn q1t_matvec(
2246 model: &Arc<CmfModel>,
2247 idx: usize,
2248 xs: &[f32],
2249 rows: usize,
2250 cols: usize,
2251 out: &mut [f32],
2252) -> bool {
2253 match backend() {
2254 #[cfg(target_os = "macos")]
2255 Backend::Metal => {
2256 if metal_q1t_enabled() {
2257 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
2258 } else {
2259 false
2260 }
2261 }
2262 #[cfg(feature = "gpu")]
2263 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
2264 Backend::None => false,
2265 }
2266}
2267
2268#[allow(unused_variables)]
2271pub fn q4b_matvec(
2272 model: &Arc<CmfModel>,
2273 idx: usize,
2274 xs: &[f32],
2275 rows: usize,
2276 cols: usize,
2277 out: &mut [f32],
2278) -> bool {
2279 match backend() {
2280 #[cfg(target_os = "macos")]
2281 Backend::Metal => false,
2282 #[cfg(feature = "gpu")]
2283 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
2284 Backend::None => false,
2285 }
2286}
2287
2288pub fn q1t_matmat(
2291 model: &Arc<CmfModel>,
2292 idx: usize,
2293 xs: &[f32],
2294 b: usize,
2295 rows: usize,
2296 cols: usize,
2297 out: &mut [f32],
2298) -> bool {
2299 match backend() {
2300 #[cfg(target_os = "macos")]
2301 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
2305 #[cfg(feature = "gpu")]
2306 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
2307 Backend::None => false,
2308 }
2309}
2310
2311#[cfg(target_os = "macos")]
2315pub(crate) fn metal_q1t_enabled() -> bool {
2316 std::env::var("CMF_METAL_Q1T")
2317 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
2318 .unwrap_or(true)
2319}
2320
2321pub fn q1_matmat(
2323 model: &Arc<CmfModel>,
2324 idx: usize,
2325 xs: &[f32],
2326 b: usize,
2327 rows: usize,
2328 cols: usize,
2329 out: &mut [f32],
2330) -> bool {
2331 match backend() {
2332 #[cfg(feature = "gpu")]
2333 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
2334 #[allow(unused_variables)]
2335 _ => false,
2336 }
2337}
2338
2339pub fn q1g_matmat(
2341 model: &Arc<CmfModel>,
2342 idx: usize,
2343 xs: &[f32],
2344 b: usize,
2345 rows: usize,
2346 cols: usize,
2347 out: &mut [f32],
2348) -> bool {
2349 match backend() {
2350 #[cfg(feature = "gpu")]
2351 Backend::Wgpu => crate::gpu_wgpu::q1g_matmat(model, idx, xs, b, rows, cols, out),
2352 #[allow(unused_variables)]
2353 _ => false,
2354 }
2355}
2356
2357static MM_KILL: AtomicBool = AtomicBool::new(false);
2362pub(crate) fn mm_killed() -> bool {
2363 MM_KILL.load(Ordering::Relaxed)
2364}
2365pub(crate) fn mm_kill() {
2366 MM_KILL.store(true, Ordering::Relaxed);
2367}
2368
2369static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2376const MM_STRIKES_TO_KILL: u32 = 3;
2377static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2384
2385pub fn mm_kill_arm(on: bool) {
2388 MM_ARMED.store(on, Ordering::Relaxed);
2389 if on {
2390 MM_STRIKES.store(0, Ordering::Relaxed);
2391 }
2392}
2393
2394pub(crate) fn mm_budget_check(
2401 what: &str,
2402 el: std::time::Duration,
2403 budget: std::time::Duration,
2404 exempt: bool,
2405) {
2406 if el <= budget {
2407 MM_STRIKES.store(0, Ordering::Relaxed);
2408 return;
2409 }
2410 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
2411 return;
2412 }
2413 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2414 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
2415 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
2416 if !on {
2417 tracing::info!(
2418 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
2419 );
2420 return;
2421 }
2422 if n >= MM_STRIKES_TO_KILL {
2423 tracing::warn!(
2424 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
2425 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
2426 );
2427 mm_kill();
2428 } else {
2429 tracing::info!(
2430 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
2431 );
2432 }
2433}
2434
2435#[allow(unused_variables, clippy::too_many_arguments)]
2440pub fn chunk_attend(
2441 q: &[f32],
2442 k: &[&[f32]],
2443 v: &[&[f32]],
2444 b: usize,
2445 s0: usize,
2446 nh: usize,
2447 nkv: usize,
2448 hd: usize,
2449 scale: f32,
2450 out: &mut [f32],
2451) -> bool {
2452 match backend() {
2453 #[cfg(feature = "gpu")]
2454 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
2455 #[allow(unreachable_patterns)]
2456 _ => false,
2457 }
2458}
2459
2460#[allow(unused_variables, clippy::too_many_arguments)]
2463pub fn chunk_attend_win(
2464 q: &[f32],
2465 k: &[&[f32]],
2466 v: &[&[f32]],
2467 b: usize,
2468 s0: usize,
2469 nh: usize,
2470 nkv: usize,
2471 hd: usize,
2472 scale: f32,
2473 window: usize,
2474 out: &mut [f32],
2475) -> bool {
2476 match backend() {
2477 #[cfg(feature = "gpu")]
2478 Backend::Wgpu => {
2479 crate::gpu_wgpu::chunk_attend_win(q, k, v, b, s0, nh, nkv, hd, scale, window, out)
2480 }
2481 #[allow(unreachable_patterns)]
2482 _ => false,
2483 }
2484}
2485
2486#[allow(unused_variables)]
2489pub fn gemm_many_keep(
2490 model: &Arc<CmfModel>,
2491 idxs: &[(usize, usize)],
2492 xs: &[f32],
2493 b: usize,
2494 cols: usize,
2495 out: &mut [f32],
2496) -> bool {
2497 match backend() {
2498 #[cfg(feature = "gpu")]
2499 Backend::Wgpu => crate::gpu_wgpu::gemm_many_keep(model, idxs, xs, b, cols, out),
2500 #[allow(unreachable_patterns)]
2501 _ => false,
2502 }
2503}
2504
2505thread_local! {
2506 static PREFILL_FAST_GEMM: Cell<bool> = const { Cell::new(false) };
2510}
2511
2512pub struct PrefillFastGemmGuard(bool);
2514
2515impl Drop for PrefillFastGemmGuard {
2516 fn drop(&mut self) {
2517 PREFILL_FAST_GEMM.with(|c| c.set(self.0));
2518 }
2519}
2520
2521pub fn enter_prefill_fast_gemm() -> PrefillFastGemmGuard {
2525 PrefillFastGemmGuard(PREFILL_FAST_GEMM.with(|c| c.replace(true)))
2526}
2527
2528pub fn prefill_fast_gemm() -> bool {
2530 PREFILL_FAST_GEMM.with(|c| c.get())
2531}
2532
2533pub const F16_MAX: f32 = 65504.0;
2536
2537pub fn abs_max_or_inf(xs: &[f32]) -> f32 {
2541 let mut acc = [0f32; 16];
2542 let mut bad = [false; 16];
2543 let mut it = xs.chunks_exact(16);
2544 for c in &mut it {
2545 for ((a, f), &v) in acc.iter_mut().zip(bad.iter_mut()).zip(c) {
2546 let v = v.abs();
2547 *f |= !(v < f32::INFINITY);
2549 *a = if v > *a { v } else { *a };
2550 }
2551 }
2552 let mut m = 0f32;
2553 for &v in it.remainder() {
2554 let v = v.abs();
2555 if !(v < f32::INFINITY) {
2556 return f32::INFINITY;
2557 }
2558 m = m.max(v);
2559 }
2560 if bad.iter().any(|&f| f) {
2561 return f32::INFINITY;
2562 }
2563 acc.iter().fold(m, |m, &a| m.max(a))
2564}
2565
2566#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2572pub struct PrefillMirror {
2573 pub kv_id: u64,
2574 pub layer: usize,
2575 pub limit: usize,
2576}
2577
2578thread_local! {
2579 static PREFILL_MIRROR: Cell<Option<PrefillMirror>> = const { Cell::new(None) };
2583}
2584
2585pub struct PrefillMirrorGuard(Option<PrefillMirror>);
2587
2588impl Drop for PrefillMirrorGuard {
2589 fn drop(&mut self) {
2590 PREFILL_MIRROR.with(|c| c.set(self.0));
2591 }
2592}
2593
2594pub fn enter_prefill_mirror(t: PrefillMirror) -> PrefillMirrorGuard {
2597 PrefillMirrorGuard(PREFILL_MIRROR.with(|c| c.replace(Some(t))))
2598}
2599
2600pub fn prefill_mirror() -> Option<PrefillMirror> {
2602 PREFILL_MIRROR.with(|c| c.get())
2603}
2604
2605#[allow(unused_variables, clippy::too_many_arguments)]
2618pub fn chunk_attend_mirror(
2619 t: PrefillMirror,
2620 cpu_k: &[Vec<f32>],
2621 cpu_v: &[Vec<f32>],
2622 base: usize,
2623 q: &[f32],
2624 b: usize,
2625 s0: usize,
2626 nh: usize,
2627 nkv: usize,
2628 hd: usize,
2629 scale: f32,
2630 ring: Option<usize>,
2631 window: usize,
2632 operand_max: f32,
2633 out: &mut [f32],
2634) -> bool {
2635 match backend() {
2636 #[cfg(feature = "gpu")]
2637 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror(
2642 t.kv_id,
2643 t.layer,
2644 t.limit,
2645 cpu_k,
2646 cpu_v,
2647 base,
2648 q,
2649 b,
2650 s0,
2651 nh,
2652 nkv,
2653 hd,
2654 scale,
2655 ring,
2656 window,
2657 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2658 operand_max,
2659 out,
2660 ),
2661 #[allow(unreachable_patterns)]
2662 _ => false,
2663 }
2664}
2665
2666#[allow(unused_variables, clippy::too_many_arguments)]
2674pub fn chunk_attend_mirror_wo(
2675 t: PrefillMirror,
2676 cpu_k: &[Vec<f32>],
2677 cpu_v: &[Vec<f32>],
2678 base: usize,
2679 q: &[f32],
2680 b: usize,
2681 s0: usize,
2682 nh: usize,
2683 nkv: usize,
2684 hd: usize,
2685 scale: f32,
2686 ring: Option<usize>,
2687 window: usize,
2688 operand_max: f32,
2689 gains: Option<&[f32]>,
2690 wo: (&Arc<CmfModel>, usize),
2691 hidden: usize,
2692 out: &mut [f32],
2693) -> bool {
2694 match backend() {
2695 #[cfg(feature = "gpu")]
2696 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror_wo(
2697 t.kv_id,
2698 t.layer,
2699 t.limit,
2700 cpu_k,
2701 cpu_v,
2702 base,
2703 q,
2704 b,
2705 s0,
2706 nh,
2707 nkv,
2708 hd,
2709 scale,
2710 ring,
2711 window,
2712 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2713 operand_max,
2714 gains,
2715 wo,
2716 hidden,
2717 out,
2718 ),
2719 #[allow(unreachable_patterns)]
2720 _ => false,
2721 }
2722}
2723
2724pub fn chunk_attend_windowed() -> bool {
2726 match backend() {
2727 #[cfg(feature = "gpu")]
2728 Backend::Wgpu => true,
2729 #[allow(unreachable_patterns)]
2730 _ => false,
2731 }
2732}
2733
2734#[allow(unused_variables, clippy::too_many_arguments)]
2738pub fn q4t_qkv(
2739 model: &Arc<CmfModel>,
2740 wq: usize,
2741 wk: usize,
2742 wv: usize,
2743 xs: &[f32],
2744 b: usize,
2745 cols: usize,
2746 rq: usize,
2747 rk: usize,
2748 rv: usize,
2749 out: &mut [f32],
2750) -> bool {
2751 match backend() {
2752 #[cfg(feature = "gpu")]
2753 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
2754 #[allow(unreachable_patterns)]
2755 _ => false,
2756 }
2757}
2758
2759#[allow(unused_variables, clippy::too_many_arguments)]
2761#[allow(clippy::too_many_arguments, unused_variables)]
2765pub fn q4tp_ffn_packed(
2766 model: &Arc<CmfModel>,
2767 w1: usize,
2768 w2: usize,
2769 xs: &[f32],
2770 b: usize,
2771 hidden: usize,
2772 inter: usize,
2773 bias: Option<&[f32]>,
2774 out: &mut [f32],
2775) -> bool {
2776 match backend() {
2777 #[cfg(feature = "gpu")]
2778 Backend::Wgpu => {
2779 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
2780 }
2781 #[allow(unreachable_patterns)]
2782 _ => false,
2783 }
2784}
2785
2786pub fn q4tp_ffn(
2787 model: &Arc<CmfModel>,
2788 w1: usize,
2789 w3: usize,
2790 w2: usize,
2791 xs: &[f32],
2792 b: usize,
2793 hidden: usize,
2794 inter: usize,
2795 out: &mut [f32],
2796) -> bool {
2797 match backend() {
2798 #[cfg(target_os = "macos")]
2799 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2800 #[cfg(feature = "gpu")]
2801 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2802 #[allow(unreachable_patterns)]
2803 _ => false,
2804 }
2805}
2806
2807#[allow(clippy::too_many_arguments, unused_variables)]
2811pub fn q4_ffn_act(
2812 model: &Arc<CmfModel>,
2813 w1: usize,
2814 w3: usize,
2815 w2: usize,
2816 xs: &[f32],
2817 b: usize,
2818 hidden: usize,
2819 inter: usize,
2820 q4tp: bool,
2821 act: GraphAct,
2822 out: &mut [f32],
2823) -> bool {
2824 match act {
2825 GraphAct::Silu if q4tp => q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2826 GraphAct::Silu => q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2827 GraphAct::GeluErf | GraphAct::GeluTanh => match backend() {
2828 #[cfg(feature = "gpu")]
2829 Backend::Wgpu => crate::gpu_wgpu::q4_ffn_act(
2830 model,
2831 w1,
2832 w3,
2833 w2,
2834 xs,
2835 b,
2836 hidden,
2837 inter,
2838 q4tp,
2839 act.code(),
2840 out,
2841 ),
2842 #[allow(unreachable_patterns)]
2843 _ => false,
2844 },
2845 }
2846}
2847
2848#[allow(clippy::too_many_arguments, unused_variables)]
2853pub fn ffn_act_keep(
2854 model: &Arc<CmfModel>,
2855 w1: usize,
2856 w3: usize,
2857 w2: usize,
2858 xs: &[f32],
2859 b: usize,
2860 hidden: usize,
2861 inter: usize,
2862 act: GraphAct,
2863 out: &mut [f32],
2864) -> bool {
2865 match backend() {
2866 #[cfg(feature = "gpu")]
2867 Backend::Wgpu => crate::gpu_wgpu::ffn_act_keep(
2868 model,
2869 w1,
2870 w3,
2871 w2,
2872 xs,
2873 b,
2874 hidden,
2875 inter,
2876 act.code(),
2877 out,
2878 ),
2879 #[allow(unreachable_patterns)]
2880 _ => false,
2881 }
2882}
2883
2884#[allow(clippy::too_many_arguments, unused_variables)]
2889pub fn q4tp_gelu_ffn(
2890 model: &Arc<CmfModel>,
2891 w_in: usize,
2892 w_out: usize,
2893 xs: &[f32],
2894 b: usize,
2895 hidden: usize,
2896 inter: usize,
2897 bias_in: &[f32],
2898 bias_out: &[f32],
2899 out: &mut [f32],
2900) -> bool {
2901 match backend() {
2902 #[cfg(feature = "gpu")]
2903 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2904 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2905 ),
2906 #[allow(unreachable_patterns)]
2907 _ => false,
2908 }
2909}
2910
2911pub fn q4t_ffn(
2912 model: &Arc<CmfModel>,
2913 w1: usize,
2914 w3: usize,
2915 w2: usize,
2916 xs: &[f32],
2917 b: usize,
2918 hidden: usize,
2919 inter: usize,
2920 out: &mut [f32],
2921) -> bool {
2922 match backend() {
2923 #[cfg(target_os = "macos")]
2924 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2925 #[cfg(feature = "gpu")]
2926 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2927 #[allow(unreachable_patterns)]
2928 _ => false,
2929 }
2930}
2931
2932pub struct DitBlockArgs<'a> {
2937 pub n: usize,
2938 pub hidden: usize,
2939 pub inter: usize,
2940 pub nh: usize,
2941 pub nkv: usize,
2942 pub hd: usize,
2943 pub eps: f32,
2944 pub rope_cos: &'a [f32],
2945 pub rope_sin: &'a [f32],
2946 pub norm1: &'a [f32],
2947 pub norm2: &'a [f32],
2948 pub ffn_norm1: &'a [f32],
2949 pub ffn_norm2: &'a [f32],
2950 pub norm_q: &'a [f32],
2951 pub norm_k: &'a [f32],
2952 pub s_msa: &'a [f32],
2953 pub gate_msa: &'a [f32],
2954 pub s_mlp: &'a [f32],
2955 pub gate_mlp: &'a [f32],
2956 pub wq: usize,
2957 pub wk: usize,
2958 pub wv: usize,
2959 pub wo: usize,
2960 pub w1: usize,
2961 pub w3: usize,
2962 pub w2: usize,
2963 pub q4tp: bool,
2967 pub resident_in: bool,
2970 pub resident_out: bool,
2974}
2975
2976pub fn dit_chain_supported() -> bool {
2980 #[cfg(feature = "gpu")]
2981 {
2982 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2983 }
2984 #[allow(unreachable_code)]
2985 false
2986}
2987
2988pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2991 #[cfg(feature = "gpu")]
2992 {
2993 if matches!(backend(), Backend::Wgpu) {
2994 return crate::gpu_wgpu::dit_state_fetch(_x);
2995 }
2996 }
2997 false
2998}
2999
3000#[allow(unused_variables)]
3004#[allow(unused_variables, clippy::too_many_arguments)]
3008pub fn dit_qkv(
3009 model: &Arc<CmfModel>,
3010 wq: usize,
3011 wk: usize,
3012 wv: usize,
3013 xs: &[f32],
3014 b: usize,
3015 hidden: usize,
3016 qrows: usize,
3017 kvrows: usize,
3018 q_out: &mut [f32],
3019 k_out: &mut [f32],
3020 v_out: &mut [f32],
3021) -> bool {
3022 match backend() {
3023 #[cfg(feature = "gpu")]
3024 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
3025 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
3026 ),
3027 #[allow(unreachable_patterns)]
3028 _ => false,
3029 }
3030}
3031
3032pub struct QwenImageAttentionArgs<'a> {
3040 pub image: &'a [f32],
3041 pub text: &'a [f32],
3042 pub image_tokens: usize,
3043 pub text_tokens: usize,
3044 pub heads: usize,
3045 pub head_dim: usize,
3046 pub image_q: usize,
3047 pub image_k: usize,
3048 pub image_v: usize,
3049 pub text_q: usize,
3050 pub text_k: usize,
3051 pub text_v: usize,
3052 pub image_out: usize,
3053 pub text_out: usize,
3054 pub image_q_norm: &'a [f32],
3055 pub image_k_norm: &'a [f32],
3056 pub text_q_norm: &'a [f32],
3057 pub text_k_norm: &'a [f32],
3058 pub image_cos: &'a [f32],
3059 pub image_sin: &'a [f32],
3060 pub text_cos: &'a [f32],
3061 pub text_sin: &'a [f32],
3062 pub image_q_bias: &'a [f32],
3063 pub image_k_bias: &'a [f32],
3064 pub image_v_bias: &'a [f32],
3065 pub text_q_bias: &'a [f32],
3066 pub text_k_bias: &'a [f32],
3067 pub text_v_bias: &'a [f32],
3068 pub image_out_bias: &'a [f32],
3069 pub text_out_bias: &'a [f32],
3070 pub image_proj: &'a mut [f32],
3071 pub text_proj: &'a mut [f32],
3072}
3073
3074#[allow(clippy::too_many_fields)]
3079pub struct QwenImageChainBlock<'a> {
3080 pub image_mod: &'a [f32],
3081 pub text_mod: &'a [f32],
3082 pub image_q: usize,
3083 pub image_k: usize,
3084 pub image_v: usize,
3085 pub text_q: usize,
3086 pub text_k: usize,
3087 pub text_v: usize,
3088 pub image_out: usize,
3089 pub text_out: usize,
3090 pub image_q_norm: &'a [f32],
3091 pub image_k_norm: &'a [f32],
3092 pub text_q_norm: &'a [f32],
3093 pub text_k_norm: &'a [f32],
3094 pub image_q_bias: &'a [f32],
3095 pub image_k_bias: &'a [f32],
3096 pub image_v_bias: &'a [f32],
3097 pub text_q_bias: &'a [f32],
3098 pub text_k_bias: &'a [f32],
3099 pub text_v_bias: &'a [f32],
3100 pub image_out_bias: &'a [f32],
3101 pub text_out_bias: &'a [f32],
3102 pub image_attn_gate: &'a [f32],
3103 pub text_attn_gate: &'a [f32],
3104 pub image_mlp_in: usize,
3105 pub image_mlp_out: usize,
3106 pub text_mlp_in: usize,
3107 pub text_mlp_out: usize,
3108 pub image_mlp_in_bias: &'a [f32],
3109 pub image_mlp_out_bias: &'a [f32],
3110 pub text_mlp_in_bias: &'a [f32],
3111 pub text_mlp_out_bias: &'a [f32],
3112}
3113
3114#[allow(clippy::too_many_fields)]
3120pub struct QwenImageBlockArgs<'a> {
3121 pub image: &'a mut [f32],
3124 pub text: &'a mut [f32],
3125 pub image_norm: &'a [f32],
3126 pub text_norm: &'a [f32],
3127 pub image_tokens: usize,
3128 pub text_tokens: usize,
3129 pub heads: usize,
3130 pub head_dim: usize,
3131 pub image_cos: &'a [f32],
3132 pub image_sin: &'a [f32],
3133 pub text_cos: &'a [f32],
3134 pub text_sin: &'a [f32],
3135 pub image_q: usize,
3136 pub image_k: usize,
3137 pub image_v: usize,
3138 pub text_q: usize,
3139 pub text_k: usize,
3140 pub text_v: usize,
3141 pub image_out: usize,
3142 pub text_out: usize,
3143 pub image_q_norm: &'a [f32],
3144 pub image_k_norm: &'a [f32],
3145 pub text_q_norm: &'a [f32],
3146 pub text_k_norm: &'a [f32],
3147 pub image_q_bias: &'a [f32],
3148 pub image_k_bias: &'a [f32],
3149 pub image_v_bias: &'a [f32],
3150 pub text_q_bias: &'a [f32],
3151 pub text_k_bias: &'a [f32],
3152 pub text_v_bias: &'a [f32],
3153 pub image_out_bias: &'a [f32],
3154 pub text_out_bias: &'a [f32],
3155 pub image_attn_gate: &'a [f32],
3156 pub text_attn_gate: &'a [f32],
3157 pub image_mlp_in: usize,
3158 pub image_mlp_out: usize,
3159 pub text_mlp_in: usize,
3160 pub text_mlp_out: usize,
3161 pub image_mlp_in_bias: &'a [f32],
3162 pub image_mlp_out_bias: &'a [f32],
3163 pub text_mlp_in_bias: &'a [f32],
3164 pub text_mlp_out_bias: &'a [f32],
3165 pub image_mlp_mod: &'a [f32],
3166 pub text_mlp_mod: &'a [f32],
3167 pub image_mlp_gate: &'a [f32],
3168 pub text_mlp_gate: &'a [f32],
3169}
3170
3171pub struct QwenImageChainArgs<'a> {
3176 pub image: &'a mut [f32],
3177 pub text: &'a mut [f32],
3178 pub image_tokens: usize,
3179 pub text_tokens: usize,
3180 pub heads: usize,
3181 pub head_dim: usize,
3182 pub image_cos: &'a [f32],
3183 pub image_sin: &'a [f32],
3184 pub text_cos: &'a [f32],
3185 pub text_sin: &'a [f32],
3186 pub blocks: &'a [QwenImageChainBlock<'a>],
3187}
3188
3189#[allow(unused_variables)]
3190pub fn qwen_image_attention(
3191 model: &Arc<CmfModel>,
3192 args: &mut QwenImageAttentionArgs<'_>,
3193) -> bool {
3194 match backend() {
3195 #[cfg(feature = "gpu")]
3196 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
3197 #[allow(unreachable_patterns)]
3198 _ => false,
3199 }
3200}
3201
3202#[allow(unused_variables)]
3203pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
3204 match backend() {
3205 #[cfg(feature = "gpu")]
3206 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
3207 #[allow(unreachable_patterns)]
3208 _ => false,
3209 }
3210}
3211
3212#[allow(unused_variables)]
3216pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
3217 match backend() {
3218 #[cfg(feature = "gpu")]
3219 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
3220 #[allow(unreachable_patterns)]
3221 _ => false,
3222 }
3223}
3224
3225#[allow(unused_variables, clippy::too_many_arguments)]
3232pub fn qwen_image_mlp_inplace(
3233 model: &Arc<CmfModel>,
3234 w_in: usize,
3235 w_out: usize,
3236 data: &mut [f32],
3237 batch: usize,
3238 hidden: usize,
3239 inter: usize,
3240 bias_in: &[f32],
3241 bias_out: &[f32],
3242 modulation: &[f32],
3243 gate: &[f32],
3244) -> bool {
3245 match backend() {
3246 #[cfg(feature = "gpu")]
3247 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
3248 model,
3249 w_in,
3250 w_out,
3251 data,
3252 batch,
3253 hidden,
3254 inter,
3255 bias_in,
3256 bias_out,
3257 modulation,
3258 gate,
3259 ),
3260 #[allow(unreachable_patterns)]
3261 _ => false,
3262 }
3263}
3264
3265pub fn fused_dit_block_available() -> bool {
3269 #[cfg(target_os = "macos")]
3270 {
3271 matches!(backend(), Backend::Metal) && fused_block_trusted()
3272 }
3273 #[cfg(not(target_os = "macos"))]
3274 {
3275 false
3276 }
3277}
3278
3279pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
3280 dit_block_seg(model, a, &[a.n], x)
3281}
3282
3283pub fn dit_block_seg(
3287 model: &Arc<CmfModel>,
3288 a: &DitBlockArgs,
3289 segs: &[usize],
3290 x: &mut [f32],
3291) -> bool {
3292 match backend() {
3293 #[cfg(target_os = "macos")]
3294 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
3295 #[cfg(feature = "gpu")]
3302 Backend::Wgpu
3303 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
3304 Some("0") => false,
3305 Some(_) => true,
3306 None => crate::gpu_wgpu::discrete_active(),
3307 } =>
3308 {
3309 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
3310 }
3311 #[allow(unreachable_patterns)]
3312 _ => false,
3313 }
3314}
3315
3316pub struct VaeResnetArgs<'a> {
3320 pub groups: usize,
3321 pub ic: usize,
3322 pub oc: usize,
3323 pub h: usize,
3324 pub w: usize,
3325 pub n1w: &'a [f32],
3326 pub n1b: &'a [f32],
3327 pub c1w: &'a [f32],
3328 pub c1b: &'a [f32],
3329 pub c1k: usize,
3330 pub n2w: &'a [f32],
3331 pub n2b: &'a [f32],
3332 pub c2w: &'a [f32],
3333 pub c2b: &'a [f32],
3334 pub c2k: usize,
3335 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
3336}
3337
3338#[allow(unused_variables)]
3341pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
3342 match backend() {
3343 #[cfg(target_os = "macos")]
3344 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
3345 _ => false,
3346 }
3347}
3348
3349#[allow(unused_variables, clippy::too_many_arguments)]
3352pub fn vae_upsample_conv(
3353 w: &[f32],
3354 bias: &[f32],
3355 x: &[f32],
3356 ic: usize,
3357 oc: usize,
3358 h: usize,
3359 w_img: usize,
3360 k: usize,
3361 out: &mut [f32],
3362) -> bool {
3363 match backend() {
3364 #[cfg(target_os = "macos")]
3365 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3366 #[cfg(feature = "gpu")]
3367 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3368 #[allow(unreachable_patterns)]
3369 _ => false,
3370 }
3371}
3372
3373#[allow(unused_variables, clippy::too_many_arguments)]
3376pub fn vae_conv2d(
3377 w: &[f32],
3378 bias: &[f32],
3379 x: &[f32],
3380 ic: usize,
3381 oc: usize,
3382 h: usize,
3383 w_img: usize,
3384 k: usize,
3385 out: &mut [f32],
3386) -> bool {
3387 match backend() {
3388 #[cfg(target_os = "macos")]
3389 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3390 #[cfg(feature = "gpu")]
3391 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3392 #[allow(unreachable_patterns)]
3393 _ => false,
3394 }
3395}
3396
3397#[allow(unused_variables, clippy::too_many_arguments)]
3401#[allow(unused_variables)]
3405#[allow(clippy::too_many_arguments)]
3406#[allow(clippy::too_many_arguments, unused_variables)]
3409pub fn dit_qkv_attention(
3410 model: &Arc<CmfModel>,
3411 qkv_idx: usize,
3412 xn: &[f32],
3413 n: usize,
3414 hidden: usize,
3415 nh: usize,
3416 hd: usize,
3417 scale: f32,
3418 nr: (&[f32], &[f32], &[f32], f32),
3419 out: &mut [f32],
3420) -> bool {
3421 match backend() {
3422 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3423 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
3424 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
3425 ),
3426 #[allow(unreachable_patterns)]
3427 _ => false,
3428 }
3429}
3430
3431#[allow(clippy::too_many_arguments)]
3434pub fn dit_qkv_attn_out(
3435 model: &Arc<CmfModel>,
3436 qkv_idx: usize,
3437 out_idx: usize,
3438 xn: &[f32],
3439 n: usize,
3440 hidden: usize,
3441 nh: usize,
3442 hd: usize,
3443 scale: f32,
3444 nr: (&[f32], &[f32], &[f32], f32),
3445 proj: &mut [f32],
3446) -> bool {
3447 match backend() {
3448 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3449 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
3450 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
3451 ),
3452 #[allow(unreachable_patterns)]
3453 _ => false,
3454 }
3455}
3456
3457#[allow(clippy::too_many_arguments)]
3459pub fn vae_qkv_attn_out(
3460 model: &Arc<CmfModel>,
3461 qkv_idx: usize,
3462 out_idx: usize,
3463 xn: &[f32],
3464 n: usize,
3465 dim: usize,
3466 nh: usize,
3467 hd: usize,
3468 scale: f32,
3469 angles: &[f32],
3470 eps: f32,
3471 qkv_bias: &[f32],
3472 proj: &mut [f32],
3473) -> bool {
3474 match backend() {
3475 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3476 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
3477 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
3478 ),
3479 #[allow(unreachable_patterns)]
3480 _ => false,
3481 }
3482}
3483
3484#[allow(clippy::too_many_arguments)]
3485pub fn vae_attention_packed(
3486 qkv: &[f32],
3487 nh: usize,
3488 n: usize,
3489 hd: usize,
3490 scale: f32,
3491 angles: &[f32],
3492 eps: f32,
3493 out: &mut [f32],
3494) -> bool {
3495 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
3496}
3497
3498#[allow(clippy::too_many_arguments)]
3499pub fn vae_attention_packed_layout(
3500 qkv: &[f32],
3501 nh: usize,
3502 n: usize,
3503 hd: usize,
3504 scale: f32,
3505 angles: &[f32],
3506 eps: f32,
3507 out: &mut [f32],
3508 layout: u32,
3509) -> bool {
3510 match backend() {
3511 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3512 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
3513 qkv, nh, n, hd, scale, angles, eps, out, layout,
3514 ),
3515 #[allow(unreachable_patterns)]
3516 _ => false,
3517 }
3518}
3519
3520#[allow(clippy::too_many_arguments)]
3521pub fn dit_split_only(
3522 qkv: &[f32],
3523 nh: usize,
3524 n: usize,
3525 hd: usize,
3526 layout: u32,
3527 norm: Option<(&[f32], f32)>,
3528 out_q: &mut [f32],
3529) -> bool {
3530 match backend() {
3531 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3532 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
3533 #[allow(unreachable_patterns)]
3534 _ => false,
3535 }
3536}
3537
3538pub fn gemm_nt_f32_transient(
3546 x: &[f32],
3547 w: &[f32],
3548 y: &mut [f32],
3549 n: usize,
3550 k: usize,
3551 m: usize,
3552) -> bool {
3553 match backend() {
3554 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3555 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
3556 #[allow(unreachable_patterns)]
3557 _ => false,
3558 }
3559}
3560
3561pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
3562 match backend() {
3563 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3564 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
3565 #[allow(unreachable_patterns)]
3566 _ => false,
3567 }
3568}
3569
3570#[allow(clippy::too_many_arguments)]
3573pub fn music3_ffn(
3574 model: &std::sync::Arc<CmfModel>,
3575 idx_in: usize,
3576 idx_out: usize,
3577 h: &[f32],
3578 bias_in: &[f32],
3579 n: usize,
3580 hs: usize,
3581 inter: usize,
3582 out: &mut [f32],
3583) -> bool {
3584 match backend() {
3585 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3586 Backend::Wgpu => {
3587 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
3588 }
3589 #[allow(unreachable_patterns)]
3590 _ => false,
3591 }
3592}
3593
3594#[allow(clippy::too_many_arguments)]
3598pub fn conv1d_gemm(
3599 x: &[f32],
3600 w: &[f32],
3601 ic: usize,
3602 oc: usize,
3603 n: usize,
3604 k: usize,
3605 pad: usize,
3606 dil: usize,
3607 out_n: usize,
3608 yt: &mut [f32],
3609) -> bool {
3610 match backend() {
3611 #[cfg(target_os = "macos")]
3612 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3613 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3614 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3615 #[allow(unreachable_patterns)]
3616 _ => false,
3617 }
3618}
3619
3620#[allow(clippy::too_many_arguments)]
3622pub fn vae_conv2d_coop(
3623 w: &[f32],
3624 bias: Option<&[f32]>,
3625 x: &[f32],
3626 ic: usize,
3627 oc: usize,
3628 h: usize,
3629 wi: usize,
3630 k: usize,
3631 out: &mut [f32],
3632) -> bool {
3633 match backend() {
3634 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3635 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
3636 #[allow(unreachable_patterns)]
3637 _ => false,
3638 }
3639}
3640
3641pub fn dit_attention_packed(
3642 qkv: &[f32],
3643 nh: usize,
3644 n: usize,
3645 hd: usize,
3646 scale: f32,
3647 nr: Option<(&[f32], &[f32], &[f32], f32)>,
3650 out: &mut [f32],
3651) -> bool {
3652 match backend() {
3653 #[cfg(feature = "gpu")]
3660 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
3661 #[allow(unreachable_patterns)]
3662 _ => false,
3663 }
3664}
3665
3666pub fn dit_attention_packed_available() -> bool {
3674 #[allow(unreachable_patterns)]
3675 match backend() {
3676 #[cfg(feature = "gpu")]
3677 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
3678 _ => false,
3679 }
3680}
3681
3682pub fn dit_attention(
3683 qh: &[f32],
3684 kh: &[f32],
3685 vh: &[f32],
3686 nh: usize,
3687 nkv: usize,
3688 n: usize,
3689 hd: usize,
3690 scale: f32,
3691 out: &mut [f32],
3692) -> bool {
3693 match backend() {
3694 #[cfg(target_os = "macos")]
3695 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3696 #[cfg(feature = "gpu")]
3697 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3698 #[allow(unreachable_patterns)]
3699 _ => false,
3700 }
3701}
3702
3703pub fn gram_alloc(cols: usize) -> Option<u64> {
3706 #[cfg(feature = "gpu")]
3707 if backend() == Backend::Wgpu {
3708 return crate::gpu_wgpu::gram::alloc(cols);
3709 }
3710 let _ = cols;
3711 None
3712}
3713
3714pub fn gram_add(id: u64, xs: &[f32], b: usize) -> bool {
3716 #[cfg(feature = "gpu")]
3717 if backend() == Backend::Wgpu {
3718 return crate::gpu_wgpu::gram::add(id, xs, b);
3719 }
3720 let _ = (id, xs, b);
3721 false
3722}
3723
3724#[cfg(feature = "gpu")]
3726pub fn gram_add_dev(id: u64, x: &wgpu::Buffer, b: usize) -> bool {
3727 if backend() == Backend::Wgpu {
3728 return crate::gpu_wgpu::gram::add_dev(id, x, b);
3729 }
3730 false
3731}
3732
3733pub fn gram_take(id: u64) -> Option<Vec<f64>> {
3735 #[cfg(feature = "gpu")]
3736 if backend() == Backend::Wgpu {
3737 return crate::gpu_wgpu::gram::take(id);
3738 }
3739 let _ = id;
3740 None
3741}
3742
3743pub fn gptq_fold_dev(h_id: u64, n: usize, lambda: f64) -> Option<u64> {
3748 #[cfg(feature = "gpu")]
3749 if backend() == Backend::Wgpu {
3750 return crate::gpu_wgpu::gptq_dev::fold(h_id, n, lambda);
3751 }
3752 let _ = (h_id, n, lambda);
3753 None
3754}
3755
3756pub fn gptq_fold_info(id: u64) -> Option<(Vec<bool>, Vec<f32>, f64)> {
3758 #[cfg(feature = "gpu")]
3759 if backend() == Backend::Wgpu {
3760 return crate::gpu_wgpu::gptq_dev::info(id);
3761 }
3762 let _ = id;
3763 None
3764}
3765
3766pub fn gptq_fold_read(id: u64) -> Option<Vec<f32>> {
3768 #[cfg(feature = "gpu")]
3769 if backend() == Backend::Wgpu {
3770 return crate::gpu_wgpu::gptq_dev::read_u(id);
3771 }
3772 let _ = id;
3773 None
3774}
3775
3776pub fn gptq_fold_free(id: u64) {
3778 #[cfg(feature = "gpu")]
3779 if backend() == Backend::Wgpu {
3780 crate::gpu_wgpu::gptq_dev::free(id);
3781 }
3782 let _ = id;
3783}
3784
3785#[allow(clippy::too_many_arguments)]
3787pub fn gptq_sweep_q4tp_dev(
3788 id: u64,
3789 w: &[f32],
3790 rows: usize,
3791 tab: &[f32],
3792 lost: &[f32],
3793 nib: &mut [u8],
3794 codes: &mut [u32],
3795) -> bool {
3796 #[cfg(feature = "gpu")]
3797 if backend() == Backend::Wgpu {
3798 return crate::gpu_wgpu::gptq_dev::sweep_q4tp(id, w, rows, tab, lost, nib, codes);
3799 }
3800 let _ = (id, w, rows, tab, lost, nib, codes);
3801 false
3802}
3803
3804pub fn gptq_sweep_q2tp_dev(
3806 id: u64,
3807 w: &[f32],
3808 rows: usize,
3809 tab: &[f32],
3810 hw: &[f32],
3811 chunks: &mut [u8],
3812 codes: &mut [u32],
3813) -> bool {
3814 #[cfg(feature = "gpu")]
3815 if backend() == Backend::Wgpu {
3816 return crate::gpu_wgpu::gptq_dev::sweep_q2tp(id, w, rows, tab, hw, chunks, codes);
3817 }
3818 let _ = (id, w, rows, tab, hw, chunks, codes);
3819 false
3820}
3821
3822pub fn gram_read(id: u64) -> Option<Vec<f64>> {
3825 #[cfg(feature = "gpu")]
3826 if backend() == Backend::Wgpu {
3827 return crate::gpu_wgpu::gram::read(id);
3828 }
3829 let _ = id;
3830 None
3831}
3832
3833pub fn gram_clear() {
3835 #[cfg(feature = "gpu")]
3836 if backend() == Backend::Wgpu {
3837 crate::gpu_wgpu::gram::clear();
3838 }
3839}
3840
3841#[allow(unused_variables)]
3846pub fn q4tp_matmat(
3847 model: &Arc<CmfModel>,
3848 idx: usize,
3849 xs: &[f32],
3850 b: usize,
3851 rows: usize,
3852 cols: usize,
3853 out: &mut [f32],
3854) -> bool {
3855 match backend() {
3856 #[cfg(target_os = "macos")]
3857 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3858 #[cfg(feature = "gpu")]
3859 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3860 #[allow(unreachable_patterns)]
3861 _ => false,
3862 }
3863}
3864
3865pub fn q2tp_matmat(
3868 model: &Arc<CmfModel>,
3869 idx: usize,
3870 xs: &[f32],
3871 b: usize,
3872 rows: usize,
3873 cols: usize,
3874 out: &mut [f32],
3875) -> bool {
3876 match backend() {
3877 #[cfg(target_os = "macos")]
3878 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3879 #[cfg(feature = "gpu")]
3880 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3881 #[allow(unreachable_patterns)]
3882 _ => false,
3883 }
3884}
3885
3886pub fn q2tp_affine_matmat(
3889 model: &Arc<CmfModel>,
3890 idx: usize,
3891 xs: &[f32],
3892 b: usize,
3893 rows: usize,
3894 cols: usize,
3895 out: &mut [f32],
3896) -> bool {
3897 match backend() {
3898 #[cfg(target_os = "macos")]
3899 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3900 #[cfg(feature = "gpu")]
3901 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3902 #[allow(unreachable_patterns)]
3903 _ => false,
3904 }
3905}
3906
3907pub fn blr_matmat(t: &crate::blr::BlrTensor, xs: &[f32], b: usize, out: &mut [f32]) -> bool {
3912 match backend() {
3913 #[cfg(feature = "gpu")]
3914 Backend::Wgpu => crate::gpu_wgpu::blr_matmat(t, xs, b, out),
3915 #[allow(unreachable_patterns)]
3916 _ => {
3917 let _ = (t, xs, b, out);
3918 false
3919 }
3920 }
3921}
3922
3923pub fn mxfp4_matvec(
3927 model: &Arc<CmfModel>,
3928 idx: usize,
3929 xs: &[f32],
3930 rows: usize,
3931 cols: usize,
3932 out: &mut [f32],
3933) -> bool {
3934 mxfp4_matmat(model, idx, xs, 1, rows, cols, out)
3935}
3936
3937pub fn mxfp4_matmat(
3940 model: &Arc<CmfModel>,
3941 idx: usize,
3942 xs: &[f32],
3943 b: usize,
3944 rows: usize,
3945 cols: usize,
3946 out: &mut [f32],
3947) -> bool {
3948 match backend() {
3949 #[cfg(feature = "gpu")]
3950 Backend::Wgpu => crate::gpu_wgpu::mxfp4::matmat(model, idx, xs, b, rows, cols, out),
3951 #[allow(unreachable_patterns)]
3952 _ => {
3953 let _ = (model, idx, xs, b, rows, cols, out);
3954 false
3955 }
3956 }
3957}
3958
3959pub fn q2tp_matvec(
3961 model: &Arc<CmfModel>,
3962 idx: usize,
3963 xs: &[f32],
3964 rows: usize,
3965 cols: usize,
3966 out: &mut [f32],
3967) -> bool {
3968 match backend() {
3969 #[cfg(target_os = "macos")]
3970 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
3971 #[cfg(feature = "gpu")]
3972 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
3973 #[allow(unreachable_patterns)]
3974 _ => false,
3975 }
3976}
3977
3978pub fn q2tp_affine_matvec(
3982 model: &Arc<CmfModel>,
3983 idx: usize,
3984 xs: &[f32],
3985 rows: usize,
3986 cols: usize,
3987 out: &mut [f32],
3988) -> bool {
3989 match backend() {
3990 #[cfg(target_os = "macos")]
3991 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3992 #[cfg(feature = "gpu")]
3993 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3994 #[allow(unreachable_patterns)]
3995 _ => false,
3996 }
3997}
3998
3999pub fn q4tp_matvec(
4004 model: &Arc<CmfModel>,
4005 idx: usize,
4006 xs: &[f32],
4007 rows: usize,
4008 cols: usize,
4009 out: &mut [f32],
4010) -> bool {
4011 match backend() {
4012 #[cfg(target_os = "macos")]
4013 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
4014 #[cfg(feature = "gpu")]
4015 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
4016 #[allow(unreachable_patterns)]
4017 _ => false,
4018 }
4019}
4020
4021pub fn q4t_matvec(
4027 model: &Arc<CmfModel>,
4028 idx: usize,
4029 xs: &[f32],
4030 rows: usize,
4031 cols: usize,
4032 out: &mut [f32],
4033) -> bool {
4034 match backend() {
4035 #[cfg(target_os = "macos")]
4036 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
4037 #[allow(unreachable_patterns)]
4038 _ => false,
4039 }
4040}
4041
4042pub fn q4t_matmat(
4043 model: &Arc<CmfModel>,
4044 idx: usize,
4045 xs: &[f32],
4046 b: usize,
4047 rows: usize,
4048 cols: usize,
4049 out: &mut [f32],
4050) -> bool {
4051 match backend() {
4052 #[cfg(target_os = "macos")]
4053 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
4054 #[cfg(feature = "gpu")]
4055 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
4056 #[allow(unreachable_patterns)]
4057 _ => false,
4058 }
4059}
4060
4061#[cfg(target_os = "macos")]
4063pub use crate::gpu_metal::{
4064 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
4065 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
4066};
4067
4068#[cfg(target_os = "macos")]
4070pub fn gdn_block(
4071 model: &Arc<CmfModel>,
4072 layers: &[GdnGpuLayer],
4073 states: &mut [&mut [f32]],
4074 cfg: &GdnGpuCfg,
4075 h: &mut [f32],
4076) -> bool {
4077 match backend() {
4078 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
4079 _ => false,
4080 }
4081}
4082
4083#[allow(unused_variables)]
4085pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
4086 match backend() {
4087 #[cfg(target_os = "macos")]
4090 Backend::Metal if jobs.iter().any(|j| j.act != 0) => false,
4091 #[cfg(target_os = "macos")]
4092 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
4093 #[cfg(feature = "gpu")]
4094 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
4095 Backend::None => false,
4096 }
4097}
4098
4099#[allow(unused_variables)]
4101pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
4102 match backend() {
4103 #[cfg(target_os = "macos")]
4104 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
4105 #[cfg(feature = "gpu")]
4106 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
4107 Backend::None => false,
4108 }
4109}
4110
4111static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
4127static 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)];
4131
4132const GRAPH_RACE_SAMPLES: u32 = 4;
4134
4135pub fn graph_race_begin_generation() {
4154 #[cfg(feature = "gpu")]
4159 {
4160 static FLUSHED: std::sync::Once = std::sync::Once::new();
4172 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
4173 if FIRST.swap(false, Ordering::Relaxed) {
4174 } else {
4176 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
4177 }
4178 }
4179 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
4180 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4181 return;
4182 }
4183 let (gn, cn) = (
4184 GRAPH_N[1].load(Ordering::Relaxed),
4185 GRAPH_N[0].load(Ordering::Relaxed),
4186 );
4187 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
4188 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
4189 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4190 let verdict = if g_avg < c_avg { 1 } else { 2 };
4191 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
4192 tracing::info!(
4193 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
4194 g_avg as f64 / 1e6,
4195 c_avg as f64 / 1e6,
4196 if verdict == 1 { "graph" } else { "normal path" }
4197 );
4198 return;
4199 }
4200 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
4201 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
4202}
4203
4204pub fn graph_race_use_graph(trusted: bool) -> bool {
4208 if trusted {
4209 return true;
4210 }
4211 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
4212 1 => true,
4213 2 => false,
4214 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
4215 }
4216}
4217
4218pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
4223 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4224 return false;
4225 }
4226 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
4227 let cn = GRAPH_N[0].load(Ordering::Relaxed);
4228 if !first || cn == 0 {
4229 return false;
4230 }
4231 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4232 let ns = dur.as_nanos() as u64;
4233 if ns > 1_000_000_000 && ns > 4 * c_avg {
4234 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
4235 tracing::info!(
4236 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
4237 ns as f64 / 1e6,
4238 c_avg as f64 / 1e6
4239 );
4240 return true;
4241 }
4242 false
4243}
4244
4245pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
4249 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4250 return;
4251 }
4252 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
4253 if tok == 0 {
4254 return;
4255 }
4256 let i = used_graph as usize;
4257 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
4258 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
4259}
4260
4261pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
4271 #[inline]
4272 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
4273 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
4274 for c in chunks.chunks_exact(8) {
4275 h ^= u64::from_le_bytes(c.try_into().unwrap());
4276 h = h.wrapping_mul(0x100_0000_01b3);
4277 }
4278 for &b in tail {
4279 h ^= b as u64;
4280 h = h.wrapping_mul(0x100_0000_01b3);
4281 }
4282 h
4283 }
4284 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
4285 if data.len() <= 4096 {
4286 return fnv(h, data);
4287 }
4288 let step = (data.len() - 64) / 63;
4289 for i in 0..64 {
4290 h = fnv(h, &data[i * step..i * step + 64]);
4291 }
4292 h
4293}
4294
4295pub(crate) fn fp_f32(data: &[f32]) -> u64 {
4298 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
4299 fp_bytes(bytes)
4300}
4301
4302#[cfg(test)]
4303mod fp_tests {
4304 use super::fp_bytes;
4305
4306 #[test]
4311 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
4312 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
4314 let h0 = fp_bytes(&base);
4315 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
4316 let mut dense = base.clone();
4319 for b in dense.iter_mut() {
4320 *b = b.wrapping_add(1);
4321 }
4322 assert_ne!(
4323 h0,
4324 fp_bytes(&dense),
4325 "a fully different tensor slipped through"
4326 );
4327 assert_ne!(h0, fp_bytes(&base[..n - 64]));
4330 let mut small = vec![3u8; 4096];
4333 let hs = fp_bytes(&small);
4334 small[2048] ^= 1;
4335 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
4336 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
4338 let v = vec![9u8; n];
4339 let _ = fp_bytes(&v); }
4341 }
4342}
4343
4344pub fn bake_release() {
4348 #[cfg(feature = "gpu")]
4349 crate::gpu_wgpu::bake_release();
4350}
4351
4352pub fn bake_precision_strict(on: bool) {
4356 #[cfg(feature = "gpu")]
4357 crate::gpu_wgpu::bake_precision_strict(on);
4358 #[cfg(not(feature = "gpu"))]
4359 let _ = on;
4360}
4361
4362pub fn hostprof_encode_done(t0: std::time::Instant) {
4368 use std::sync::atomic::{AtomicU64, Ordering};
4369 static ENC: AtomicU64 = AtomicU64::new(0);
4370 static N: AtomicU64 = AtomicU64::new(0);
4371 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4372 return;
4373 }
4374 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4375 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4376 if n % 100 == 0 {
4377 eprintln!(
4378 "hostprof: encode {:.2} ms/token over {n} tokens",
4379 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4380 );
4381 }
4382}
4383
4384pub fn hostprof_total(t0: std::time::Instant) {
4385 use std::sync::atomic::{AtomicU64, Ordering};
4386 static TOT: AtomicU64 = AtomicU64::new(0);
4387 static N: AtomicU64 = AtomicU64::new(0);
4388 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4389 return;
4390 }
4391 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4392 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4393 if n % 100 == 0 {
4394 eprintln!(
4395 "hostprof: total {:.2} ms/token over {n} tokens",
4396 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4397 );
4398 }
4399}
4400
4401pub fn stageprof(stage: u32, dt: std::time::Duration) {
4405 use std::sync::atomic::{AtomicU64, Ordering};
4406 static NS: [AtomicU64; 4] = [
4407 AtomicU64::new(0),
4408 AtomicU64::new(0),
4409 AtomicU64::new(0),
4410 AtomicU64::new(0),
4411 ];
4412 static N: AtomicU64 = AtomicU64::new(0);
4413 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4414 return;
4415 }
4416 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
4417 if stage == 1 {
4418 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4419 if n % 200 == 0 {
4420 eprintln!(
4421 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
4422 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4423 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4424 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4425 );
4426 }
4427 }
4428}
4429
4430pub fn weight_bytes_dispatched() -> u64 {
4433 let mut total = 0u64;
4434 #[cfg(target_os = "macos")]
4435 {
4436 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4437 }
4438 #[cfg(feature = "gpu")]
4439 {
4440 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4441 }
4442 total
4443}
4444
4445pub fn weight_bytes_by() -> [u64; 6] {
4448 #[cfg(target_os = "macos")]
4449 {
4450 let mut o = [0u64; 6];
4451 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
4452 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
4453 }
4454 return o;
4455 }
4456 #[allow(unreachable_code)]
4457 [0; 6]
4458}
4459
4460#[cfg(test)]
4461mod f16_guard_tests {
4462 use super::*;
4463
4464 #[test]
4467 fn abs_max_or_inf_is_the_magnitude_maximum() {
4468 let mut xs: Vec<f32> = (0..37).map(|i| (i as f32 - 18.0) * 0.5).collect();
4469 assert_eq!(abs_max_or_inf(&xs), 9.0);
4470 xs[36] = -70000.0; assert_eq!(abs_max_or_inf(&xs), 70000.0);
4472 xs[3] = f32::NAN; assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4474 xs[3] = 0.0;
4475 xs[35] = f32::NEG_INFINITY;
4476 assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4477 assert_eq!(abs_max_or_inf(&[]), 0.0);
4478 assert_eq!(abs_max_or_inf(&[-0.0, -1e-30]), 1e-30);
4479 }
4480}
4481
4482#[cfg(test)]
4483mod probe_warmup_tests {
4484 use super::*;
4485 use std::time::Duration;
4486
4487 fn ms(v: f64) -> Duration {
4488 Duration::from_nanos((v * 1e6) as u64)
4489 }
4490
4491 #[test]
4496 fn one_cold_first_sample_does_not_lose_the_class() {
4497 let p = Probe::new();
4498 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
4500 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
4501 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
4502 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
4503 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
4504 assert_eq!(
4505 p.state.load(Ordering::Relaxed),
4506 1,
4507 "the device is 3x faster once warm and must win"
4508 );
4509 }
4510
4511 #[test]
4515 fn the_warmup_is_spent_once_and_never_underflows() {
4516 let p = Probe::new();
4517 for _ in 0..8 {
4518 probe_record_into(&p, "matmat", None, true, ms(10.0));
4519 }
4520 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
4521 assert_eq!(
4522 p.gpu_n.load(Ordering::Relaxed),
4523 7,
4524 "one sample burned, the rest counted"
4525 );
4526 }
4527
4528 #[test]
4534 fn a_class_whose_device_always_declines_settles_on_the_host() {
4535 let _probe_guard = probe_test_guard();
4536 let c = OpClass::MatmatWide;
4540 let p = &PROBES[c as usize];
4541 p.state.store(0, Ordering::Relaxed);
4542 p.declines.store(0, Ordering::Relaxed);
4543 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
4544 probe_note_decline(c);
4545 }
4546 assert_eq!(
4547 p.state.load(Ordering::Relaxed),
4548 0,
4549 "one short of the limit is still a question, not an answer"
4550 );
4551 probe_note_decline(c);
4552 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
4553 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
4554 p.state.store(0, Ordering::Relaxed);
4555 p.declines.store(0, Ordering::Relaxed);
4556 }
4557
4558 #[test]
4561 fn a_slow_device_still_loses_after_the_warmup() {
4562 let p = Probe::new();
4563 for _ in 0..4 {
4564 probe_record_into(&p, "matvec", None, true, ms(40.0));
4565 }
4566 for _ in 0..4 {
4567 probe_record_into(&p, "matvec", None, false, ms(2.0));
4568 }
4569 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
4570 }
4571}
4572
4573pub(crate) struct ImageStageGuard {
4576 #[cfg(target_os = "macos")]
4577 metal: Option<crate::gpu_metal::ImageStageGuard>,
4578 #[cfg(feature = "gpu")]
4579 wgpu: crate::gpu_wgpu::ImageStageGuard,
4580}
4581
4582pub(crate) fn image_stage_scope() -> ImageStageGuard {
4583 ImageStageGuard {
4584 #[cfg(target_os = "macos")]
4585 metal: if matches!(backend(), Backend::Metal) {
4586 Some(crate::gpu_metal::image_stage_scope())
4587 } else {
4588 None
4589 },
4590 #[cfg(feature = "gpu")]
4591 wgpu: crate::gpu_wgpu::image_stage_scope(),
4592 }
4593}
4594
4595impl ImageStageGuard {
4596 pub(crate) fn track_model(&mut self, uid: u64) {
4597 #[cfg(target_os = "macos")]
4598 if let Some(metal) = &mut self.metal {
4599 metal.track_model(uid);
4600 }
4601 #[cfg(feature = "gpu")]
4602 self.wgpu.track_model(uid);
4603 #[cfg(not(target_os = "macos"))]
4604 let _ = uid;
4605 }
4606}
4607
4608#[derive(Clone, Copy, Debug, PartialEq)]
4628pub struct Qi21Geom {
4629 pub hidden: usize,
4630 pub nh: usize,
4631 pub hd: usize,
4632 pub inter: usize,
4633 pub in_ch: usize,
4634 pub eps: f32,
4635}
4636
4637pub struct Qi21BlockRef<'a> {
4640 pub w: [usize; 7],
4641 pub norm_q: &'a [f32],
4642 pub norm_k: &'a [f32],
4643}
4644
4645pub struct Qi21PrefillArgs<'a> {
4647 pub model: &'a Arc<CmfModel>,
4648 pub geom: Qi21Geom,
4649 pub blocks: &'a [Qi21BlockRef<'a>],
4650 pub img_in: &'a [f32],
4652 pub proj_out: &'a [f32],
4653 pub key: u64,
4654 pub x: &'a [f32],
4656 pub lp: usize,
4657 pub rope_p: (&'a [f32], &'a [f32]),
4659 pub rope_t: (&'a [f32], &'a [f32]),
4660 pub vis: &'a [u32],
4662 pub mods0: &'a [f32],
4664 pub n: usize,
4666}
4667
4668#[allow(unused_variables)]
4670pub fn qi21_prefill(a: &Qi21PrefillArgs) -> bool {
4671 match backend() {
4672 #[cfg(target_os = "macos")]
4673 Backend::Metal => crate::gpu_metal::qi21::prefill(a),
4674 #[cfg(feature = "gpu")]
4675 Backend::Wgpu => crate::gpu_wgpu::qi21::prefill(a),
4676 #[allow(unreachable_patterns)]
4677 _ => false,
4678 }
4679}
4680
4681#[allow(unused_variables)]
4683pub fn qi21_step(key: u64, xtok: &[f32], mods: &[f32], fs: &[f32], out: &mut [f32]) -> bool {
4684 match backend() {
4685 #[cfg(target_os = "macos")]
4686 Backend::Metal => crate::gpu_metal::qi21::step(key, xtok, mods, fs, out),
4687 #[cfg(feature = "gpu")]
4688 Backend::Wgpu => crate::gpu_wgpu::qi21::step(key, xtok, mods, fs, out),
4689 #[allow(unreachable_patterns)]
4690 _ => false,
4691 }
4692}
4693
4694#[allow(unused_variables)]
4697pub fn qi21_release_key(key: u64) {
4698 #[cfg(target_os = "macos")]
4699 if matches!(backend(), Backend::Metal) {
4700 crate::gpu_metal::qi21::release_key(key);
4701 }
4702 #[cfg(feature = "gpu")]
4703 crate::gpu_wgpu::qi21::release_key(key);
4704}
4705
4706pub fn qi21_release() {
4709 #[cfg(target_os = "macos")]
4710 if matches!(backend(), Backend::Metal) {
4711 crate::gpu_metal::qi21::release();
4712 }
4713 #[cfg(feature = "gpu")]
4714 {
4715 crate::gpu_wgpu::qi21::release();
4716 crate::gpu_wgpu::qi21_vae::release();
4717 }
4718}
4719
4720#[derive(Clone, Copy)]
4723pub struct Qi21VaeConvRef<'a> {
4724 pub w: &'a [f32],
4725 pub b: &'a [f32],
4726 pub ci: usize,
4727 pub co: usize,
4728 pub k: usize,
4729}
4730
4731pub struct Qi21VaeResRef<'a> {
4734 pub g1: &'a [f32],
4735 pub c1: Qi21VaeConvRef<'a>,
4736 pub g2: &'a [f32],
4737 pub c2: Qi21VaeConvRef<'a>,
4738 pub shortcut: Option<Qi21VaeConvRef<'a>>,
4739}
4740
4741pub struct Qi21VaeUpRef<'a> {
4745 pub resnets: Vec<Qi21VaeResRef<'a>>,
4746 pub up: Option<(Qi21VaeConvRef<'a>, usize)>,
4747 pub in_dim: usize,
4748 pub out_dim: usize,
4749}
4750
4751pub struct Qi21VaeDecodeArgs<'a> {
4754 pub key: u64,
4756 pub post_quant: Qi21VaeConvRef<'a>,
4757 pub conv_in: Qi21VaeConvRef<'a>,
4758 pub mid_res: [Qi21VaeResRef<'a>; 2],
4759 pub attn_gamma: &'a [f32],
4762 pub attn_qkv: Qi21VaeConvRef<'a>,
4763 pub attn_proj: Qi21VaeConvRef<'a>,
4764 pub ups: Vec<Qi21VaeUpRef<'a>>,
4765 pub norm_out: &'a [f32],
4766 pub conv_out: Qi21VaeConvRef<'a>,
4767}
4768
4769#[allow(unused_variables)]
4773pub fn qi21_vae_decode(a: &Qi21VaeDecodeArgs, z: &[f32], h: usize, w: usize, out: &mut [f32]) -> bool {
4774 match backend() {
4775 #[cfg(feature = "gpu")]
4776 Backend::Wgpu => crate::gpu_wgpu::qi21_vae::decode(a, z, h, w, out),
4777 #[allow(unreachable_patterns)]
4778 _ => false,
4779 }
4780}
4781
4782#[derive(Clone, Copy)]
4790pub struct ZBlockRef<'a> {
4791 pub wq: usize,
4793 pub wk: usize,
4794 pub wv: usize,
4795 pub wo: usize,
4796 pub w1: usize,
4799 pub w3: usize,
4800 pub w2: usize,
4801 pub norm1: &'a [f32],
4803 pub norm2: &'a [f32],
4804 pub ffn_norm1: &'a [f32],
4806 pub ffn_norm2: &'a [f32],
4807 pub norm_q: &'a [f32],
4809 pub norm_k: &'a [f32],
4810}
4811
4812#[derive(Clone, Copy, Debug, PartialEq)]
4816pub struct ZGeom {
4817 pub hidden: usize,
4818 pub nh: usize,
4819 pub hd: usize,
4820 pub inter: usize,
4821 pub eps: f32,
4822 pub final_eps: f32,
4823 pub patch_dim: usize,
4824}
4825
4826pub struct ZPrepareArgs<'a> {
4830 pub model: &'a Arc<CmfModel>,
4831 pub geom: ZGeom,
4832 pub key: u64,
4835 pub n_img: usize,
4838 pub n_img_p: usize,
4839 pub n_cap_p: usize,
4840 pub grid: (usize, usize),
4843 pub cap: &'a [f32],
4845 pub rope_img: (&'a [f32], &'a [f32]),
4848 pub rope_joint: (&'a [f32], &'a [f32]),
4850 pub x_emb_w: &'a [f32],
4853 pub x_emb_b: &'a [f32],
4854 pub x_pad: &'a [f32],
4855 pub final_w: &'a [f32],
4857 pub final_b: &'a [f32],
4858 pub noise_refiner: &'a [ZBlockRef<'a>],
4860 pub layers: &'a [ZBlockRef<'a>],
4861 pub mods_all: Option<&'a [f32]>,
4867 pub final_scale_all: Option<&'a [f32]>,
4868 pub neg: Option<ZNegArgs<'a>>,
4874}
4875
4876pub struct ZNegArgs<'a> {
4880 pub cap: &'a [f32],
4882 pub n_cap_p: usize,
4883 pub rope_img: (&'a [f32], &'a [f32]),
4885 pub rope_joint: (&'a [f32], &'a [f32]),
4887}
4888
4889pub struct ZStepArgs<'a> {
4891 pub key: u64,
4894 pub step: usize,
4897 pub x_tok: &'a [f32],
4901 pub mods: &'a [f32],
4905 pub final_scale: &'a [f32],
4907 pub out: &'a mut [f32],
4910 pub out_neg: Option<&'a mut [f32]>,
4913}
4914
4915#[allow(unused_variables)]
4919pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
4920 match backend() {
4921 #[cfg(target_os = "macos")]
4922 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
4923 #[cfg(feature = "gpu")]
4924 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
4925 #[allow(unreachable_patterns)]
4926 _ => false,
4927 }
4928}
4929
4930#[allow(unused_variables)]
4934pub fn zimage_step(a: &mut ZStepArgs) -> bool {
4935 match backend() {
4936 #[cfg(target_os = "macos")]
4937 Backend::Metal => crate::gpu_metal::zimage::step(a),
4938 #[cfg(feature = "gpu")]
4939 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
4940 #[allow(unreachable_patterns)]
4941 _ => false,
4942 }
4943}
4944
4945#[allow(unused_variables)]
4950pub fn zimage_preload(
4951 model: &Arc<CmfModel>,
4952 geom: &ZGeom,
4953 noise_refiner: &[ZBlockRef],
4954 layers: &[ZBlockRef],
4955 context_refiner: &[ZBlockRef],
4956) -> bool {
4957 match backend() {
4958 #[cfg(target_os = "macos")]
4959 Backend::Metal => {
4960 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
4961 }
4962 #[cfg(feature = "gpu")]
4963 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
4964 #[allow(unreachable_patterns)]
4965 _ => false,
4966 }
4967}
4968
4969pub fn zimage_flush_pipelines() {
4973 #[cfg(feature = "gpu")]
4974 if matches!(backend(), Backend::Wgpu) {
4975 crate::gpu_wgpu::pipeline_cache_flush();
4976 }
4977}
4978
4979pub fn zimage_warmup() -> bool {
4984 match backend() {
4985 #[cfg(target_os = "macos")]
4986 Backend::Metal => crate::gpu_metal::zimage::warmup(),
4987 #[cfg(feature = "gpu")]
4988 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
4989 #[allow(unreachable_patterns)]
4990 _ => false,
4991 }
4992}
4993
4994#[allow(unused_variables)]
4998pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
4999 match backend() {
5000 #[cfg(target_os = "macos")]
5001 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
5002 #[cfg(feature = "gpu")]
5003 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
5004 #[allow(unreachable_patterns)]
5005 _ => false,
5006 }
5007}
5008
5009pub fn zimage_release_dit() {
5012 #[cfg(target_os = "macos")]
5013 crate::gpu_metal::zimage::release_dit();
5014 #[cfg(feature = "gpu")]
5015 crate::gpu_wgpu::zimage::release_dit();
5016}
5017
5018pub fn zimage_release() {
5023 #[cfg(target_os = "macos")]
5024 crate::gpu_metal::zimage::release();
5025 #[cfg(feature = "gpu")]
5026 crate::gpu_wgpu::zimage::release();
5027}
5028
5029#[allow(unused_variables)]
5035pub fn zimage_refine_caption(
5036 model: &Arc<CmfModel>,
5037 geom: &ZGeom,
5038 blocks: &[ZBlockRef],
5039 rope_cap: (&[f32], &[f32]),
5040 cap: &mut [f32],
5041) -> bool {
5042 match backend() {
5043 #[cfg(target_os = "macos")]
5044 Backend::Metal => {
5045 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
5046 }
5047 #[cfg(feature = "gpu")]
5048 Backend::Wgpu => {
5049 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
5050 }
5051 #[allow(unreachable_patterns)]
5052 _ => false,
5053 }
5054}
5055
5056#[allow(unused_variables)]
5062pub fn vae_decode_chain(
5063 a: &crate::vae::VaeChainArgs,
5064 z: &[f32],
5065 h: usize,
5066 w: usize,
5067 out: &mut [f32],
5068) -> bool {
5069 match backend() {
5070 #[cfg(target_os = "macos")]
5071 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
5072 #[cfg(feature = "gpu")]
5073 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
5074 #[allow(unreachable_patterns)]
5075 _ => false,
5076 }
5077}