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: AtomicBool = AtomicBool::new(false);
1169
1170pub fn pause_gpu() -> GpuPause {
1172 GPU_PAUSED.store(true, Ordering::Relaxed);
1173 GpuPause(())
1174}
1175
1176pub struct GpuPause(());
1177
1178impl Drop for GpuPause {
1179 fn drop(&mut self) {
1180 GPU_PAUSED.store(false, Ordering::Relaxed);
1181 }
1182}
1183
1184pub fn enabled() -> bool {
1185 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
1186}
1187
1188pub fn wgpu_active() -> bool {
1202 #[cfg(feature = "gpu")]
1203 {
1204 matches!(backend(), Backend::Wgpu)
1205 }
1206 #[cfg(not(feature = "gpu"))]
1207 {
1208 false
1209 }
1210}
1211
1212pub fn default_device() -> usize {
1219 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
1220 *D.get_or_init(|| {
1221 std::env::var("CMF_GPU_ADAPTER")
1222 .ok()
1223 .and_then(|v| v.trim().parse::<usize>().ok())
1224 .unwrap_or(0)
1225 })
1226}
1227
1228thread_local! {
1229 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
1230}
1231
1232pub fn current_device() -> usize {
1234 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
1235}
1236
1237pub fn set_current_device(i: usize) {
1241 CUR_DEV.with(|c| c.set(Some(i)));
1242}
1243
1244pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
1246 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
1247 let r = f();
1248 CUR_DEV.with(|c| c.set(prev));
1249 r
1250}
1251
1252pub fn device_count() -> usize {
1255 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1256 {
1257 return crate::gpu_wgpu::adapter_count();
1258 }
1259 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1260 {
1261 usize::from(backend_available())
1262 }
1263}
1264
1265pub fn vram_budget() -> u64 {
1269 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1270 {
1271 return crate::gpu_wgpu::device_vram_budget();
1272 }
1273 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
1274 {
1275 if backend_available() { u64::MAX } else { 0 }
1276 }
1277}
1278
1279pub fn resident_bytes() -> u64 {
1283 #[cfg(feature = "gpu")]
1284 {
1285 if backend() == Backend::Wgpu {
1286 return crate::gpu_wgpu::resident_bytes();
1287 }
1288 }
1289 0
1290}
1291
1292pub fn o1_device_stats(kv_id: u64) -> (usize, u64) {
1296 #[cfg(feature = "gpu")]
1297 {
1298 if backend() == Backend::Wgpu {
1299 return crate::gpu_wgpu::o1_device_stats(kv_id);
1300 }
1301 }
1302 let _ = kv_id;
1303 (0, 0)
1304}
1305
1306pub fn upload_bytes() -> u64 {
1310 #[cfg(feature = "gpu")]
1311 {
1312 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
1313 }
1314 #[cfg(not(feature = "gpu"))]
1315 0
1316}
1317
1318pub fn upload_bandwidth_probe(block: usize, rounds: usize) -> Option<f64> {
1322 #[cfg(feature = "gpu")]
1323 {
1324 return crate::gpu_wgpu::upload_bandwidth_probe(block, rounds);
1325 }
1326 let _ = (block, rounds);
1327 None
1328}
1329
1330#[derive(Clone, Copy, PartialEq, Eq, Debug)]
1342pub enum GraphPhase {
1343 Prefill,
1344 Decode,
1345}
1346
1347pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
1355 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
1356 Some("0") => false,
1357 Some("prefill") => phase == GraphPhase::Prefill,
1358 Some(_) => true,
1359 None => {
1360 if wgpu_graph_default() {
1361 return true;
1362 }
1363 let _ = phase;
1368 false
1369 }
1370 }
1371}
1372
1373pub fn wgpu_graph_default() -> bool {
1374 #[cfg(feature = "gpu")]
1375 {
1376 matches!(backend(), Backend::Wgpu)
1382 && (crate::gpu_wgpu::discrete_active()
1383 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1384 }
1385 #[cfg(not(feature = "gpu"))]
1386 {
1387 false
1388 }
1389}
1390
1391#[allow(clippy::too_many_arguments, unused_variables)]
1393pub fn q8_matvec_range(
1394 model: &Arc<CmfModel>,
1395 idx: usize,
1396 row0: usize,
1397 row_scale: &[f32],
1398 xs: &[f32],
1399 rows: usize,
1400 cols: usize,
1401 out: &mut [f32],
1402) -> bool {
1403 match backend() {
1404 #[cfg(target_os = "macos")]
1405 Backend::Metal => {
1406 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1407 }
1408 #[cfg(feature = "gpu")]
1409 Backend::Wgpu => {
1410 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1411 }
1412 Backend::None => false,
1413 }
1414}
1415
1416pub(crate) fn q82_short_rows(
1418 model: &Arc<CmfModel>,
1419 idx: usize,
1420 xs: &[f32],
1421 b: usize,
1422 rows: usize,
1423 cols: usize,
1424 out: &mut [f32],
1425) -> bool {
1426 #[cfg(feature = "gpu")]
1427 if enabled_here() && backend() == Backend::Wgpu {
1428 return crate::gpu_wgpu::q82_short_rows(model, idx, xs, b, rows, cols, out);
1429 }
1430 let _ = (model, idx, xs, b, rows, cols, out);
1431 false
1432}
1433
1434#[allow(clippy::too_many_arguments, unused_variables)]
1437#[allow(clippy::too_many_arguments)]
1441pub fn q8_matmat_2f(
1442 model: &Arc<CmfModel>,
1443 idx: usize,
1444 row_scale: &[f32],
1445 col_field: &[f32],
1446 xs: &[f32],
1447 b: usize,
1448 rows: usize,
1449 cols: usize,
1450 out: &mut [f32],
1451) -> bool {
1452 #[allow(unreachable_patterns)]
1453 match backend() {
1454 #[cfg(feature = "gpu")]
1455 Backend::Wgpu => {
1456 crate::gpu_wgpu::q8_matmat_2f(model, idx, row_scale, col_field, xs, b, rows, cols, out)
1457 }
1458 _ => false,
1459 }
1460}
1461
1462pub fn q8_matmat(
1463 model: &Arc<CmfModel>,
1464 idx: usize,
1465 row_scale: &[f32],
1466 pre: &[f32],
1467 b: usize,
1468 rows: usize,
1469 cols: usize,
1470 out: &mut [f32],
1471) -> bool {
1472 match backend() {
1473 #[cfg(target_os = "macos")]
1474 Backend::Metal => {
1475 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1476 }
1477 #[cfg(feature = "gpu")]
1478 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1479 Backend::None => false,
1480 }
1481}
1482
1483#[allow(unused_variables)]
1486pub fn q1_matvec(
1487 model: &Arc<CmfModel>,
1488 idx: usize,
1489 xs: &[f32],
1490 rows: usize,
1491 cols: usize,
1492 out: &mut [f32],
1493) -> bool {
1494 match backend() {
1495 #[cfg(target_os = "macos")]
1496 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1497 #[cfg(feature = "gpu")]
1498 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1499 Backend::None => false,
1500 }
1501}
1502
1503#[allow(unused_variables)]
1507pub fn q1g_matvec(
1508 model: &Arc<CmfModel>,
1509 idx: usize,
1510 xs: &[f32],
1511 rows: usize,
1512 cols: usize,
1513 out: &mut [f32],
1514) -> bool {
1515 match backend() {
1516 #[cfg(target_os = "macos")]
1517 Backend::Metal => crate::gpu_metal::q1g_matvec(model, idx, xs, rows, cols, out),
1518 #[cfg(feature = "gpu")]
1519 Backend::Wgpu => crate::gpu_wgpu::q1g_matvec(model, idx, xs, rows, cols, out),
1520 Backend::None => false,
1521 }
1522}
1523
1524#[allow(clippy::too_many_arguments)]
1528pub fn attn_dropin(
1529 model: &Arc<CmfModel>,
1530 kv_id: u64,
1531 layer: usize,
1532 normed: &[f32],
1533 wq_idx: usize,
1534 wk_idx: usize,
1535 wv_idx: usize,
1536 wo_idx: usize,
1537 q_norm: Option<&[f32]>,
1538 k_norm: Option<&[f32]>,
1539 late_qk_norm: bool,
1540 invf: &[f32],
1541 nh: usize,
1542 nkv: usize,
1543 hd: usize,
1544 rd: usize,
1545 hidden: usize,
1546 pos: usize,
1547 cap: usize,
1548 gemma: bool,
1549 eps: f32,
1550 cpu_k: &[Vec<f32>],
1551 cpu_v: &[Vec<f32>],
1552 out: &mut [f32],
1553) -> bool {
1554 match backend() {
1555 #[cfg(feature = "gpu")]
1556 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1557 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm,
1558 late_qk_norm, invf, nh, nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1559 ),
1560 #[allow(unused_variables)]
1561 _ => false,
1562 }
1563}
1564
1565#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1569pub enum GraphPrismOp {
1570 None,
1571 Forward,
1572 InverseEmbedding,
1573}
1574
1575pub struct GraphW<'a> {
1579 pub idx: usize,
1580 pub kind: u8,
1581 pub row_scale: &'a [f32],
1582 pub data: &'a [f32],
1583 pub prism: GraphPrismOp,
1584 pub affine: bool,
1585}
1586
1587pub enum GraphAttn<'a> {
1590 Full {
1591 wq: GraphW<'a>,
1592 wk: GraphW<'a>,
1593 wv: GraphW<'a>,
1594 wo: GraphW<'a>,
1595 q_norm: Option<&'a [f32]>,
1596 k_norm: Option<&'a [f32]>,
1597 late_qk_norm: bool,
1599 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1601 output_gate: bool,
1604 cpu_k: &'a [Vec<f32>],
1605 cpu_v: &'a [Vec<f32>],
1606 cpu_base: usize,
1610 geom: Option<GraphAttnGeom<'a>>,
1617 head_gate: Option<GraphW<'a>>,
1622 },
1623 Gdn {
1624 qkv: GraphW<'a>,
1625 z: GraphW<'a>,
1626 a: GraphW<'a>,
1627 b: GraphW<'a>,
1628 out: GraphW<'a>,
1629 conv1d: &'a [f32],
1630 a_log: &'a [f32],
1631 dt_bias: &'a [f32],
1632 norm: &'a [f32],
1633 nv: usize,
1634 nk: usize,
1635 dk: usize,
1636 dv: usize,
1637 kk: usize,
1638 cpu_state: &'a [f32],
1643 },
1644 ShortConv {
1651 inp: GraphW<'a>,
1653 out: GraphW<'a>,
1655 taps: &'a [f32],
1658 kernel: usize,
1659 cpu_state: &'a [f32],
1663 },
1664}
1665
1666#[derive(Clone, Copy)]
1671pub struct GraphAttnGeom<'a> {
1672 pub nkv: usize,
1674 pub hd: usize,
1678 pub dv: usize,
1680 pub rd: usize,
1682 pub invf: &'a [f32],
1684 pub rope_scale: f32,
1689 pub window: Option<usize>,
1692 pub sink: Option<&'a [f32]>,
1695 pub v_norm: bool,
1698}
1699
1700#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1704pub enum GraphAct {
1705 Silu,
1706 GeluErf,
1709 GeluTanh,
1712}
1713
1714impl GraphAct {
1715 pub fn code(self) -> u32 {
1716 match self {
1717 Self::Silu => 0,
1718 Self::GeluErf => 1,
1719 Self::GeluTanh => 2,
1720 }
1721 }
1722}
1723
1724pub struct GraphLayer<'a> {
1726 pub input_norm: &'a [f32],
1727 pub attn: GraphAttn<'a>,
1728 pub post_norm: &'a [f32],
1729 pub ffn: GraphFfn<'a>,
1730 pub attn_out_norm: Option<&'a [f32]>,
1733 pub ffn_out_norm: Option<&'a [f32]>,
1734 pub layer_scale: Option<f32>,
1737}
1738
1739pub enum GraphFfn<'a> {
1744 AttentionOnly,
1747 Dense {
1748 gate: GraphW<'a>,
1749 up: GraphW<'a>,
1750 down: GraphW<'a>,
1751 act: GraphAct,
1754 },
1755 Moe {
1756 router: GraphW<'a>,
1758 shared_gate: GraphW<'a>,
1760 experts: Vec<(usize, usize, usize)>,
1764 n_exp: usize,
1766 top_k: usize,
1767 inter: usize,
1768 norm_topk: bool,
1769 q4tp: bool,
1775 gu_q2: bool,
1779 q82: bool,
1784 sigmoid: bool,
1788 bias: Option<&'a [f32]>,
1791 has_shared: bool,
1795 shared_gated: bool,
1800 route_scale: f32,
1803 },
1804}
1805
1806#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1809pub enum TokenGraphOutcome {
1810 Declined,
1812 Completed,
1814 Failed,
1816}
1817
1818#[allow(clippy::too_many_arguments)]
1823pub fn forward_token_graph(
1824 model: &Arc<CmfModel>,
1825 kv_id: u64,
1826 layers: &[GraphLayer],
1827 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1830 o1_epoch: u64,
1831 invf: &[f32],
1832 h: &mut [f32],
1833 nh: usize,
1834 nkv: usize,
1835 hd: usize,
1836 attn_scale: f32,
1837 rd: usize,
1838 hidden: usize,
1839 inter: usize,
1840 position: usize,
1841 cap: usize,
1842 gemma: bool,
1843 eps: f32,
1844 lm_head: Option<(&GraphW, usize)>,
1845 final_norm: &[f32],
1846 logits: &mut Vec<f32>,
1847 loop_norm_at: &[usize],
1848 steps: usize,
1849 embed: Option<(&GraphW, usize, f32)>,
1850 ids_out: Option<&mut Vec<u32>>,
1851 layers_run: Option<&mut usize>,
1854 layer_base: usize,
1858 hidden_too: bool,
1860) -> TokenGraphOutcome {
1861 match backend() {
1862 #[cfg(feature = "gpu")]
1863 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1864 model,
1865 kv_id,
1866 layers,
1867 o1,
1868 o1_epoch,
1869 invf,
1870 h,
1871 nh,
1872 nkv,
1873 hd,
1874 attn_scale,
1875 rd,
1876 hidden,
1877 inter,
1878 position,
1879 cap,
1880 gemma,
1881 eps,
1882 lm_head,
1883 final_norm,
1884 logits,
1885 loop_norm_at,
1886 steps,
1887 embed,
1888 ids_out,
1889 layers_run,
1890 layer_base,
1891 hidden_too,
1892 ),
1893 #[allow(unused_variables)]
1894 _ => {
1895 let _ = (
1896 attn_scale,
1897 lm_head,
1898 final_norm,
1899 logits,
1900 loop_norm_at,
1901 layers_run,
1902 layer_base,
1903 hidden_too,
1904 );
1905 TokenGraphOutcome::Declined
1906 }
1907 }
1908}
1909
1910pub fn forward_embryo_graph(
1915 model: &Arc<EmbryoGraphModel>,
1916 kv_id: u64,
1917 hidden: &[f32],
1918 position: usize,
1919 logits: &mut Vec<f32>,
1920) -> bool {
1921 #[cfg(feature = "gpu")]
1922 if backend() == Backend::Wgpu {
1923 return crate::gpu_wgpu::forward_embryo_graph(model, kv_id, hidden, position, logits);
1924 }
1925 let _ = (model, kv_id, hidden, position, logits);
1926 false
1927}
1928
1929#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1933pub enum BatchGraphOutcome {
1934 Declined,
1937 Completed,
1939 Failed,
1943}
1944
1945pub struct SpecTail<'a> {
1946 pub lm: GraphW<'a>,
1947 pub lm_rows: usize,
1948 pub final_norm: &'a [f32],
1949 pub logits_out: &'a mut Vec<f32>,
1950}
1951
1952#[allow(clippy::too_many_arguments)]
1956pub fn forward_batch_graph(
1957 model: &Arc<CmfModel>,
1958 kv_id: u64,
1959 layers: &[GraphLayer],
1960 invf: &[f32],
1961 h: &mut [f32],
1962 nh: usize,
1963 nkv: usize,
1964 hd: usize,
1965 rd: usize,
1966 hidden: usize,
1967 inter: usize,
1968 positions: &[usize],
1969 cap: usize,
1970 gemma: bool,
1971 eps: f32,
1972 attn_scale: f32,
1973 k: usize,
1974 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1977 o1_epoch: u64,
1978 spec: Option<SpecTail<'_>>,
1979 layers_run: Option<&mut usize>,
1985) -> BatchGraphOutcome {
1986 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)
1987}
1988
1989thread_local! {
1990 static MIMO_ATTN_SCRATCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1991}
1992
1993pub(crate) fn mimo_attention_scratch_enabled() -> bool {
1994 MIMO_ATTN_SCRATCH.with(std::cell::Cell::get)
1995}
1996
1997#[doc(hidden)]
2000pub fn mimo_attention_scratch_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2001 struct Restore(bool);
2002 impl Drop for Restore {
2003 fn drop(&mut self) {
2004 MIMO_ATTN_SCRATCH.with(|v| v.set(self.0));
2005 }
2006 }
2007 let _restore = Restore(MIMO_ATTN_SCRATCH.with(|v| v.replace(enabled)));
2008 f()
2009}
2010
2011pub fn g4_batch_on() -> bool {
2014 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2015 *ON.get_or_init(|| std::env::var("CMF_G4_BATCH").as_deref() != Ok("0"))
2016}
2017
2018pub(crate) fn moe_q82_graph_on() -> bool {
2021 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2022 *ON.get_or_init(|| std::env::var("CMF_MOE_Q82").as_deref() != Ok("0"))
2023}
2024
2025thread_local! {
2026 static MIMO_Q8_SHORT: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
2027}
2028
2029pub(crate) fn mimo_q8_short_enabled() -> bool {
2030 MIMO_Q8_SHORT.with(std::cell::Cell::get)
2031}
2032
2033#[doc(hidden)]
2035pub fn mimo_q8_short_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2036 struct Restore(bool);
2037 impl Drop for Restore {
2038 fn drop(&mut self) {
2039 MIMO_Q8_SHORT.with(|v| v.set(self.0));
2040 }
2041 }
2042 let _restore = Restore(MIMO_Q8_SHORT.with(|v| v.replace(enabled)));
2043 f()
2044}
2045
2046#[allow(clippy::too_many_arguments)]
2048pub fn forward_batch_graph_at(
2049 model: &Arc<CmfModel>,
2050 kv_id: u64,
2051 layer_base: usize,
2052 layers: &[GraphLayer],
2053 invf: &[f32],
2054 h: &mut [f32],
2055 nh: usize,
2056 nkv: usize,
2057 hd: usize,
2058 rd: usize,
2059 hidden: usize,
2060 inter: usize,
2061 positions: &[usize],
2062 cap: usize,
2063 gemma: bool,
2064 eps: f32,
2065 attn_scale: f32,
2066 k: usize,
2067 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
2070 o1_epoch: u64,
2071 spec: Option<SpecTail<'_>>,
2072 layers_run: Option<&mut usize>,
2078) -> BatchGraphOutcome {
2079 match backend() {
2080 #[cfg(feature = "gpu")]
2081 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph_at(
2082 model, kv_id, layer_base, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions,
2083 cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run,
2084 ),
2085 #[allow(unreachable_patterns)]
2086 _ => {
2087 let _ = (o1, o1_epoch, spec, layers_run);
2088 BatchGraphOutcome::Declined
2089 }
2090 }
2091}
2092
2093pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
2098 #[cfg(feature = "gpu")]
2099 if backend() == Backend::Wgpu {
2100 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
2101 }
2102 #[allow(unreachable_code)]
2103 {
2104 let _ = (kv_id, slot, base_pos, expected_layers);
2105 false
2106 }
2107}
2108
2109pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
2115 #[cfg(feature = "gpu")]
2116 if backend() == Backend::Wgpu {
2117 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
2118 }
2119 #[cfg(target_os = "macos")]
2120 if backend() == Backend::Metal {
2121 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
2122 return true;
2123 }
2124 false
2125}
2126
2127pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
2131 #[cfg(feature = "gpu")]
2132 if backend() == Backend::Wgpu {
2133 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
2134 }
2135 None
2136}
2137
2138pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
2141 #[cfg(feature = "gpu")]
2142 if backend() == Backend::Wgpu {
2143 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
2144 }
2145 false
2146}
2147
2148pub fn graph_kv_read_rows(
2152 _kv_id: u64,
2153 _reqs: &[(usize, usize, usize)],
2154 _nkv: usize,
2155 _hd: usize,
2156) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
2157 #[cfg(feature = "gpu")]
2158 if backend() == Backend::Wgpu {
2159 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
2160 }
2161 None
2162}
2163
2164pub fn graph_kv_pull_host(
2170 _kv_id: u64,
2171 _layer: usize,
2172 _from: usize,
2173 _to: usize,
2174 _nkv: usize,
2175 _hd: usize,
2176) -> Option<(Vec<f32>, Vec<f32>, usize)> {
2177 #[cfg(feature = "gpu")]
2178 if backend() == Backend::Wgpu {
2179 return crate::gpu_wgpu::kv_mirror_pull_host(_kv_id, _layer, _from, _to, _nkv, _hd);
2180 }
2181 None
2182}
2183
2184pub fn graph_kv_reset(_kv_id: u64) {
2186 #[cfg(feature = "gpu")]
2187 if backend() == Backend::Wgpu {
2188 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
2189 crate::gpu_wgpu::embryo_graph_reset(_kv_id);
2190 }
2191}
2192
2193pub fn q1t_matvec(
2197 model: &Arc<CmfModel>,
2198 idx: usize,
2199 xs: &[f32],
2200 rows: usize,
2201 cols: usize,
2202 out: &mut [f32],
2203) -> bool {
2204 match backend() {
2205 #[cfg(target_os = "macos")]
2206 Backend::Metal => {
2207 if metal_q1t_enabled() {
2208 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
2209 } else {
2210 false
2211 }
2212 }
2213 #[cfg(feature = "gpu")]
2214 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
2215 Backend::None => false,
2216 }
2217}
2218
2219#[allow(unused_variables)]
2222pub fn q4b_matvec(
2223 model: &Arc<CmfModel>,
2224 idx: usize,
2225 xs: &[f32],
2226 rows: usize,
2227 cols: usize,
2228 out: &mut [f32],
2229) -> bool {
2230 match backend() {
2231 #[cfg(target_os = "macos")]
2232 Backend::Metal => false,
2233 #[cfg(feature = "gpu")]
2234 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
2235 Backend::None => false,
2236 }
2237}
2238
2239pub fn q1t_matmat(
2242 model: &Arc<CmfModel>,
2243 idx: usize,
2244 xs: &[f32],
2245 b: usize,
2246 rows: usize,
2247 cols: usize,
2248 out: &mut [f32],
2249) -> bool {
2250 match backend() {
2251 #[cfg(target_os = "macos")]
2252 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
2256 #[cfg(feature = "gpu")]
2257 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
2258 Backend::None => false,
2259 }
2260}
2261
2262#[cfg(target_os = "macos")]
2266pub(crate) fn metal_q1t_enabled() -> bool {
2267 std::env::var("CMF_METAL_Q1T")
2268 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
2269 .unwrap_or(true)
2270}
2271
2272pub fn q1_matmat(
2274 model: &Arc<CmfModel>,
2275 idx: usize,
2276 xs: &[f32],
2277 b: usize,
2278 rows: usize,
2279 cols: usize,
2280 out: &mut [f32],
2281) -> bool {
2282 match backend() {
2283 #[cfg(feature = "gpu")]
2284 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
2285 #[allow(unused_variables)]
2286 _ => false,
2287 }
2288}
2289
2290pub fn q1g_matmat(
2292 model: &Arc<CmfModel>,
2293 idx: usize,
2294 xs: &[f32],
2295 b: usize,
2296 rows: usize,
2297 cols: usize,
2298 out: &mut [f32],
2299) -> bool {
2300 match backend() {
2301 #[cfg(feature = "gpu")]
2302 Backend::Wgpu => crate::gpu_wgpu::q1g_matmat(model, idx, xs, b, rows, cols, out),
2303 #[allow(unused_variables)]
2304 _ => false,
2305 }
2306}
2307
2308static MM_KILL: AtomicBool = AtomicBool::new(false);
2313pub(crate) fn mm_killed() -> bool {
2314 MM_KILL.load(Ordering::Relaxed)
2315}
2316pub(crate) fn mm_kill() {
2317 MM_KILL.store(true, Ordering::Relaxed);
2318}
2319
2320static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2327const MM_STRIKES_TO_KILL: u32 = 3;
2328static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2335
2336pub fn mm_kill_arm(on: bool) {
2339 MM_ARMED.store(on, Ordering::Relaxed);
2340 if on {
2341 MM_STRIKES.store(0, Ordering::Relaxed);
2342 }
2343}
2344
2345pub(crate) fn mm_budget_check(
2352 what: &str,
2353 el: std::time::Duration,
2354 budget: std::time::Duration,
2355 exempt: bool,
2356) {
2357 if el <= budget {
2358 MM_STRIKES.store(0, Ordering::Relaxed);
2359 return;
2360 }
2361 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
2362 return;
2363 }
2364 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2365 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
2366 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
2367 if !on {
2368 tracing::info!(
2369 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
2370 );
2371 return;
2372 }
2373 if n >= MM_STRIKES_TO_KILL {
2374 tracing::warn!(
2375 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
2376 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
2377 );
2378 mm_kill();
2379 } else {
2380 tracing::info!(
2381 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
2382 );
2383 }
2384}
2385
2386#[allow(unused_variables, clippy::too_many_arguments)]
2391pub fn chunk_attend(
2392 q: &[f32],
2393 k: &[&[f32]],
2394 v: &[&[f32]],
2395 b: usize,
2396 s0: usize,
2397 nh: usize,
2398 nkv: usize,
2399 hd: usize,
2400 scale: f32,
2401 out: &mut [f32],
2402) -> bool {
2403 match backend() {
2404 #[cfg(feature = "gpu")]
2405 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
2406 #[allow(unreachable_patterns)]
2407 _ => false,
2408 }
2409}
2410
2411#[allow(unused_variables, clippy::too_many_arguments)]
2414pub fn chunk_attend_win(
2415 q: &[f32],
2416 k: &[&[f32]],
2417 v: &[&[f32]],
2418 b: usize,
2419 s0: usize,
2420 nh: usize,
2421 nkv: usize,
2422 hd: usize,
2423 scale: f32,
2424 window: usize,
2425 out: &mut [f32],
2426) -> bool {
2427 match backend() {
2428 #[cfg(feature = "gpu")]
2429 Backend::Wgpu => {
2430 crate::gpu_wgpu::chunk_attend_win(q, k, v, b, s0, nh, nkv, hd, scale, window, out)
2431 }
2432 #[allow(unreachable_patterns)]
2433 _ => false,
2434 }
2435}
2436
2437#[allow(unused_variables)]
2440pub fn gemm_many_keep(
2441 model: &Arc<CmfModel>,
2442 idxs: &[(usize, usize)],
2443 xs: &[f32],
2444 b: usize,
2445 cols: usize,
2446 out: &mut [f32],
2447) -> bool {
2448 match backend() {
2449 #[cfg(feature = "gpu")]
2450 Backend::Wgpu => crate::gpu_wgpu::gemm_many_keep(model, idxs, xs, b, cols, out),
2451 #[allow(unreachable_patterns)]
2452 _ => false,
2453 }
2454}
2455
2456thread_local! {
2457 static PREFILL_FAST_GEMM: Cell<bool> = const { Cell::new(false) };
2461}
2462
2463pub struct PrefillFastGemmGuard(bool);
2465
2466impl Drop for PrefillFastGemmGuard {
2467 fn drop(&mut self) {
2468 PREFILL_FAST_GEMM.with(|c| c.set(self.0));
2469 }
2470}
2471
2472pub fn enter_prefill_fast_gemm() -> PrefillFastGemmGuard {
2476 PrefillFastGemmGuard(PREFILL_FAST_GEMM.with(|c| c.replace(true)))
2477}
2478
2479pub fn prefill_fast_gemm() -> bool {
2481 PREFILL_FAST_GEMM.with(|c| c.get())
2482}
2483
2484pub const F16_MAX: f32 = 65504.0;
2487
2488pub fn abs_max_or_inf(xs: &[f32]) -> f32 {
2492 let mut acc = [0f32; 16];
2493 let mut bad = [false; 16];
2494 let mut it = xs.chunks_exact(16);
2495 for c in &mut it {
2496 for ((a, f), &v) in acc.iter_mut().zip(bad.iter_mut()).zip(c) {
2497 let v = v.abs();
2498 *f |= !(v < f32::INFINITY);
2500 *a = if v > *a { v } else { *a };
2501 }
2502 }
2503 let mut m = 0f32;
2504 for &v in it.remainder() {
2505 let v = v.abs();
2506 if !(v < f32::INFINITY) {
2507 return f32::INFINITY;
2508 }
2509 m = m.max(v);
2510 }
2511 if bad.iter().any(|&f| f) {
2512 return f32::INFINITY;
2513 }
2514 acc.iter().fold(m, |m, &a| m.max(a))
2515}
2516
2517#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2523pub struct PrefillMirror {
2524 pub kv_id: u64,
2525 pub layer: usize,
2526 pub limit: usize,
2527}
2528
2529thread_local! {
2530 static PREFILL_MIRROR: Cell<Option<PrefillMirror>> = const { Cell::new(None) };
2534}
2535
2536pub struct PrefillMirrorGuard(Option<PrefillMirror>);
2538
2539impl Drop for PrefillMirrorGuard {
2540 fn drop(&mut self) {
2541 PREFILL_MIRROR.with(|c| c.set(self.0));
2542 }
2543}
2544
2545pub fn enter_prefill_mirror(t: PrefillMirror) -> PrefillMirrorGuard {
2548 PrefillMirrorGuard(PREFILL_MIRROR.with(|c| c.replace(Some(t))))
2549}
2550
2551pub fn prefill_mirror() -> Option<PrefillMirror> {
2553 PREFILL_MIRROR.with(|c| c.get())
2554}
2555
2556#[allow(unused_variables, clippy::too_many_arguments)]
2569pub fn chunk_attend_mirror(
2570 t: PrefillMirror,
2571 cpu_k: &[Vec<f32>],
2572 cpu_v: &[Vec<f32>],
2573 base: usize,
2574 q: &[f32],
2575 b: usize,
2576 s0: usize,
2577 nh: usize,
2578 nkv: usize,
2579 hd: usize,
2580 scale: f32,
2581 ring: Option<usize>,
2582 window: usize,
2583 operand_max: f32,
2584 out: &mut [f32],
2585) -> bool {
2586 match backend() {
2587 #[cfg(feature = "gpu")]
2588 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror(
2593 t.kv_id,
2594 t.layer,
2595 t.limit,
2596 cpu_k,
2597 cpu_v,
2598 base,
2599 q,
2600 b,
2601 s0,
2602 nh,
2603 nkv,
2604 hd,
2605 scale,
2606 ring,
2607 window,
2608 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2609 operand_max,
2610 out,
2611 ),
2612 #[allow(unreachable_patterns)]
2613 _ => false,
2614 }
2615}
2616
2617#[allow(unused_variables, clippy::too_many_arguments)]
2625pub fn chunk_attend_mirror_wo(
2626 t: PrefillMirror,
2627 cpu_k: &[Vec<f32>],
2628 cpu_v: &[Vec<f32>],
2629 base: usize,
2630 q: &[f32],
2631 b: usize,
2632 s0: usize,
2633 nh: usize,
2634 nkv: usize,
2635 hd: usize,
2636 scale: f32,
2637 ring: Option<usize>,
2638 window: usize,
2639 operand_max: f32,
2640 gains: Option<&[f32]>,
2641 wo: (&Arc<CmfModel>, usize),
2642 hidden: usize,
2643 out: &mut [f32],
2644) -> bool {
2645 match backend() {
2646 #[cfg(feature = "gpu")]
2647 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror_wo(
2648 t.kv_id,
2649 t.layer,
2650 t.limit,
2651 cpu_k,
2652 cpu_v,
2653 base,
2654 q,
2655 b,
2656 s0,
2657 nh,
2658 nkv,
2659 hd,
2660 scale,
2661 ring,
2662 window,
2663 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2664 operand_max,
2665 gains,
2666 wo,
2667 hidden,
2668 out,
2669 ),
2670 #[allow(unreachable_patterns)]
2671 _ => false,
2672 }
2673}
2674
2675pub fn chunk_attend_windowed() -> bool {
2677 match backend() {
2678 #[cfg(feature = "gpu")]
2679 Backend::Wgpu => true,
2680 #[allow(unreachable_patterns)]
2681 _ => false,
2682 }
2683}
2684
2685#[allow(unused_variables, clippy::too_many_arguments)]
2689pub fn q4t_qkv(
2690 model: &Arc<CmfModel>,
2691 wq: usize,
2692 wk: usize,
2693 wv: usize,
2694 xs: &[f32],
2695 b: usize,
2696 cols: usize,
2697 rq: usize,
2698 rk: usize,
2699 rv: usize,
2700 out: &mut [f32],
2701) -> bool {
2702 match backend() {
2703 #[cfg(feature = "gpu")]
2704 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
2705 #[allow(unreachable_patterns)]
2706 _ => false,
2707 }
2708}
2709
2710#[allow(unused_variables, clippy::too_many_arguments)]
2712#[allow(clippy::too_many_arguments, unused_variables)]
2716pub fn q4tp_ffn_packed(
2717 model: &Arc<CmfModel>,
2718 w1: usize,
2719 w2: usize,
2720 xs: &[f32],
2721 b: usize,
2722 hidden: usize,
2723 inter: usize,
2724 bias: Option<&[f32]>,
2725 out: &mut [f32],
2726) -> bool {
2727 match backend() {
2728 #[cfg(feature = "gpu")]
2729 Backend::Wgpu => {
2730 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
2731 }
2732 #[allow(unreachable_patterns)]
2733 _ => false,
2734 }
2735}
2736
2737pub fn q4tp_ffn(
2738 model: &Arc<CmfModel>,
2739 w1: usize,
2740 w3: usize,
2741 w2: usize,
2742 xs: &[f32],
2743 b: usize,
2744 hidden: usize,
2745 inter: usize,
2746 out: &mut [f32],
2747) -> bool {
2748 match backend() {
2749 #[cfg(target_os = "macos")]
2750 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2751 #[cfg(feature = "gpu")]
2752 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2753 #[allow(unreachable_patterns)]
2754 _ => false,
2755 }
2756}
2757
2758#[allow(clippy::too_many_arguments, unused_variables)]
2762pub fn q4_ffn_act(
2763 model: &Arc<CmfModel>,
2764 w1: usize,
2765 w3: usize,
2766 w2: usize,
2767 xs: &[f32],
2768 b: usize,
2769 hidden: usize,
2770 inter: usize,
2771 q4tp: bool,
2772 act: GraphAct,
2773 out: &mut [f32],
2774) -> bool {
2775 match act {
2776 GraphAct::Silu if q4tp => q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2777 GraphAct::Silu => q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2778 GraphAct::GeluErf | GraphAct::GeluTanh => match backend() {
2779 #[cfg(feature = "gpu")]
2780 Backend::Wgpu => crate::gpu_wgpu::q4_ffn_act(
2781 model,
2782 w1,
2783 w3,
2784 w2,
2785 xs,
2786 b,
2787 hidden,
2788 inter,
2789 q4tp,
2790 act.code(),
2791 out,
2792 ),
2793 #[allow(unreachable_patterns)]
2794 _ => false,
2795 },
2796 }
2797}
2798
2799#[allow(clippy::too_many_arguments, unused_variables)]
2804pub fn ffn_act_keep(
2805 model: &Arc<CmfModel>,
2806 w1: usize,
2807 w3: usize,
2808 w2: usize,
2809 xs: &[f32],
2810 b: usize,
2811 hidden: usize,
2812 inter: usize,
2813 act: GraphAct,
2814 out: &mut [f32],
2815) -> bool {
2816 match backend() {
2817 #[cfg(feature = "gpu")]
2818 Backend::Wgpu => crate::gpu_wgpu::ffn_act_keep(
2819 model,
2820 w1,
2821 w3,
2822 w2,
2823 xs,
2824 b,
2825 hidden,
2826 inter,
2827 act.code(),
2828 out,
2829 ),
2830 #[allow(unreachable_patterns)]
2831 _ => false,
2832 }
2833}
2834
2835#[allow(clippy::too_many_arguments, unused_variables)]
2840pub fn q4tp_gelu_ffn(
2841 model: &Arc<CmfModel>,
2842 w_in: usize,
2843 w_out: usize,
2844 xs: &[f32],
2845 b: usize,
2846 hidden: usize,
2847 inter: usize,
2848 bias_in: &[f32],
2849 bias_out: &[f32],
2850 out: &mut [f32],
2851) -> bool {
2852 match backend() {
2853 #[cfg(feature = "gpu")]
2854 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2855 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2856 ),
2857 #[allow(unreachable_patterns)]
2858 _ => false,
2859 }
2860}
2861
2862pub fn q4t_ffn(
2863 model: &Arc<CmfModel>,
2864 w1: usize,
2865 w3: usize,
2866 w2: usize,
2867 xs: &[f32],
2868 b: usize,
2869 hidden: usize,
2870 inter: usize,
2871 out: &mut [f32],
2872) -> bool {
2873 match backend() {
2874 #[cfg(target_os = "macos")]
2875 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2876 #[cfg(feature = "gpu")]
2877 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2878 #[allow(unreachable_patterns)]
2879 _ => false,
2880 }
2881}
2882
2883pub struct DitBlockArgs<'a> {
2888 pub n: usize,
2889 pub hidden: usize,
2890 pub inter: usize,
2891 pub nh: usize,
2892 pub nkv: usize,
2893 pub hd: usize,
2894 pub eps: f32,
2895 pub rope_cos: &'a [f32],
2896 pub rope_sin: &'a [f32],
2897 pub norm1: &'a [f32],
2898 pub norm2: &'a [f32],
2899 pub ffn_norm1: &'a [f32],
2900 pub ffn_norm2: &'a [f32],
2901 pub norm_q: &'a [f32],
2902 pub norm_k: &'a [f32],
2903 pub s_msa: &'a [f32],
2904 pub gate_msa: &'a [f32],
2905 pub s_mlp: &'a [f32],
2906 pub gate_mlp: &'a [f32],
2907 pub wq: usize,
2908 pub wk: usize,
2909 pub wv: usize,
2910 pub wo: usize,
2911 pub w1: usize,
2912 pub w3: usize,
2913 pub w2: usize,
2914 pub q4tp: bool,
2918 pub resident_in: bool,
2921 pub resident_out: bool,
2925}
2926
2927pub fn dit_chain_supported() -> bool {
2931 #[cfg(feature = "gpu")]
2932 {
2933 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2934 }
2935 #[allow(unreachable_code)]
2936 false
2937}
2938
2939pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2942 #[cfg(feature = "gpu")]
2943 {
2944 if matches!(backend(), Backend::Wgpu) {
2945 return crate::gpu_wgpu::dit_state_fetch(_x);
2946 }
2947 }
2948 false
2949}
2950
2951#[allow(unused_variables)]
2955#[allow(unused_variables, clippy::too_many_arguments)]
2959pub fn dit_qkv(
2960 model: &Arc<CmfModel>,
2961 wq: usize,
2962 wk: usize,
2963 wv: usize,
2964 xs: &[f32],
2965 b: usize,
2966 hidden: usize,
2967 qrows: usize,
2968 kvrows: usize,
2969 q_out: &mut [f32],
2970 k_out: &mut [f32],
2971 v_out: &mut [f32],
2972) -> bool {
2973 match backend() {
2974 #[cfg(feature = "gpu")]
2975 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2976 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2977 ),
2978 #[allow(unreachable_patterns)]
2979 _ => false,
2980 }
2981}
2982
2983pub struct QwenImageAttentionArgs<'a> {
2991 pub image: &'a [f32],
2992 pub text: &'a [f32],
2993 pub image_tokens: usize,
2994 pub text_tokens: usize,
2995 pub heads: usize,
2996 pub head_dim: usize,
2997 pub image_q: usize,
2998 pub image_k: usize,
2999 pub image_v: usize,
3000 pub text_q: usize,
3001 pub text_k: usize,
3002 pub text_v: usize,
3003 pub image_out: usize,
3004 pub text_out: usize,
3005 pub image_q_norm: &'a [f32],
3006 pub image_k_norm: &'a [f32],
3007 pub text_q_norm: &'a [f32],
3008 pub text_k_norm: &'a [f32],
3009 pub image_cos: &'a [f32],
3010 pub image_sin: &'a [f32],
3011 pub text_cos: &'a [f32],
3012 pub text_sin: &'a [f32],
3013 pub image_q_bias: &'a [f32],
3014 pub image_k_bias: &'a [f32],
3015 pub image_v_bias: &'a [f32],
3016 pub text_q_bias: &'a [f32],
3017 pub text_k_bias: &'a [f32],
3018 pub text_v_bias: &'a [f32],
3019 pub image_out_bias: &'a [f32],
3020 pub text_out_bias: &'a [f32],
3021 pub image_proj: &'a mut [f32],
3022 pub text_proj: &'a mut [f32],
3023}
3024
3025#[allow(clippy::too_many_fields)]
3030pub struct QwenImageChainBlock<'a> {
3031 pub image_mod: &'a [f32],
3032 pub text_mod: &'a [f32],
3033 pub image_q: usize,
3034 pub image_k: usize,
3035 pub image_v: usize,
3036 pub text_q: usize,
3037 pub text_k: usize,
3038 pub text_v: usize,
3039 pub image_out: usize,
3040 pub text_out: usize,
3041 pub image_q_norm: &'a [f32],
3042 pub image_k_norm: &'a [f32],
3043 pub text_q_norm: &'a [f32],
3044 pub text_k_norm: &'a [f32],
3045 pub image_q_bias: &'a [f32],
3046 pub image_k_bias: &'a [f32],
3047 pub image_v_bias: &'a [f32],
3048 pub text_q_bias: &'a [f32],
3049 pub text_k_bias: &'a [f32],
3050 pub text_v_bias: &'a [f32],
3051 pub image_out_bias: &'a [f32],
3052 pub text_out_bias: &'a [f32],
3053 pub image_attn_gate: &'a [f32],
3054 pub text_attn_gate: &'a [f32],
3055 pub image_mlp_in: usize,
3056 pub image_mlp_out: usize,
3057 pub text_mlp_in: usize,
3058 pub text_mlp_out: usize,
3059 pub image_mlp_in_bias: &'a [f32],
3060 pub image_mlp_out_bias: &'a [f32],
3061 pub text_mlp_in_bias: &'a [f32],
3062 pub text_mlp_out_bias: &'a [f32],
3063}
3064
3065#[allow(clippy::too_many_fields)]
3071pub struct QwenImageBlockArgs<'a> {
3072 pub image: &'a mut [f32],
3075 pub text: &'a mut [f32],
3076 pub image_norm: &'a [f32],
3077 pub text_norm: &'a [f32],
3078 pub image_tokens: usize,
3079 pub text_tokens: usize,
3080 pub heads: usize,
3081 pub head_dim: usize,
3082 pub image_cos: &'a [f32],
3083 pub image_sin: &'a [f32],
3084 pub text_cos: &'a [f32],
3085 pub text_sin: &'a [f32],
3086 pub image_q: usize,
3087 pub image_k: usize,
3088 pub image_v: usize,
3089 pub text_q: usize,
3090 pub text_k: usize,
3091 pub text_v: usize,
3092 pub image_out: usize,
3093 pub text_out: usize,
3094 pub image_q_norm: &'a [f32],
3095 pub image_k_norm: &'a [f32],
3096 pub text_q_norm: &'a [f32],
3097 pub text_k_norm: &'a [f32],
3098 pub image_q_bias: &'a [f32],
3099 pub image_k_bias: &'a [f32],
3100 pub image_v_bias: &'a [f32],
3101 pub text_q_bias: &'a [f32],
3102 pub text_k_bias: &'a [f32],
3103 pub text_v_bias: &'a [f32],
3104 pub image_out_bias: &'a [f32],
3105 pub text_out_bias: &'a [f32],
3106 pub image_attn_gate: &'a [f32],
3107 pub text_attn_gate: &'a [f32],
3108 pub image_mlp_in: usize,
3109 pub image_mlp_out: usize,
3110 pub text_mlp_in: usize,
3111 pub text_mlp_out: usize,
3112 pub image_mlp_in_bias: &'a [f32],
3113 pub image_mlp_out_bias: &'a [f32],
3114 pub text_mlp_in_bias: &'a [f32],
3115 pub text_mlp_out_bias: &'a [f32],
3116 pub image_mlp_mod: &'a [f32],
3117 pub text_mlp_mod: &'a [f32],
3118 pub image_mlp_gate: &'a [f32],
3119 pub text_mlp_gate: &'a [f32],
3120}
3121
3122pub struct QwenImageChainArgs<'a> {
3127 pub image: &'a mut [f32],
3128 pub text: &'a mut [f32],
3129 pub image_tokens: usize,
3130 pub text_tokens: usize,
3131 pub heads: usize,
3132 pub head_dim: usize,
3133 pub image_cos: &'a [f32],
3134 pub image_sin: &'a [f32],
3135 pub text_cos: &'a [f32],
3136 pub text_sin: &'a [f32],
3137 pub blocks: &'a [QwenImageChainBlock<'a>],
3138}
3139
3140#[allow(unused_variables)]
3141pub fn qwen_image_attention(
3142 model: &Arc<CmfModel>,
3143 args: &mut QwenImageAttentionArgs<'_>,
3144) -> bool {
3145 match backend() {
3146 #[cfg(feature = "gpu")]
3147 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
3148 #[allow(unreachable_patterns)]
3149 _ => false,
3150 }
3151}
3152
3153#[allow(unused_variables)]
3154pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
3155 match backend() {
3156 #[cfg(feature = "gpu")]
3157 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
3158 #[allow(unreachable_patterns)]
3159 _ => false,
3160 }
3161}
3162
3163#[allow(unused_variables)]
3167pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
3168 match backend() {
3169 #[cfg(feature = "gpu")]
3170 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
3171 #[allow(unreachable_patterns)]
3172 _ => false,
3173 }
3174}
3175
3176#[allow(unused_variables, clippy::too_many_arguments)]
3183pub fn qwen_image_mlp_inplace(
3184 model: &Arc<CmfModel>,
3185 w_in: usize,
3186 w_out: usize,
3187 data: &mut [f32],
3188 batch: usize,
3189 hidden: usize,
3190 inter: usize,
3191 bias_in: &[f32],
3192 bias_out: &[f32],
3193 modulation: &[f32],
3194 gate: &[f32],
3195) -> bool {
3196 match backend() {
3197 #[cfg(feature = "gpu")]
3198 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
3199 model,
3200 w_in,
3201 w_out,
3202 data,
3203 batch,
3204 hidden,
3205 inter,
3206 bias_in,
3207 bias_out,
3208 modulation,
3209 gate,
3210 ),
3211 #[allow(unreachable_patterns)]
3212 _ => false,
3213 }
3214}
3215
3216pub fn fused_dit_block_available() -> bool {
3220 #[cfg(target_os = "macos")]
3221 {
3222 matches!(backend(), Backend::Metal) && fused_block_trusted()
3223 }
3224 #[cfg(not(target_os = "macos"))]
3225 {
3226 false
3227 }
3228}
3229
3230pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
3231 dit_block_seg(model, a, &[a.n], x)
3232}
3233
3234pub fn dit_block_seg(
3238 model: &Arc<CmfModel>,
3239 a: &DitBlockArgs,
3240 segs: &[usize],
3241 x: &mut [f32],
3242) -> bool {
3243 match backend() {
3244 #[cfg(target_os = "macos")]
3245 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
3246 #[cfg(feature = "gpu")]
3253 Backend::Wgpu
3254 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
3255 Some("0") => false,
3256 Some(_) => true,
3257 None => crate::gpu_wgpu::discrete_active(),
3258 } =>
3259 {
3260 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
3261 }
3262 #[allow(unreachable_patterns)]
3263 _ => false,
3264 }
3265}
3266
3267pub struct VaeResnetArgs<'a> {
3271 pub groups: usize,
3272 pub ic: usize,
3273 pub oc: usize,
3274 pub h: usize,
3275 pub w: usize,
3276 pub n1w: &'a [f32],
3277 pub n1b: &'a [f32],
3278 pub c1w: &'a [f32],
3279 pub c1b: &'a [f32],
3280 pub c1k: usize,
3281 pub n2w: &'a [f32],
3282 pub n2b: &'a [f32],
3283 pub c2w: &'a [f32],
3284 pub c2b: &'a [f32],
3285 pub c2k: usize,
3286 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
3287}
3288
3289#[allow(unused_variables)]
3292pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
3293 match backend() {
3294 #[cfg(target_os = "macos")]
3295 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
3296 _ => false,
3297 }
3298}
3299
3300#[allow(unused_variables, clippy::too_many_arguments)]
3303pub fn vae_upsample_conv(
3304 w: &[f32],
3305 bias: &[f32],
3306 x: &[f32],
3307 ic: usize,
3308 oc: usize,
3309 h: usize,
3310 w_img: usize,
3311 k: usize,
3312 out: &mut [f32],
3313) -> bool {
3314 match backend() {
3315 #[cfg(target_os = "macos")]
3316 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3317 #[cfg(feature = "gpu")]
3318 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3319 #[allow(unreachable_patterns)]
3320 _ => false,
3321 }
3322}
3323
3324#[allow(unused_variables, clippy::too_many_arguments)]
3327pub fn vae_conv2d(
3328 w: &[f32],
3329 bias: &[f32],
3330 x: &[f32],
3331 ic: usize,
3332 oc: usize,
3333 h: usize,
3334 w_img: usize,
3335 k: usize,
3336 out: &mut [f32],
3337) -> bool {
3338 match backend() {
3339 #[cfg(target_os = "macos")]
3340 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3341 #[cfg(feature = "gpu")]
3342 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3343 #[allow(unreachable_patterns)]
3344 _ => false,
3345 }
3346}
3347
3348#[allow(unused_variables, clippy::too_many_arguments)]
3352#[allow(unused_variables)]
3356#[allow(clippy::too_many_arguments)]
3357#[allow(clippy::too_many_arguments, unused_variables)]
3360pub fn dit_qkv_attention(
3361 model: &Arc<CmfModel>,
3362 qkv_idx: usize,
3363 xn: &[f32],
3364 n: usize,
3365 hidden: usize,
3366 nh: usize,
3367 hd: usize,
3368 scale: f32,
3369 nr: (&[f32], &[f32], &[f32], f32),
3370 out: &mut [f32],
3371) -> bool {
3372 match backend() {
3373 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3374 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
3375 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
3376 ),
3377 #[allow(unreachable_patterns)]
3378 _ => false,
3379 }
3380}
3381
3382#[allow(clippy::too_many_arguments)]
3385pub fn dit_qkv_attn_out(
3386 model: &Arc<CmfModel>,
3387 qkv_idx: usize,
3388 out_idx: usize,
3389 xn: &[f32],
3390 n: usize,
3391 hidden: usize,
3392 nh: usize,
3393 hd: usize,
3394 scale: f32,
3395 nr: (&[f32], &[f32], &[f32], f32),
3396 proj: &mut [f32],
3397) -> bool {
3398 match backend() {
3399 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3400 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
3401 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
3402 ),
3403 #[allow(unreachable_patterns)]
3404 _ => false,
3405 }
3406}
3407
3408#[allow(clippy::too_many_arguments)]
3410pub fn vae_qkv_attn_out(
3411 model: &Arc<CmfModel>,
3412 qkv_idx: usize,
3413 out_idx: usize,
3414 xn: &[f32],
3415 n: usize,
3416 dim: usize,
3417 nh: usize,
3418 hd: usize,
3419 scale: f32,
3420 angles: &[f32],
3421 eps: f32,
3422 qkv_bias: &[f32],
3423 proj: &mut [f32],
3424) -> bool {
3425 match backend() {
3426 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3427 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
3428 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
3429 ),
3430 #[allow(unreachable_patterns)]
3431 _ => false,
3432 }
3433}
3434
3435#[allow(clippy::too_many_arguments)]
3436pub fn vae_attention_packed(
3437 qkv: &[f32],
3438 nh: usize,
3439 n: usize,
3440 hd: usize,
3441 scale: f32,
3442 angles: &[f32],
3443 eps: f32,
3444 out: &mut [f32],
3445) -> bool {
3446 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
3447}
3448
3449#[allow(clippy::too_many_arguments)]
3450pub fn vae_attention_packed_layout(
3451 qkv: &[f32],
3452 nh: usize,
3453 n: usize,
3454 hd: usize,
3455 scale: f32,
3456 angles: &[f32],
3457 eps: f32,
3458 out: &mut [f32],
3459 layout: u32,
3460) -> bool {
3461 match backend() {
3462 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3463 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
3464 qkv, nh, n, hd, scale, angles, eps, out, layout,
3465 ),
3466 #[allow(unreachable_patterns)]
3467 _ => false,
3468 }
3469}
3470
3471#[allow(clippy::too_many_arguments)]
3472pub fn dit_split_only(
3473 qkv: &[f32],
3474 nh: usize,
3475 n: usize,
3476 hd: usize,
3477 layout: u32,
3478 norm: Option<(&[f32], f32)>,
3479 out_q: &mut [f32],
3480) -> bool {
3481 match backend() {
3482 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3483 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
3484 #[allow(unreachable_patterns)]
3485 _ => false,
3486 }
3487}
3488
3489pub fn gemm_nt_f32_transient(
3497 x: &[f32],
3498 w: &[f32],
3499 y: &mut [f32],
3500 n: usize,
3501 k: usize,
3502 m: usize,
3503) -> bool {
3504 match backend() {
3505 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3506 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
3507 #[allow(unreachable_patterns)]
3508 _ => false,
3509 }
3510}
3511
3512pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
3513 match backend() {
3514 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3515 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
3516 #[allow(unreachable_patterns)]
3517 _ => false,
3518 }
3519}
3520
3521#[allow(clippy::too_many_arguments)]
3524pub fn music3_ffn(
3525 model: &std::sync::Arc<CmfModel>,
3526 idx_in: usize,
3527 idx_out: usize,
3528 h: &[f32],
3529 bias_in: &[f32],
3530 n: usize,
3531 hs: usize,
3532 inter: usize,
3533 out: &mut [f32],
3534) -> bool {
3535 match backend() {
3536 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3537 Backend::Wgpu => {
3538 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
3539 }
3540 #[allow(unreachable_patterns)]
3541 _ => false,
3542 }
3543}
3544
3545#[allow(clippy::too_many_arguments)]
3549pub fn conv1d_gemm(
3550 x: &[f32],
3551 w: &[f32],
3552 ic: usize,
3553 oc: usize,
3554 n: usize,
3555 k: usize,
3556 pad: usize,
3557 dil: usize,
3558 out_n: usize,
3559 yt: &mut [f32],
3560) -> bool {
3561 match backend() {
3562 #[cfg(target_os = "macos")]
3563 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3564 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3565 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3566 #[allow(unreachable_patterns)]
3567 _ => false,
3568 }
3569}
3570
3571#[allow(clippy::too_many_arguments)]
3573pub fn vae_conv2d_coop(
3574 w: &[f32],
3575 bias: Option<&[f32]>,
3576 x: &[f32],
3577 ic: usize,
3578 oc: usize,
3579 h: usize,
3580 wi: usize,
3581 k: usize,
3582 out: &mut [f32],
3583) -> bool {
3584 match backend() {
3585 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3586 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
3587 #[allow(unreachable_patterns)]
3588 _ => false,
3589 }
3590}
3591
3592pub fn dit_attention_packed(
3593 qkv: &[f32],
3594 nh: usize,
3595 n: usize,
3596 hd: usize,
3597 scale: f32,
3598 nr: Option<(&[f32], &[f32], &[f32], f32)>,
3601 out: &mut [f32],
3602) -> bool {
3603 match backend() {
3604 #[cfg(feature = "gpu")]
3611 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
3612 #[allow(unreachable_patterns)]
3613 _ => false,
3614 }
3615}
3616
3617pub fn dit_attention_packed_available() -> bool {
3625 #[allow(unreachable_patterns)]
3626 match backend() {
3627 #[cfg(feature = "gpu")]
3628 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
3629 _ => false,
3630 }
3631}
3632
3633pub fn dit_attention(
3634 qh: &[f32],
3635 kh: &[f32],
3636 vh: &[f32],
3637 nh: usize,
3638 nkv: usize,
3639 n: usize,
3640 hd: usize,
3641 scale: f32,
3642 out: &mut [f32],
3643) -> bool {
3644 match backend() {
3645 #[cfg(target_os = "macos")]
3646 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3647 #[cfg(feature = "gpu")]
3648 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3649 #[allow(unreachable_patterns)]
3650 _ => false,
3651 }
3652}
3653
3654pub fn gram_alloc(cols: usize) -> Option<u64> {
3657 #[cfg(feature = "gpu")]
3658 if backend() == Backend::Wgpu {
3659 return crate::gpu_wgpu::gram::alloc(cols);
3660 }
3661 let _ = cols;
3662 None
3663}
3664
3665pub fn gram_add(id: u64, xs: &[f32], b: usize) -> bool {
3667 #[cfg(feature = "gpu")]
3668 if backend() == Backend::Wgpu {
3669 return crate::gpu_wgpu::gram::add(id, xs, b);
3670 }
3671 let _ = (id, xs, b);
3672 false
3673}
3674
3675#[cfg(feature = "gpu")]
3677pub fn gram_add_dev(id: u64, x: &wgpu::Buffer, b: usize) -> bool {
3678 if backend() == Backend::Wgpu {
3679 return crate::gpu_wgpu::gram::add_dev(id, x, b);
3680 }
3681 false
3682}
3683
3684pub fn gram_take(id: u64) -> Option<Vec<f64>> {
3686 #[cfg(feature = "gpu")]
3687 if backend() == Backend::Wgpu {
3688 return crate::gpu_wgpu::gram::take(id);
3689 }
3690 let _ = id;
3691 None
3692}
3693
3694pub fn gptq_fold_dev(h_id: u64, n: usize, lambda: f64) -> Option<u64> {
3699 #[cfg(feature = "gpu")]
3700 if backend() == Backend::Wgpu {
3701 return crate::gpu_wgpu::gptq_dev::fold(h_id, n, lambda);
3702 }
3703 let _ = (h_id, n, lambda);
3704 None
3705}
3706
3707pub fn gptq_fold_info(id: u64) -> Option<(Vec<bool>, Vec<f32>, f64)> {
3709 #[cfg(feature = "gpu")]
3710 if backend() == Backend::Wgpu {
3711 return crate::gpu_wgpu::gptq_dev::info(id);
3712 }
3713 let _ = id;
3714 None
3715}
3716
3717pub fn gptq_fold_read(id: u64) -> Option<Vec<f32>> {
3719 #[cfg(feature = "gpu")]
3720 if backend() == Backend::Wgpu {
3721 return crate::gpu_wgpu::gptq_dev::read_u(id);
3722 }
3723 let _ = id;
3724 None
3725}
3726
3727pub fn gptq_fold_free(id: u64) {
3729 #[cfg(feature = "gpu")]
3730 if backend() == Backend::Wgpu {
3731 crate::gpu_wgpu::gptq_dev::free(id);
3732 }
3733 let _ = id;
3734}
3735
3736#[allow(clippy::too_many_arguments)]
3738pub fn gptq_sweep_q4tp_dev(
3739 id: u64,
3740 w: &[f32],
3741 rows: usize,
3742 tab: &[f32],
3743 lost: &[f32],
3744 nib: &mut [u8],
3745 codes: &mut [u32],
3746) -> bool {
3747 #[cfg(feature = "gpu")]
3748 if backend() == Backend::Wgpu {
3749 return crate::gpu_wgpu::gptq_dev::sweep_q4tp(id, w, rows, tab, lost, nib, codes);
3750 }
3751 let _ = (id, w, rows, tab, lost, nib, codes);
3752 false
3753}
3754
3755pub fn gptq_sweep_q2tp_dev(
3757 id: u64,
3758 w: &[f32],
3759 rows: usize,
3760 tab: &[f32],
3761 hw: &[f32],
3762 chunks: &mut [u8],
3763 codes: &mut [u32],
3764) -> bool {
3765 #[cfg(feature = "gpu")]
3766 if backend() == Backend::Wgpu {
3767 return crate::gpu_wgpu::gptq_dev::sweep_q2tp(id, w, rows, tab, hw, chunks, codes);
3768 }
3769 let _ = (id, w, rows, tab, hw, chunks, codes);
3770 false
3771}
3772
3773pub fn gram_read(id: u64) -> Option<Vec<f64>> {
3776 #[cfg(feature = "gpu")]
3777 if backend() == Backend::Wgpu {
3778 return crate::gpu_wgpu::gram::read(id);
3779 }
3780 let _ = id;
3781 None
3782}
3783
3784pub fn gram_clear() {
3786 #[cfg(feature = "gpu")]
3787 if backend() == Backend::Wgpu {
3788 crate::gpu_wgpu::gram::clear();
3789 }
3790}
3791
3792#[allow(unused_variables)]
3797pub fn q4tp_matmat(
3798 model: &Arc<CmfModel>,
3799 idx: usize,
3800 xs: &[f32],
3801 b: usize,
3802 rows: usize,
3803 cols: usize,
3804 out: &mut [f32],
3805) -> bool {
3806 match backend() {
3807 #[cfg(target_os = "macos")]
3808 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3809 #[cfg(feature = "gpu")]
3810 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3811 #[allow(unreachable_patterns)]
3812 _ => false,
3813 }
3814}
3815
3816pub fn q2tp_matmat(
3819 model: &Arc<CmfModel>,
3820 idx: usize,
3821 xs: &[f32],
3822 b: usize,
3823 rows: usize,
3824 cols: usize,
3825 out: &mut [f32],
3826) -> bool {
3827 match backend() {
3828 #[cfg(target_os = "macos")]
3829 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3830 #[cfg(feature = "gpu")]
3831 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3832 #[allow(unreachable_patterns)]
3833 _ => false,
3834 }
3835}
3836
3837pub fn q2tp_affine_matmat(
3840 model: &Arc<CmfModel>,
3841 idx: usize,
3842 xs: &[f32],
3843 b: usize,
3844 rows: usize,
3845 cols: usize,
3846 out: &mut [f32],
3847) -> bool {
3848 match backend() {
3849 #[cfg(target_os = "macos")]
3850 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3851 #[cfg(feature = "gpu")]
3852 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3853 #[allow(unreachable_patterns)]
3854 _ => false,
3855 }
3856}
3857
3858pub fn blr_matmat(t: &crate::blr::BlrTensor, xs: &[f32], b: usize, out: &mut [f32]) -> bool {
3863 match backend() {
3864 #[cfg(feature = "gpu")]
3865 Backend::Wgpu => crate::gpu_wgpu::blr_matmat(t, xs, b, out),
3866 #[allow(unreachable_patterns)]
3867 _ => {
3868 let _ = (t, xs, b, out);
3869 false
3870 }
3871 }
3872}
3873
3874pub fn q2tp_matvec(
3876 model: &Arc<CmfModel>,
3877 idx: usize,
3878 xs: &[f32],
3879 rows: usize,
3880 cols: usize,
3881 out: &mut [f32],
3882) -> bool {
3883 match backend() {
3884 #[cfg(target_os = "macos")]
3885 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
3886 #[cfg(feature = "gpu")]
3887 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
3888 #[allow(unreachable_patterns)]
3889 _ => false,
3890 }
3891}
3892
3893pub fn q2tp_affine_matvec(
3897 model: &Arc<CmfModel>,
3898 idx: usize,
3899 xs: &[f32],
3900 rows: usize,
3901 cols: usize,
3902 out: &mut [f32],
3903) -> bool {
3904 match backend() {
3905 #[cfg(target_os = "macos")]
3906 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3907 #[cfg(feature = "gpu")]
3908 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3909 #[allow(unreachable_patterns)]
3910 _ => false,
3911 }
3912}
3913
3914pub fn q4tp_matvec(
3919 model: &Arc<CmfModel>,
3920 idx: usize,
3921 xs: &[f32],
3922 rows: usize,
3923 cols: usize,
3924 out: &mut [f32],
3925) -> bool {
3926 match backend() {
3927 #[cfg(target_os = "macos")]
3928 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
3929 #[cfg(feature = "gpu")]
3930 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
3931 #[allow(unreachable_patterns)]
3932 _ => false,
3933 }
3934}
3935
3936pub fn q4t_matvec(
3942 model: &Arc<CmfModel>,
3943 idx: usize,
3944 xs: &[f32],
3945 rows: usize,
3946 cols: usize,
3947 out: &mut [f32],
3948) -> bool {
3949 match backend() {
3950 #[cfg(target_os = "macos")]
3951 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
3952 #[allow(unreachable_patterns)]
3953 _ => false,
3954 }
3955}
3956
3957pub fn q4t_matmat(
3958 model: &Arc<CmfModel>,
3959 idx: usize,
3960 xs: &[f32],
3961 b: usize,
3962 rows: usize,
3963 cols: usize,
3964 out: &mut [f32],
3965) -> bool {
3966 match backend() {
3967 #[cfg(target_os = "macos")]
3968 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
3969 #[cfg(feature = "gpu")]
3970 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
3971 #[allow(unreachable_patterns)]
3972 _ => false,
3973 }
3974}
3975
3976#[cfg(target_os = "macos")]
3978pub use crate::gpu_metal::{
3979 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
3980 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
3981};
3982
3983#[cfg(target_os = "macos")]
3985pub fn gdn_block(
3986 model: &Arc<CmfModel>,
3987 layers: &[GdnGpuLayer],
3988 states: &mut [&mut [f32]],
3989 cfg: &GdnGpuCfg,
3990 h: &mut [f32],
3991) -> bool {
3992 match backend() {
3993 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
3994 _ => false,
3995 }
3996}
3997
3998#[allow(unused_variables)]
4000pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
4001 match backend() {
4002 #[cfg(target_os = "macos")]
4005 Backend::Metal if jobs.iter().any(|j| j.act != 0) => false,
4006 #[cfg(target_os = "macos")]
4007 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
4008 #[cfg(feature = "gpu")]
4009 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
4010 Backend::None => false,
4011 }
4012}
4013
4014#[allow(unused_variables)]
4016pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
4017 match backend() {
4018 #[cfg(target_os = "macos")]
4019 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
4020 #[cfg(feature = "gpu")]
4021 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
4022 Backend::None => false,
4023 }
4024}
4025
4026static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
4042static 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)];
4046
4047const GRAPH_RACE_SAMPLES: u32 = 4;
4049
4050pub fn graph_race_begin_generation() {
4069 #[cfg(feature = "gpu")]
4074 {
4075 static FLUSHED: std::sync::Once = std::sync::Once::new();
4087 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
4088 if FIRST.swap(false, Ordering::Relaxed) {
4089 } else {
4091 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
4092 }
4093 }
4094 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
4095 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4096 return;
4097 }
4098 let (gn, cn) = (
4099 GRAPH_N[1].load(Ordering::Relaxed),
4100 GRAPH_N[0].load(Ordering::Relaxed),
4101 );
4102 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
4103 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
4104 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4105 let verdict = if g_avg < c_avg { 1 } else { 2 };
4106 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
4107 tracing::info!(
4108 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
4109 g_avg as f64 / 1e6,
4110 c_avg as f64 / 1e6,
4111 if verdict == 1 { "graph" } else { "normal path" }
4112 );
4113 return;
4114 }
4115 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
4116 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
4117}
4118
4119pub fn graph_race_use_graph(trusted: bool) -> bool {
4123 if trusted {
4124 return true;
4125 }
4126 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
4127 1 => true,
4128 2 => false,
4129 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
4130 }
4131}
4132
4133pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
4138 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4139 return false;
4140 }
4141 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
4142 let cn = GRAPH_N[0].load(Ordering::Relaxed);
4143 if !first || cn == 0 {
4144 return false;
4145 }
4146 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4147 let ns = dur.as_nanos() as u64;
4148 if ns > 1_000_000_000 && ns > 4 * c_avg {
4149 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
4150 tracing::info!(
4151 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
4152 ns as f64 / 1e6,
4153 c_avg as f64 / 1e6
4154 );
4155 return true;
4156 }
4157 false
4158}
4159
4160pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
4164 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4165 return;
4166 }
4167 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
4168 if tok == 0 {
4169 return;
4170 }
4171 let i = used_graph as usize;
4172 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
4173 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
4174}
4175
4176pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
4186 #[inline]
4187 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
4188 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
4189 for c in chunks.chunks_exact(8) {
4190 h ^= u64::from_le_bytes(c.try_into().unwrap());
4191 h = h.wrapping_mul(0x100_0000_01b3);
4192 }
4193 for &b in tail {
4194 h ^= b as u64;
4195 h = h.wrapping_mul(0x100_0000_01b3);
4196 }
4197 h
4198 }
4199 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
4200 if data.len() <= 4096 {
4201 return fnv(h, data);
4202 }
4203 let step = (data.len() - 64) / 63;
4204 for i in 0..64 {
4205 h = fnv(h, &data[i * step..i * step + 64]);
4206 }
4207 h
4208}
4209
4210pub(crate) fn fp_f32(data: &[f32]) -> u64 {
4213 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
4214 fp_bytes(bytes)
4215}
4216
4217#[cfg(test)]
4218mod fp_tests {
4219 use super::fp_bytes;
4220
4221 #[test]
4226 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
4227 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
4229 let h0 = fp_bytes(&base);
4230 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
4231 let mut dense = base.clone();
4234 for b in dense.iter_mut() {
4235 *b = b.wrapping_add(1);
4236 }
4237 assert_ne!(
4238 h0,
4239 fp_bytes(&dense),
4240 "a fully different tensor slipped through"
4241 );
4242 assert_ne!(h0, fp_bytes(&base[..n - 64]));
4245 let mut small = vec![3u8; 4096];
4248 let hs = fp_bytes(&small);
4249 small[2048] ^= 1;
4250 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
4251 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
4253 let v = vec![9u8; n];
4254 let _ = fp_bytes(&v); }
4256 }
4257}
4258
4259pub fn bake_release() {
4263 #[cfg(feature = "gpu")]
4264 crate::gpu_wgpu::bake_release();
4265}
4266
4267pub fn bake_precision_strict(on: bool) {
4271 #[cfg(feature = "gpu")]
4272 crate::gpu_wgpu::bake_precision_strict(on);
4273 #[cfg(not(feature = "gpu"))]
4274 let _ = on;
4275}
4276
4277pub fn hostprof_encode_done(t0: std::time::Instant) {
4283 use std::sync::atomic::{AtomicU64, Ordering};
4284 static ENC: AtomicU64 = AtomicU64::new(0);
4285 static N: AtomicU64 = AtomicU64::new(0);
4286 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4287 return;
4288 }
4289 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4290 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4291 if n % 100 == 0 {
4292 eprintln!(
4293 "hostprof: encode {:.2} ms/token over {n} tokens",
4294 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4295 );
4296 }
4297}
4298
4299pub fn hostprof_total(t0: std::time::Instant) {
4300 use std::sync::atomic::{AtomicU64, Ordering};
4301 static TOT: AtomicU64 = AtomicU64::new(0);
4302 static N: AtomicU64 = AtomicU64::new(0);
4303 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4304 return;
4305 }
4306 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4307 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4308 if n % 100 == 0 {
4309 eprintln!(
4310 "hostprof: total {:.2} ms/token over {n} tokens",
4311 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4312 );
4313 }
4314}
4315
4316pub fn stageprof(stage: u32, dt: std::time::Duration) {
4320 use std::sync::atomic::{AtomicU64, Ordering};
4321 static NS: [AtomicU64; 4] = [
4322 AtomicU64::new(0),
4323 AtomicU64::new(0),
4324 AtomicU64::new(0),
4325 AtomicU64::new(0),
4326 ];
4327 static N: AtomicU64 = AtomicU64::new(0);
4328 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4329 return;
4330 }
4331 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
4332 if stage == 1 {
4333 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4334 if n % 200 == 0 {
4335 eprintln!(
4336 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
4337 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4338 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4339 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4340 );
4341 }
4342 }
4343}
4344
4345pub fn weight_bytes_dispatched() -> u64 {
4348 let mut total = 0u64;
4349 #[cfg(target_os = "macos")]
4350 {
4351 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4352 }
4353 #[cfg(feature = "gpu")]
4354 {
4355 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4356 }
4357 total
4358}
4359
4360pub fn weight_bytes_by() -> [u64; 6] {
4363 #[cfg(target_os = "macos")]
4364 {
4365 let mut o = [0u64; 6];
4366 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
4367 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
4368 }
4369 return o;
4370 }
4371 #[allow(unreachable_code)]
4372 [0; 6]
4373}
4374
4375#[cfg(test)]
4376mod f16_guard_tests {
4377 use super::*;
4378
4379 #[test]
4382 fn abs_max_or_inf_is_the_magnitude_maximum() {
4383 let mut xs: Vec<f32> = (0..37).map(|i| (i as f32 - 18.0) * 0.5).collect();
4384 assert_eq!(abs_max_or_inf(&xs), 9.0);
4385 xs[36] = -70000.0; assert_eq!(abs_max_or_inf(&xs), 70000.0);
4387 xs[3] = f32::NAN; assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4389 xs[3] = 0.0;
4390 xs[35] = f32::NEG_INFINITY;
4391 assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4392 assert_eq!(abs_max_or_inf(&[]), 0.0);
4393 assert_eq!(abs_max_or_inf(&[-0.0, -1e-30]), 1e-30);
4394 }
4395}
4396
4397#[cfg(test)]
4398mod probe_warmup_tests {
4399 use super::*;
4400 use std::time::Duration;
4401
4402 fn ms(v: f64) -> Duration {
4403 Duration::from_nanos((v * 1e6) as u64)
4404 }
4405
4406 #[test]
4411 fn one_cold_first_sample_does_not_lose_the_class() {
4412 let p = Probe::new();
4413 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
4415 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
4416 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
4417 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
4418 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
4419 assert_eq!(
4420 p.state.load(Ordering::Relaxed),
4421 1,
4422 "the device is 3x faster once warm and must win"
4423 );
4424 }
4425
4426 #[test]
4430 fn the_warmup_is_spent_once_and_never_underflows() {
4431 let p = Probe::new();
4432 for _ in 0..8 {
4433 probe_record_into(&p, "matmat", None, true, ms(10.0));
4434 }
4435 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
4436 assert_eq!(
4437 p.gpu_n.load(Ordering::Relaxed),
4438 7,
4439 "one sample burned, the rest counted"
4440 );
4441 }
4442
4443 #[test]
4449 fn a_class_whose_device_always_declines_settles_on_the_host() {
4450 let _probe_guard = probe_test_guard();
4451 let c = OpClass::MatmatWide;
4455 let p = &PROBES[c as usize];
4456 p.state.store(0, Ordering::Relaxed);
4457 p.declines.store(0, Ordering::Relaxed);
4458 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
4459 probe_note_decline(c);
4460 }
4461 assert_eq!(
4462 p.state.load(Ordering::Relaxed),
4463 0,
4464 "one short of the limit is still a question, not an answer"
4465 );
4466 probe_note_decline(c);
4467 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
4468 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
4469 p.state.store(0, Ordering::Relaxed);
4470 p.declines.store(0, Ordering::Relaxed);
4471 }
4472
4473 #[test]
4476 fn a_slow_device_still_loses_after_the_warmup() {
4477 let p = Probe::new();
4478 for _ in 0..4 {
4479 probe_record_into(&p, "matvec", None, true, ms(40.0));
4480 }
4481 for _ in 0..4 {
4482 probe_record_into(&p, "matvec", None, false, ms(2.0));
4483 }
4484 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
4485 }
4486}
4487
4488pub(crate) struct ImageStageGuard {
4491 #[cfg(target_os = "macos")]
4492 metal: Option<crate::gpu_metal::ImageStageGuard>,
4493 #[cfg(feature = "gpu")]
4494 wgpu: crate::gpu_wgpu::ImageStageGuard,
4495}
4496
4497pub(crate) fn image_stage_scope() -> ImageStageGuard {
4498 ImageStageGuard {
4499 #[cfg(target_os = "macos")]
4500 metal: if matches!(backend(), Backend::Metal) {
4501 Some(crate::gpu_metal::image_stage_scope())
4502 } else {
4503 None
4504 },
4505 #[cfg(feature = "gpu")]
4506 wgpu: crate::gpu_wgpu::image_stage_scope(),
4507 }
4508}
4509
4510impl ImageStageGuard {
4511 pub(crate) fn track_model(&mut self, uid: u64) {
4512 #[cfg(target_os = "macos")]
4513 if let Some(metal) = &mut self.metal {
4514 metal.track_model(uid);
4515 }
4516 #[cfg(feature = "gpu")]
4517 self.wgpu.track_model(uid);
4518 #[cfg(not(target_os = "macos"))]
4519 let _ = uid;
4520 }
4521}
4522
4523#[derive(Clone, Copy, Debug, PartialEq)]
4543pub struct Qi21Geom {
4544 pub hidden: usize,
4545 pub nh: usize,
4546 pub hd: usize,
4547 pub inter: usize,
4548 pub in_ch: usize,
4549 pub eps: f32,
4550}
4551
4552pub struct Qi21BlockRef<'a> {
4555 pub w: [usize; 7],
4556 pub norm_q: &'a [f32],
4557 pub norm_k: &'a [f32],
4558}
4559
4560pub struct Qi21PrefillArgs<'a> {
4562 pub model: &'a Arc<CmfModel>,
4563 pub geom: Qi21Geom,
4564 pub blocks: &'a [Qi21BlockRef<'a>],
4565 pub img_in: &'a [f32],
4567 pub proj_out: &'a [f32],
4568 pub key: u64,
4569 pub x: &'a [f32],
4571 pub lp: usize,
4572 pub rope_p: (&'a [f32], &'a [f32]),
4574 pub rope_t: (&'a [f32], &'a [f32]),
4575 pub vis: &'a [u32],
4577 pub mods0: &'a [f32],
4579 pub n: usize,
4581}
4582
4583#[allow(unused_variables)]
4585pub fn qi21_prefill(a: &Qi21PrefillArgs) -> bool {
4586 match backend() {
4587 #[cfg(target_os = "macos")]
4588 Backend::Metal => crate::gpu_metal::qi21::prefill(a),
4589 #[cfg(feature = "gpu")]
4590 Backend::Wgpu => crate::gpu_wgpu::qi21::prefill(a),
4591 #[allow(unreachable_patterns)]
4592 _ => false,
4593 }
4594}
4595
4596#[allow(unused_variables)]
4598pub fn qi21_step(key: u64, xtok: &[f32], mods: &[f32], fs: &[f32], out: &mut [f32]) -> bool {
4599 match backend() {
4600 #[cfg(target_os = "macos")]
4601 Backend::Metal => crate::gpu_metal::qi21::step(key, xtok, mods, fs, out),
4602 #[cfg(feature = "gpu")]
4603 Backend::Wgpu => crate::gpu_wgpu::qi21::step(key, xtok, mods, fs, out),
4604 #[allow(unreachable_patterns)]
4605 _ => false,
4606 }
4607}
4608
4609#[allow(unused_variables)]
4612pub fn qi21_release_key(key: u64) {
4613 #[cfg(target_os = "macos")]
4614 if matches!(backend(), Backend::Metal) {
4615 crate::gpu_metal::qi21::release_key(key);
4616 }
4617 #[cfg(feature = "gpu")]
4618 crate::gpu_wgpu::qi21::release_key(key);
4619}
4620
4621pub fn qi21_release() {
4624 #[cfg(target_os = "macos")]
4625 if matches!(backend(), Backend::Metal) {
4626 crate::gpu_metal::qi21::release();
4627 }
4628 #[cfg(feature = "gpu")]
4629 {
4630 crate::gpu_wgpu::qi21::release();
4631 crate::gpu_wgpu::qi21_vae::release();
4632 }
4633}
4634
4635#[derive(Clone, Copy)]
4638pub struct Qi21VaeConvRef<'a> {
4639 pub w: &'a [f32],
4640 pub b: &'a [f32],
4641 pub ci: usize,
4642 pub co: usize,
4643 pub k: usize,
4644}
4645
4646pub struct Qi21VaeResRef<'a> {
4649 pub g1: &'a [f32],
4650 pub c1: Qi21VaeConvRef<'a>,
4651 pub g2: &'a [f32],
4652 pub c2: Qi21VaeConvRef<'a>,
4653 pub shortcut: Option<Qi21VaeConvRef<'a>>,
4654}
4655
4656pub struct Qi21VaeUpRef<'a> {
4660 pub resnets: Vec<Qi21VaeResRef<'a>>,
4661 pub up: Option<(Qi21VaeConvRef<'a>, usize)>,
4662 pub in_dim: usize,
4663 pub out_dim: usize,
4664}
4665
4666pub struct Qi21VaeDecodeArgs<'a> {
4669 pub key: u64,
4671 pub post_quant: Qi21VaeConvRef<'a>,
4672 pub conv_in: Qi21VaeConvRef<'a>,
4673 pub mid_res: [Qi21VaeResRef<'a>; 2],
4674 pub attn_gamma: &'a [f32],
4677 pub attn_qkv: Qi21VaeConvRef<'a>,
4678 pub attn_proj: Qi21VaeConvRef<'a>,
4679 pub ups: Vec<Qi21VaeUpRef<'a>>,
4680 pub norm_out: &'a [f32],
4681 pub conv_out: Qi21VaeConvRef<'a>,
4682}
4683
4684#[allow(unused_variables)]
4688pub fn qi21_vae_decode(a: &Qi21VaeDecodeArgs, z: &[f32], h: usize, w: usize, out: &mut [f32]) -> bool {
4689 match backend() {
4690 #[cfg(feature = "gpu")]
4691 Backend::Wgpu => crate::gpu_wgpu::qi21_vae::decode(a, z, h, w, out),
4692 #[allow(unreachable_patterns)]
4693 _ => false,
4694 }
4695}
4696
4697#[derive(Clone, Copy)]
4705pub struct ZBlockRef<'a> {
4706 pub wq: usize,
4708 pub wk: usize,
4709 pub wv: usize,
4710 pub wo: usize,
4711 pub w1: usize,
4714 pub w3: usize,
4715 pub w2: usize,
4716 pub norm1: &'a [f32],
4718 pub norm2: &'a [f32],
4719 pub ffn_norm1: &'a [f32],
4721 pub ffn_norm2: &'a [f32],
4722 pub norm_q: &'a [f32],
4724 pub norm_k: &'a [f32],
4725}
4726
4727#[derive(Clone, Copy, Debug, PartialEq)]
4731pub struct ZGeom {
4732 pub hidden: usize,
4733 pub nh: usize,
4734 pub hd: usize,
4735 pub inter: usize,
4736 pub eps: f32,
4737 pub final_eps: f32,
4738 pub patch_dim: usize,
4739}
4740
4741pub struct ZPrepareArgs<'a> {
4745 pub model: &'a Arc<CmfModel>,
4746 pub geom: ZGeom,
4747 pub key: u64,
4750 pub n_img: usize,
4753 pub n_img_p: usize,
4754 pub n_cap_p: usize,
4755 pub grid: (usize, usize),
4758 pub cap: &'a [f32],
4760 pub rope_img: (&'a [f32], &'a [f32]),
4763 pub rope_joint: (&'a [f32], &'a [f32]),
4765 pub x_emb_w: &'a [f32],
4768 pub x_emb_b: &'a [f32],
4769 pub x_pad: &'a [f32],
4770 pub final_w: &'a [f32],
4772 pub final_b: &'a [f32],
4773 pub noise_refiner: &'a [ZBlockRef<'a>],
4775 pub layers: &'a [ZBlockRef<'a>],
4776 pub mods_all: Option<&'a [f32]>,
4782 pub final_scale_all: Option<&'a [f32]>,
4783 pub neg: Option<ZNegArgs<'a>>,
4789}
4790
4791pub struct ZNegArgs<'a> {
4795 pub cap: &'a [f32],
4797 pub n_cap_p: usize,
4798 pub rope_img: (&'a [f32], &'a [f32]),
4800 pub rope_joint: (&'a [f32], &'a [f32]),
4802}
4803
4804pub struct ZStepArgs<'a> {
4806 pub key: u64,
4809 pub step: usize,
4812 pub x_tok: &'a [f32],
4816 pub mods: &'a [f32],
4820 pub final_scale: &'a [f32],
4822 pub out: &'a mut [f32],
4825 pub out_neg: Option<&'a mut [f32]>,
4828}
4829
4830#[allow(unused_variables)]
4834pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
4835 match backend() {
4836 #[cfg(target_os = "macos")]
4837 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
4838 #[cfg(feature = "gpu")]
4839 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
4840 #[allow(unreachable_patterns)]
4841 _ => false,
4842 }
4843}
4844
4845#[allow(unused_variables)]
4849pub fn zimage_step(a: &mut ZStepArgs) -> bool {
4850 match backend() {
4851 #[cfg(target_os = "macos")]
4852 Backend::Metal => crate::gpu_metal::zimage::step(a),
4853 #[cfg(feature = "gpu")]
4854 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
4855 #[allow(unreachable_patterns)]
4856 _ => false,
4857 }
4858}
4859
4860#[allow(unused_variables)]
4865pub fn zimage_preload(
4866 model: &Arc<CmfModel>,
4867 geom: &ZGeom,
4868 noise_refiner: &[ZBlockRef],
4869 layers: &[ZBlockRef],
4870 context_refiner: &[ZBlockRef],
4871) -> bool {
4872 match backend() {
4873 #[cfg(target_os = "macos")]
4874 Backend::Metal => {
4875 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
4876 }
4877 #[cfg(feature = "gpu")]
4878 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
4879 #[allow(unreachable_patterns)]
4880 _ => false,
4881 }
4882}
4883
4884pub fn zimage_flush_pipelines() {
4888 #[cfg(feature = "gpu")]
4889 if matches!(backend(), Backend::Wgpu) {
4890 crate::gpu_wgpu::pipeline_cache_flush();
4891 }
4892}
4893
4894pub fn zimage_warmup() -> bool {
4899 match backend() {
4900 #[cfg(target_os = "macos")]
4901 Backend::Metal => crate::gpu_metal::zimage::warmup(),
4902 #[cfg(feature = "gpu")]
4903 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
4904 #[allow(unreachable_patterns)]
4905 _ => false,
4906 }
4907}
4908
4909#[allow(unused_variables)]
4913pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
4914 match backend() {
4915 #[cfg(target_os = "macos")]
4916 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
4917 #[cfg(feature = "gpu")]
4918 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
4919 #[allow(unreachable_patterns)]
4920 _ => false,
4921 }
4922}
4923
4924pub fn zimage_release_dit() {
4927 #[cfg(target_os = "macos")]
4928 crate::gpu_metal::zimage::release_dit();
4929 #[cfg(feature = "gpu")]
4930 crate::gpu_wgpu::zimage::release_dit();
4931}
4932
4933pub fn zimage_release() {
4938 #[cfg(target_os = "macos")]
4939 crate::gpu_metal::zimage::release();
4940 #[cfg(feature = "gpu")]
4941 crate::gpu_wgpu::zimage::release();
4942}
4943
4944#[allow(unused_variables)]
4950pub fn zimage_refine_caption(
4951 model: &Arc<CmfModel>,
4952 geom: &ZGeom,
4953 blocks: &[ZBlockRef],
4954 rope_cap: (&[f32], &[f32]),
4955 cap: &mut [f32],
4956) -> bool {
4957 match backend() {
4958 #[cfg(target_os = "macos")]
4959 Backend::Metal => {
4960 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4961 }
4962 #[cfg(feature = "gpu")]
4963 Backend::Wgpu => {
4964 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4965 }
4966 #[allow(unreachable_patterns)]
4967 _ => false,
4968 }
4969}
4970
4971#[allow(unused_variables)]
4977pub fn vae_decode_chain(
4978 a: &crate::vae::VaeChainArgs,
4979 z: &[f32],
4980 h: usize,
4981 w: usize,
4982 out: &mut [f32],
4983) -> bool {
4984 match backend() {
4985 #[cfg(target_os = "macos")]
4986 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
4987 #[cfg(feature = "gpu")]
4988 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
4989 #[allow(unreachable_patterns)]
4990 _ => false,
4991 }
4992}