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 const GRAPH_KIND_BLR: u8 = 11;
1579
1580pub struct GraphW<'a> {
1584 pub idx: usize,
1585 pub kind: u8,
1586 pub row_scale: &'a [f32],
1587 pub data: &'a [f32],
1588 pub prism: GraphPrismOp,
1589 pub affine: bool,
1590}
1591
1592pub enum GraphAttn<'a> {
1595 Full {
1596 wq: GraphW<'a>,
1597 wk: GraphW<'a>,
1598 wv: GraphW<'a>,
1599 wo: GraphW<'a>,
1600 q_norm: Option<&'a [f32]>,
1601 k_norm: Option<&'a [f32]>,
1602 late_qk_norm: bool,
1604 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1606 output_gate: bool,
1609 cpu_k: &'a [Vec<f32>],
1610 cpu_v: &'a [Vec<f32>],
1611 cpu_base: usize,
1615 geom: Option<GraphAttnGeom<'a>>,
1622 head_gate: Option<GraphW<'a>>,
1627 },
1628 Gdn {
1629 qkv: GraphW<'a>,
1630 z: GraphW<'a>,
1631 a: GraphW<'a>,
1632 b: GraphW<'a>,
1633 out: GraphW<'a>,
1634 conv1d: &'a [f32],
1635 a_log: &'a [f32],
1636 dt_bias: &'a [f32],
1637 norm: &'a [f32],
1638 nv: usize,
1639 nk: usize,
1640 dk: usize,
1641 dv: usize,
1642 kk: usize,
1643 cpu_state: &'a [f32],
1648 },
1649 ShortConv {
1656 inp: GraphW<'a>,
1658 out: GraphW<'a>,
1660 taps: &'a [f32],
1663 kernel: usize,
1664 cpu_state: &'a [f32],
1668 },
1669}
1670
1671#[derive(Clone, Copy)]
1676pub struct GraphAttnGeom<'a> {
1677 pub nkv: usize,
1679 pub hd: usize,
1683 pub dv: usize,
1685 pub rd: usize,
1687 pub invf: &'a [f32],
1689 pub rope_scale: f32,
1694 pub window: Option<usize>,
1697 pub sink: Option<&'a [f32]>,
1700 pub v_norm: bool,
1703}
1704
1705#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1709pub enum GraphAct {
1710 Silu,
1711 GeluErf,
1714 GeluTanh,
1717}
1718
1719impl GraphAct {
1720 pub fn code(self) -> u32 {
1721 match self {
1722 Self::Silu => 0,
1723 Self::GeluErf => 1,
1724 Self::GeluTanh => 2,
1725 }
1726 }
1727}
1728
1729pub struct GraphLayer<'a> {
1731 pub input_norm: &'a [f32],
1732 pub attn: GraphAttn<'a>,
1733 pub post_norm: &'a [f32],
1734 pub ffn: GraphFfn<'a>,
1735 pub attn_out_norm: Option<&'a [f32]>,
1738 pub ffn_out_norm: Option<&'a [f32]>,
1739 pub layer_scale: Option<f32>,
1742}
1743
1744pub enum GraphFfn<'a> {
1749 AttentionOnly,
1752 Dense {
1753 gate: GraphW<'a>,
1754 up: GraphW<'a>,
1755 down: GraphW<'a>,
1756 act: GraphAct,
1759 },
1760 Moe {
1761 router: GraphW<'a>,
1763 shared_gate: GraphW<'a>,
1765 experts: Vec<(usize, usize, usize)>,
1769 n_exp: usize,
1771 top_k: usize,
1772 inter: usize,
1773 norm_topk: bool,
1774 q4tp: bool,
1780 gu_q2: bool,
1784 q82: bool,
1789 sigmoid: bool,
1793 bias: Option<&'a [f32]>,
1796 has_shared: bool,
1800 shared_gated: bool,
1805 route_scale: f32,
1808 },
1809}
1810
1811#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1814pub enum TokenGraphOutcome {
1815 Declined,
1817 Completed,
1819 Failed,
1821}
1822
1823#[allow(clippy::too_many_arguments)]
1828pub fn forward_token_graph(
1829 model: &Arc<CmfModel>,
1830 kv_id: u64,
1831 layers: &[GraphLayer],
1832 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1835 o1_epoch: u64,
1836 invf: &[f32],
1837 h: &mut [f32],
1838 nh: usize,
1839 nkv: usize,
1840 hd: usize,
1841 attn_scale: f32,
1842 rd: usize,
1843 hidden: usize,
1844 inter: usize,
1845 position: usize,
1846 cap: usize,
1847 gemma: bool,
1848 eps: f32,
1849 lm_head: Option<(&GraphW, usize)>,
1850 final_norm: &[f32],
1851 logits: &mut Vec<f32>,
1852 loop_norm_at: &[usize],
1853 steps: usize,
1854 embed: Option<(&GraphW, usize, f32)>,
1855 ids_out: Option<&mut Vec<u32>>,
1856 layers_run: Option<&mut usize>,
1859 layer_base: usize,
1863 hidden_too: bool,
1865) -> TokenGraphOutcome {
1866 match backend() {
1867 #[cfg(feature = "gpu")]
1868 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1869 model,
1870 kv_id,
1871 layers,
1872 o1,
1873 o1_epoch,
1874 invf,
1875 h,
1876 nh,
1877 nkv,
1878 hd,
1879 attn_scale,
1880 rd,
1881 hidden,
1882 inter,
1883 position,
1884 cap,
1885 gemma,
1886 eps,
1887 lm_head,
1888 final_norm,
1889 logits,
1890 loop_norm_at,
1891 steps,
1892 embed,
1893 ids_out,
1894 layers_run,
1895 layer_base,
1896 hidden_too,
1897 ),
1898 #[allow(unused_variables)]
1899 _ => {
1900 let _ = (
1901 attn_scale,
1902 lm_head,
1903 final_norm,
1904 logits,
1905 loop_norm_at,
1906 layers_run,
1907 layer_base,
1908 hidden_too,
1909 );
1910 TokenGraphOutcome::Declined
1911 }
1912 }
1913}
1914
1915pub fn forward_embryo_graph(
1920 model: &Arc<EmbryoGraphModel>,
1921 kv_id: u64,
1922 hidden: &[f32],
1923 position: usize,
1924 logits: &mut Vec<f32>,
1925) -> bool {
1926 #[cfg(feature = "gpu")]
1927 if backend() == Backend::Wgpu {
1928 return crate::gpu_wgpu::forward_embryo_graph(model, kv_id, hidden, position, logits);
1929 }
1930 let _ = (model, kv_id, hidden, position, logits);
1931 false
1932}
1933
1934#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1938pub enum BatchGraphOutcome {
1939 Declined,
1942 Completed,
1944 Failed,
1948}
1949
1950pub struct SpecTail<'a> {
1951 pub lm: GraphW<'a>,
1952 pub lm_rows: usize,
1953 pub final_norm: &'a [f32],
1954 pub logits_out: &'a mut Vec<f32>,
1955}
1956
1957#[allow(clippy::too_many_arguments)]
1961pub fn forward_batch_graph(
1962 model: &Arc<CmfModel>,
1963 kv_id: u64,
1964 layers: &[GraphLayer],
1965 invf: &[f32],
1966 h: &mut [f32],
1967 nh: usize,
1968 nkv: usize,
1969 hd: usize,
1970 rd: usize,
1971 hidden: usize,
1972 inter: usize,
1973 positions: &[usize],
1974 cap: usize,
1975 gemma: bool,
1976 eps: f32,
1977 attn_scale: f32,
1978 k: usize,
1979 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1982 o1_epoch: u64,
1983 spec: Option<SpecTail<'_>>,
1984 layers_run: Option<&mut usize>,
1990) -> BatchGraphOutcome {
1991 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)
1992}
1993
1994thread_local! {
1995 static MIMO_ATTN_SCRATCH: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
1996}
1997
1998pub(crate) fn mimo_attention_scratch_enabled() -> bool {
1999 MIMO_ATTN_SCRATCH.with(std::cell::Cell::get)
2000}
2001
2002#[doc(hidden)]
2005pub fn mimo_attention_scratch_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2006 struct Restore(bool);
2007 impl Drop for Restore {
2008 fn drop(&mut self) {
2009 MIMO_ATTN_SCRATCH.with(|v| v.set(self.0));
2010 }
2011 }
2012 let _restore = Restore(MIMO_ATTN_SCRATCH.with(|v| v.replace(enabled)));
2013 f()
2014}
2015
2016pub fn g4_batch_on() -> bool {
2019 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2020 *ON.get_or_init(|| std::env::var("CMF_G4_BATCH").as_deref() != Ok("0"))
2021}
2022
2023pub(crate) fn moe_q82_graph_on() -> bool {
2026 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2027 *ON.get_or_init(|| std::env::var("CMF_MOE_Q82").as_deref() != Ok("0"))
2028}
2029
2030thread_local! {
2031 static MIMO_Q8_SHORT: std::cell::Cell<bool> = const { std::cell::Cell::new(true) };
2032}
2033
2034pub(crate) fn mimo_q8_short_enabled() -> bool {
2035 MIMO_Q8_SHORT.with(std::cell::Cell::get)
2036}
2037
2038#[doc(hidden)]
2040pub fn mimo_q8_short_scope<R>(enabled: bool, f: impl FnOnce() -> R) -> R {
2041 struct Restore(bool);
2042 impl Drop for Restore {
2043 fn drop(&mut self) {
2044 MIMO_Q8_SHORT.with(|v| v.set(self.0));
2045 }
2046 }
2047 let _restore = Restore(MIMO_Q8_SHORT.with(|v| v.replace(enabled)));
2048 f()
2049}
2050
2051#[allow(clippy::too_many_arguments)]
2053pub fn forward_batch_graph_at(
2054 model: &Arc<CmfModel>,
2055 kv_id: u64,
2056 layer_base: usize,
2057 layers: &[GraphLayer],
2058 invf: &[f32],
2059 h: &mut [f32],
2060 nh: usize,
2061 nkv: usize,
2062 hd: usize,
2063 rd: usize,
2064 hidden: usize,
2065 inter: usize,
2066 positions: &[usize],
2067 cap: usize,
2068 gemma: bool,
2069 eps: f32,
2070 attn_scale: f32,
2071 k: usize,
2072 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
2075 o1_epoch: u64,
2076 spec: Option<SpecTail<'_>>,
2077 layers_run: Option<&mut usize>,
2083) -> BatchGraphOutcome {
2084 match backend() {
2085 #[cfg(feature = "gpu")]
2086 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph_at(
2087 model, kv_id, layer_base, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions,
2088 cap, gemma, eps, attn_scale, k, o1, o1_epoch, spec, layers_run,
2089 ),
2090 #[allow(unreachable_patterns)]
2091 _ => {
2092 let _ = (o1, o1_epoch, spec, layers_run);
2093 BatchGraphOutcome::Declined
2094 }
2095 }
2096}
2097
2098pub fn gdn_spec_restore(kv_id: u64, slot: usize, base_pos: usize, expected_layers: usize) -> bool {
2103 #[cfg(feature = "gpu")]
2104 if backend() == Backend::Wgpu {
2105 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot, base_pos, expected_layers);
2106 }
2107 #[allow(unreachable_code)]
2108 {
2109 let _ = (kv_id, slot, base_pos, expected_layers);
2110 false
2111 }
2112}
2113
2114pub fn graph_kv_set_stored(kv_id: u64, layer: usize, stored: usize) -> bool {
2120 #[cfg(feature = "gpu")]
2121 if backend() == Backend::Wgpu {
2122 return crate::gpu_wgpu::kv_mirror_set_stored(kv_id, layer, stored);
2123 }
2124 #[cfg(target_os = "macos")]
2125 if backend() == Backend::Metal {
2126 crate::gpu_metal::kv_mirror_set_stored(kv_id, layer, stored);
2127 return true;
2128 }
2129 false
2130}
2131
2132pub fn graph_kv_stored(_kv_id: u64, _layer: usize) -> Option<usize> {
2136 #[cfg(feature = "gpu")]
2137 if backend() == Backend::Wgpu {
2138 return crate::gpu_wgpu::kv_mirror_stored(_kv_id, _layer);
2139 }
2140 None
2141}
2142
2143pub fn graph_state_resident(_kv_id: u64, _layer: usize) -> bool {
2146 #[cfg(feature = "gpu")]
2147 if backend() == Backend::Wgpu {
2148 return crate::gpu_wgpu::graph_state_resident(_kv_id, _layer);
2149 }
2150 false
2151}
2152
2153pub fn graph_kv_read_rows(
2157 _kv_id: u64,
2158 _reqs: &[(usize, usize, usize)],
2159 _nkv: usize,
2160 _hd: usize,
2161) -> Option<Vec<(Vec<f32>, Vec<f32>)>> {
2162 #[cfg(feature = "gpu")]
2163 if backend() == Backend::Wgpu {
2164 return crate::gpu_wgpu::kv_mirror_read_rows(_kv_id, _reqs, _nkv, _hd);
2165 }
2166 None
2167}
2168
2169pub fn graph_kv_pull_host(
2175 _kv_id: u64,
2176 _layer: usize,
2177 _from: usize,
2178 _to: usize,
2179 _nkv: usize,
2180 _hd: usize,
2181) -> Option<(Vec<f32>, Vec<f32>, usize)> {
2182 #[cfg(feature = "gpu")]
2183 if backend() == Backend::Wgpu {
2184 return crate::gpu_wgpu::kv_mirror_pull_host(_kv_id, _layer, _from, _to, _nkv, _hd);
2185 }
2186 None
2187}
2188
2189pub fn graph_kv_reset(_kv_id: u64) {
2191 #[cfg(feature = "gpu")]
2192 if backend() == Backend::Wgpu {
2193 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
2194 crate::gpu_wgpu::embryo_graph_reset(_kv_id);
2195 }
2196}
2197
2198pub fn q1t_matvec(
2202 model: &Arc<CmfModel>,
2203 idx: usize,
2204 xs: &[f32],
2205 rows: usize,
2206 cols: usize,
2207 out: &mut [f32],
2208) -> bool {
2209 match backend() {
2210 #[cfg(target_os = "macos")]
2211 Backend::Metal => {
2212 if metal_q1t_enabled() {
2213 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
2214 } else {
2215 false
2216 }
2217 }
2218 #[cfg(feature = "gpu")]
2219 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
2220 Backend::None => false,
2221 }
2222}
2223
2224#[allow(unused_variables)]
2227pub fn q4b_matvec(
2228 model: &Arc<CmfModel>,
2229 idx: usize,
2230 xs: &[f32],
2231 rows: usize,
2232 cols: usize,
2233 out: &mut [f32],
2234) -> bool {
2235 match backend() {
2236 #[cfg(target_os = "macos")]
2237 Backend::Metal => false,
2238 #[cfg(feature = "gpu")]
2239 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
2240 Backend::None => false,
2241 }
2242}
2243
2244pub fn q1t_matmat(
2247 model: &Arc<CmfModel>,
2248 idx: usize,
2249 xs: &[f32],
2250 b: usize,
2251 rows: usize,
2252 cols: usize,
2253 out: &mut [f32],
2254) -> bool {
2255 match backend() {
2256 #[cfg(target_os = "macos")]
2257 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
2261 #[cfg(feature = "gpu")]
2262 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
2263 Backend::None => false,
2264 }
2265}
2266
2267#[cfg(target_os = "macos")]
2271pub(crate) fn metal_q1t_enabled() -> bool {
2272 std::env::var("CMF_METAL_Q1T")
2273 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
2274 .unwrap_or(true)
2275}
2276
2277pub fn q1_matmat(
2279 model: &Arc<CmfModel>,
2280 idx: usize,
2281 xs: &[f32],
2282 b: usize,
2283 rows: usize,
2284 cols: usize,
2285 out: &mut [f32],
2286) -> bool {
2287 match backend() {
2288 #[cfg(feature = "gpu")]
2289 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
2290 #[allow(unused_variables)]
2291 _ => false,
2292 }
2293}
2294
2295pub fn q1g_matmat(
2297 model: &Arc<CmfModel>,
2298 idx: usize,
2299 xs: &[f32],
2300 b: usize,
2301 rows: usize,
2302 cols: usize,
2303 out: &mut [f32],
2304) -> bool {
2305 match backend() {
2306 #[cfg(feature = "gpu")]
2307 Backend::Wgpu => crate::gpu_wgpu::q1g_matmat(model, idx, xs, b, rows, cols, out),
2308 #[allow(unused_variables)]
2309 _ => false,
2310 }
2311}
2312
2313static MM_KILL: AtomicBool = AtomicBool::new(false);
2318pub(crate) fn mm_killed() -> bool {
2319 MM_KILL.load(Ordering::Relaxed)
2320}
2321pub(crate) fn mm_kill() {
2322 MM_KILL.store(true, Ordering::Relaxed);
2323}
2324
2325static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
2332const MM_STRIKES_TO_KILL: u32 = 3;
2333static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2340
2341pub fn mm_kill_arm(on: bool) {
2344 MM_ARMED.store(on, Ordering::Relaxed);
2345 if on {
2346 MM_STRIKES.store(0, Ordering::Relaxed);
2347 }
2348}
2349
2350pub(crate) fn mm_budget_check(
2357 what: &str,
2358 el: std::time::Duration,
2359 budget: std::time::Duration,
2360 exempt: bool,
2361) {
2362 if el <= budget {
2363 MM_STRIKES.store(0, Ordering::Relaxed);
2364 return;
2365 }
2366 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
2367 return;
2368 }
2369 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
2370 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
2371 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
2372 if !on {
2373 tracing::info!(
2374 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
2375 );
2376 return;
2377 }
2378 if n >= MM_STRIKES_TO_KILL {
2379 tracing::warn!(
2380 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
2381 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
2382 );
2383 mm_kill();
2384 } else {
2385 tracing::info!(
2386 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
2387 );
2388 }
2389}
2390
2391#[allow(unused_variables, clippy::too_many_arguments)]
2396pub fn chunk_attend(
2397 q: &[f32],
2398 k: &[&[f32]],
2399 v: &[&[f32]],
2400 b: usize,
2401 s0: usize,
2402 nh: usize,
2403 nkv: usize,
2404 hd: usize,
2405 scale: f32,
2406 out: &mut [f32],
2407) -> bool {
2408 match backend() {
2409 #[cfg(feature = "gpu")]
2410 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
2411 #[allow(unreachable_patterns)]
2412 _ => false,
2413 }
2414}
2415
2416#[allow(unused_variables, clippy::too_many_arguments)]
2419pub fn chunk_attend_win(
2420 q: &[f32],
2421 k: &[&[f32]],
2422 v: &[&[f32]],
2423 b: usize,
2424 s0: usize,
2425 nh: usize,
2426 nkv: usize,
2427 hd: usize,
2428 scale: f32,
2429 window: usize,
2430 out: &mut [f32],
2431) -> bool {
2432 match backend() {
2433 #[cfg(feature = "gpu")]
2434 Backend::Wgpu => {
2435 crate::gpu_wgpu::chunk_attend_win(q, k, v, b, s0, nh, nkv, hd, scale, window, out)
2436 }
2437 #[allow(unreachable_patterns)]
2438 _ => false,
2439 }
2440}
2441
2442#[allow(unused_variables)]
2445pub fn gemm_many_keep(
2446 model: &Arc<CmfModel>,
2447 idxs: &[(usize, usize)],
2448 xs: &[f32],
2449 b: usize,
2450 cols: usize,
2451 out: &mut [f32],
2452) -> bool {
2453 match backend() {
2454 #[cfg(feature = "gpu")]
2455 Backend::Wgpu => crate::gpu_wgpu::gemm_many_keep(model, idxs, xs, b, cols, out),
2456 #[allow(unreachable_patterns)]
2457 _ => false,
2458 }
2459}
2460
2461thread_local! {
2462 static PREFILL_FAST_GEMM: Cell<bool> = const { Cell::new(false) };
2466}
2467
2468pub struct PrefillFastGemmGuard(bool);
2470
2471impl Drop for PrefillFastGemmGuard {
2472 fn drop(&mut self) {
2473 PREFILL_FAST_GEMM.with(|c| c.set(self.0));
2474 }
2475}
2476
2477pub fn enter_prefill_fast_gemm() -> PrefillFastGemmGuard {
2481 PrefillFastGemmGuard(PREFILL_FAST_GEMM.with(|c| c.replace(true)))
2482}
2483
2484pub fn prefill_fast_gemm() -> bool {
2486 PREFILL_FAST_GEMM.with(|c| c.get())
2487}
2488
2489pub const F16_MAX: f32 = 65504.0;
2492
2493pub fn abs_max_or_inf(xs: &[f32]) -> f32 {
2497 let mut acc = [0f32; 16];
2498 let mut bad = [false; 16];
2499 let mut it = xs.chunks_exact(16);
2500 for c in &mut it {
2501 for ((a, f), &v) in acc.iter_mut().zip(bad.iter_mut()).zip(c) {
2502 let v = v.abs();
2503 *f |= !(v < f32::INFINITY);
2505 *a = if v > *a { v } else { *a };
2506 }
2507 }
2508 let mut m = 0f32;
2509 for &v in it.remainder() {
2510 let v = v.abs();
2511 if !(v < f32::INFINITY) {
2512 return f32::INFINITY;
2513 }
2514 m = m.max(v);
2515 }
2516 if bad.iter().any(|&f| f) {
2517 return f32::INFINITY;
2518 }
2519 acc.iter().fold(m, |m, &a| m.max(a))
2520}
2521
2522#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2528pub struct PrefillMirror {
2529 pub kv_id: u64,
2530 pub layer: usize,
2531 pub limit: usize,
2532}
2533
2534thread_local! {
2535 static PREFILL_MIRROR: Cell<Option<PrefillMirror>> = const { Cell::new(None) };
2539}
2540
2541pub struct PrefillMirrorGuard(Option<PrefillMirror>);
2543
2544impl Drop for PrefillMirrorGuard {
2545 fn drop(&mut self) {
2546 PREFILL_MIRROR.with(|c| c.set(self.0));
2547 }
2548}
2549
2550pub fn enter_prefill_mirror(t: PrefillMirror) -> PrefillMirrorGuard {
2553 PrefillMirrorGuard(PREFILL_MIRROR.with(|c| c.replace(Some(t))))
2554}
2555
2556pub fn prefill_mirror() -> Option<PrefillMirror> {
2558 PREFILL_MIRROR.with(|c| c.get())
2559}
2560
2561#[allow(unused_variables, clippy::too_many_arguments)]
2574pub fn chunk_attend_mirror(
2575 t: PrefillMirror,
2576 cpu_k: &[Vec<f32>],
2577 cpu_v: &[Vec<f32>],
2578 base: usize,
2579 q: &[f32],
2580 b: usize,
2581 s0: usize,
2582 nh: usize,
2583 nkv: usize,
2584 hd: usize,
2585 scale: f32,
2586 ring: Option<usize>,
2587 window: usize,
2588 operand_max: f32,
2589 out: &mut [f32],
2590) -> bool {
2591 match backend() {
2592 #[cfg(feature = "gpu")]
2593 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror(
2598 t.kv_id,
2599 t.layer,
2600 t.limit,
2601 cpu_k,
2602 cpu_v,
2603 base,
2604 q,
2605 b,
2606 s0,
2607 nh,
2608 nkv,
2609 hd,
2610 scale,
2611 ring,
2612 window,
2613 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2614 operand_max,
2615 out,
2616 ),
2617 #[allow(unreachable_patterns)]
2618 _ => false,
2619 }
2620}
2621
2622#[allow(unused_variables, clippy::too_many_arguments)]
2630pub fn chunk_attend_mirror_wo(
2631 t: PrefillMirror,
2632 cpu_k: &[Vec<f32>],
2633 cpu_v: &[Vec<f32>],
2634 base: usize,
2635 q: &[f32],
2636 b: usize,
2637 s0: usize,
2638 nh: usize,
2639 nkv: usize,
2640 hd: usize,
2641 scale: f32,
2642 ring: Option<usize>,
2643 window: usize,
2644 operand_max: f32,
2645 gains: Option<&[f32]>,
2646 wo: (&Arc<CmfModel>, usize),
2647 hidden: usize,
2648 out: &mut [f32],
2649) -> bool {
2650 match backend() {
2651 #[cfg(feature = "gpu")]
2652 Backend::Wgpu => crate::gpu_wgpu::chunk_attend_mirror_wo(
2653 t.kv_id,
2654 t.layer,
2655 t.limit,
2656 cpu_k,
2657 cpu_v,
2658 base,
2659 q,
2660 b,
2661 s0,
2662 nh,
2663 nkv,
2664 hd,
2665 scale,
2666 ring,
2667 window,
2668 std::env::var("CMF_PREFILL_ATTN_COOP").as_deref() != Ok("0"),
2669 operand_max,
2670 gains,
2671 wo,
2672 hidden,
2673 out,
2674 ),
2675 #[allow(unreachable_patterns)]
2676 _ => false,
2677 }
2678}
2679
2680pub fn chunk_attend_windowed() -> bool {
2682 match backend() {
2683 #[cfg(feature = "gpu")]
2684 Backend::Wgpu => true,
2685 #[allow(unreachable_patterns)]
2686 _ => false,
2687 }
2688}
2689
2690#[allow(unused_variables, clippy::too_many_arguments)]
2694pub fn q4t_qkv(
2695 model: &Arc<CmfModel>,
2696 wq: usize,
2697 wk: usize,
2698 wv: usize,
2699 xs: &[f32],
2700 b: usize,
2701 cols: usize,
2702 rq: usize,
2703 rk: usize,
2704 rv: usize,
2705 out: &mut [f32],
2706) -> bool {
2707 match backend() {
2708 #[cfg(feature = "gpu")]
2709 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
2710 #[allow(unreachable_patterns)]
2711 _ => false,
2712 }
2713}
2714
2715#[allow(unused_variables, clippy::too_many_arguments)]
2717#[allow(clippy::too_many_arguments, unused_variables)]
2721pub fn q4tp_ffn_packed(
2722 model: &Arc<CmfModel>,
2723 w1: usize,
2724 w2: usize,
2725 xs: &[f32],
2726 b: usize,
2727 hidden: usize,
2728 inter: usize,
2729 bias: Option<&[f32]>,
2730 out: &mut [f32],
2731) -> bool {
2732 match backend() {
2733 #[cfg(feature = "gpu")]
2734 Backend::Wgpu => {
2735 crate::gpu_wgpu::ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
2736 }
2737 #[allow(unreachable_patterns)]
2738 _ => false,
2739 }
2740}
2741
2742pub fn q4tp_ffn(
2743 model: &Arc<CmfModel>,
2744 w1: usize,
2745 w3: usize,
2746 w2: usize,
2747 xs: &[f32],
2748 b: usize,
2749 hidden: usize,
2750 inter: usize,
2751 out: &mut [f32],
2752) -> bool {
2753 match backend() {
2754 #[cfg(target_os = "macos")]
2755 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2756 #[cfg(feature = "gpu")]
2757 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2758 #[allow(unreachable_patterns)]
2759 _ => false,
2760 }
2761}
2762
2763#[allow(clippy::too_many_arguments, unused_variables)]
2767pub fn q4_ffn_act(
2768 model: &Arc<CmfModel>,
2769 w1: usize,
2770 w3: usize,
2771 w2: usize,
2772 xs: &[f32],
2773 b: usize,
2774 hidden: usize,
2775 inter: usize,
2776 q4tp: bool,
2777 act: GraphAct,
2778 out: &mut [f32],
2779) -> bool {
2780 match act {
2781 GraphAct::Silu if q4tp => q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2782 GraphAct::Silu => q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2783 GraphAct::GeluErf | GraphAct::GeluTanh => match backend() {
2784 #[cfg(feature = "gpu")]
2785 Backend::Wgpu => crate::gpu_wgpu::q4_ffn_act(
2786 model,
2787 w1,
2788 w3,
2789 w2,
2790 xs,
2791 b,
2792 hidden,
2793 inter,
2794 q4tp,
2795 act.code(),
2796 out,
2797 ),
2798 #[allow(unreachable_patterns)]
2799 _ => false,
2800 },
2801 }
2802}
2803
2804#[allow(clippy::too_many_arguments, unused_variables)]
2809pub fn ffn_act_keep(
2810 model: &Arc<CmfModel>,
2811 w1: usize,
2812 w3: usize,
2813 w2: usize,
2814 xs: &[f32],
2815 b: usize,
2816 hidden: usize,
2817 inter: usize,
2818 act: GraphAct,
2819 out: &mut [f32],
2820) -> bool {
2821 match backend() {
2822 #[cfg(feature = "gpu")]
2823 Backend::Wgpu => crate::gpu_wgpu::ffn_act_keep(
2824 model,
2825 w1,
2826 w3,
2827 w2,
2828 xs,
2829 b,
2830 hidden,
2831 inter,
2832 act.code(),
2833 out,
2834 ),
2835 #[allow(unreachable_patterns)]
2836 _ => false,
2837 }
2838}
2839
2840#[allow(clippy::too_many_arguments, unused_variables)]
2845pub fn q4tp_gelu_ffn(
2846 model: &Arc<CmfModel>,
2847 w_in: usize,
2848 w_out: usize,
2849 xs: &[f32],
2850 b: usize,
2851 hidden: usize,
2852 inter: usize,
2853 bias_in: &[f32],
2854 bias_out: &[f32],
2855 out: &mut [f32],
2856) -> bool {
2857 match backend() {
2858 #[cfg(feature = "gpu")]
2859 Backend::Wgpu => crate::gpu_wgpu::q4tp_gelu_ffn(
2860 model, w_in, w_out, xs, b, hidden, inter, bias_in, bias_out, out,
2861 ),
2862 #[allow(unreachable_patterns)]
2863 _ => false,
2864 }
2865}
2866
2867pub fn q4t_ffn(
2868 model: &Arc<CmfModel>,
2869 w1: usize,
2870 w3: usize,
2871 w2: usize,
2872 xs: &[f32],
2873 b: usize,
2874 hidden: usize,
2875 inter: usize,
2876 out: &mut [f32],
2877) -> bool {
2878 match backend() {
2879 #[cfg(target_os = "macos")]
2880 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2881 #[cfg(feature = "gpu")]
2882 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
2883 #[allow(unreachable_patterns)]
2884 _ => false,
2885 }
2886}
2887
2888pub struct DitBlockArgs<'a> {
2893 pub n: usize,
2894 pub hidden: usize,
2895 pub inter: usize,
2896 pub nh: usize,
2897 pub nkv: usize,
2898 pub hd: usize,
2899 pub eps: f32,
2900 pub rope_cos: &'a [f32],
2901 pub rope_sin: &'a [f32],
2902 pub norm1: &'a [f32],
2903 pub norm2: &'a [f32],
2904 pub ffn_norm1: &'a [f32],
2905 pub ffn_norm2: &'a [f32],
2906 pub norm_q: &'a [f32],
2907 pub norm_k: &'a [f32],
2908 pub s_msa: &'a [f32],
2909 pub gate_msa: &'a [f32],
2910 pub s_mlp: &'a [f32],
2911 pub gate_mlp: &'a [f32],
2912 pub wq: usize,
2913 pub wk: usize,
2914 pub wv: usize,
2915 pub wo: usize,
2916 pub w1: usize,
2917 pub w3: usize,
2918 pub w2: usize,
2919 pub q4tp: bool,
2923 pub resident_in: bool,
2926 pub resident_out: bool,
2930}
2931
2932pub fn dit_chain_supported() -> bool {
2936 #[cfg(feature = "gpu")]
2937 {
2938 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
2939 }
2940 #[allow(unreachable_code)]
2941 false
2942}
2943
2944pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
2947 #[cfg(feature = "gpu")]
2948 {
2949 if matches!(backend(), Backend::Wgpu) {
2950 return crate::gpu_wgpu::dit_state_fetch(_x);
2951 }
2952 }
2953 false
2954}
2955
2956#[allow(unused_variables)]
2960#[allow(unused_variables, clippy::too_many_arguments)]
2964pub fn dit_qkv(
2965 model: &Arc<CmfModel>,
2966 wq: usize,
2967 wk: usize,
2968 wv: usize,
2969 xs: &[f32],
2970 b: usize,
2971 hidden: usize,
2972 qrows: usize,
2973 kvrows: usize,
2974 q_out: &mut [f32],
2975 k_out: &mut [f32],
2976 v_out: &mut [f32],
2977) -> bool {
2978 match backend() {
2979 #[cfg(feature = "gpu")]
2980 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
2981 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
2982 ),
2983 #[allow(unreachable_patterns)]
2984 _ => false,
2985 }
2986}
2987
2988pub struct QwenImageAttentionArgs<'a> {
2996 pub image: &'a [f32],
2997 pub text: &'a [f32],
2998 pub image_tokens: usize,
2999 pub text_tokens: usize,
3000 pub heads: usize,
3001 pub head_dim: usize,
3002 pub image_q: usize,
3003 pub image_k: usize,
3004 pub image_v: usize,
3005 pub text_q: usize,
3006 pub text_k: usize,
3007 pub text_v: usize,
3008 pub image_out: usize,
3009 pub text_out: usize,
3010 pub image_q_norm: &'a [f32],
3011 pub image_k_norm: &'a [f32],
3012 pub text_q_norm: &'a [f32],
3013 pub text_k_norm: &'a [f32],
3014 pub image_cos: &'a [f32],
3015 pub image_sin: &'a [f32],
3016 pub text_cos: &'a [f32],
3017 pub text_sin: &'a [f32],
3018 pub image_q_bias: &'a [f32],
3019 pub image_k_bias: &'a [f32],
3020 pub image_v_bias: &'a [f32],
3021 pub text_q_bias: &'a [f32],
3022 pub text_k_bias: &'a [f32],
3023 pub text_v_bias: &'a [f32],
3024 pub image_out_bias: &'a [f32],
3025 pub text_out_bias: &'a [f32],
3026 pub image_proj: &'a mut [f32],
3027 pub text_proj: &'a mut [f32],
3028}
3029
3030#[allow(clippy::too_many_fields)]
3035pub struct QwenImageChainBlock<'a> {
3036 pub image_mod: &'a [f32],
3037 pub text_mod: &'a [f32],
3038 pub image_q: usize,
3039 pub image_k: usize,
3040 pub image_v: usize,
3041 pub text_q: usize,
3042 pub text_k: usize,
3043 pub text_v: usize,
3044 pub image_out: usize,
3045 pub text_out: usize,
3046 pub image_q_norm: &'a [f32],
3047 pub image_k_norm: &'a [f32],
3048 pub text_q_norm: &'a [f32],
3049 pub text_k_norm: &'a [f32],
3050 pub image_q_bias: &'a [f32],
3051 pub image_k_bias: &'a [f32],
3052 pub image_v_bias: &'a [f32],
3053 pub text_q_bias: &'a [f32],
3054 pub text_k_bias: &'a [f32],
3055 pub text_v_bias: &'a [f32],
3056 pub image_out_bias: &'a [f32],
3057 pub text_out_bias: &'a [f32],
3058 pub image_attn_gate: &'a [f32],
3059 pub text_attn_gate: &'a [f32],
3060 pub image_mlp_in: usize,
3061 pub image_mlp_out: usize,
3062 pub text_mlp_in: usize,
3063 pub text_mlp_out: usize,
3064 pub image_mlp_in_bias: &'a [f32],
3065 pub image_mlp_out_bias: &'a [f32],
3066 pub text_mlp_in_bias: &'a [f32],
3067 pub text_mlp_out_bias: &'a [f32],
3068}
3069
3070#[allow(clippy::too_many_fields)]
3076pub struct QwenImageBlockArgs<'a> {
3077 pub image: &'a mut [f32],
3080 pub text: &'a mut [f32],
3081 pub image_norm: &'a [f32],
3082 pub text_norm: &'a [f32],
3083 pub image_tokens: usize,
3084 pub text_tokens: usize,
3085 pub heads: usize,
3086 pub head_dim: usize,
3087 pub image_cos: &'a [f32],
3088 pub image_sin: &'a [f32],
3089 pub text_cos: &'a [f32],
3090 pub text_sin: &'a [f32],
3091 pub image_q: usize,
3092 pub image_k: usize,
3093 pub image_v: usize,
3094 pub text_q: usize,
3095 pub text_k: usize,
3096 pub text_v: usize,
3097 pub image_out: usize,
3098 pub text_out: usize,
3099 pub image_q_norm: &'a [f32],
3100 pub image_k_norm: &'a [f32],
3101 pub text_q_norm: &'a [f32],
3102 pub text_k_norm: &'a [f32],
3103 pub image_q_bias: &'a [f32],
3104 pub image_k_bias: &'a [f32],
3105 pub image_v_bias: &'a [f32],
3106 pub text_q_bias: &'a [f32],
3107 pub text_k_bias: &'a [f32],
3108 pub text_v_bias: &'a [f32],
3109 pub image_out_bias: &'a [f32],
3110 pub text_out_bias: &'a [f32],
3111 pub image_attn_gate: &'a [f32],
3112 pub text_attn_gate: &'a [f32],
3113 pub image_mlp_in: usize,
3114 pub image_mlp_out: usize,
3115 pub text_mlp_in: usize,
3116 pub text_mlp_out: usize,
3117 pub image_mlp_in_bias: &'a [f32],
3118 pub image_mlp_out_bias: &'a [f32],
3119 pub text_mlp_in_bias: &'a [f32],
3120 pub text_mlp_out_bias: &'a [f32],
3121 pub image_mlp_mod: &'a [f32],
3122 pub text_mlp_mod: &'a [f32],
3123 pub image_mlp_gate: &'a [f32],
3124 pub text_mlp_gate: &'a [f32],
3125}
3126
3127pub struct QwenImageChainArgs<'a> {
3132 pub image: &'a mut [f32],
3133 pub text: &'a mut [f32],
3134 pub image_tokens: usize,
3135 pub text_tokens: usize,
3136 pub heads: usize,
3137 pub head_dim: usize,
3138 pub image_cos: &'a [f32],
3139 pub image_sin: &'a [f32],
3140 pub text_cos: &'a [f32],
3141 pub text_sin: &'a [f32],
3142 pub blocks: &'a [QwenImageChainBlock<'a>],
3143}
3144
3145#[allow(unused_variables)]
3146pub fn qwen_image_attention(
3147 model: &Arc<CmfModel>,
3148 args: &mut QwenImageAttentionArgs<'_>,
3149) -> bool {
3150 match backend() {
3151 #[cfg(feature = "gpu")]
3152 Backend::Wgpu => crate::gpu_wgpu::qwen_image_attention(model, args),
3153 #[allow(unreachable_patterns)]
3154 _ => false,
3155 }
3156}
3157
3158#[allow(unused_variables)]
3159pub fn qwen_image_block(model: &Arc<CmfModel>, args: &mut QwenImageBlockArgs<'_>) -> bool {
3160 match backend() {
3161 #[cfg(feature = "gpu")]
3162 Backend::Wgpu => crate::gpu_wgpu::qwen_image_block(model, args),
3163 #[allow(unreachable_patterns)]
3164 _ => false,
3165 }
3166}
3167
3168#[allow(unused_variables)]
3172pub fn qwen_image_chain(model: &Arc<CmfModel>, args: &mut QwenImageChainArgs<'_>) -> bool {
3173 match backend() {
3174 #[cfg(feature = "gpu")]
3175 Backend::Wgpu => crate::gpu_wgpu::qwen_image_chain(model, args),
3176 #[allow(unreachable_patterns)]
3177 _ => false,
3178 }
3179}
3180
3181#[allow(unused_variables, clippy::too_many_arguments)]
3188pub fn qwen_image_mlp_inplace(
3189 model: &Arc<CmfModel>,
3190 w_in: usize,
3191 w_out: usize,
3192 data: &mut [f32],
3193 batch: usize,
3194 hidden: usize,
3195 inter: usize,
3196 bias_in: &[f32],
3197 bias_out: &[f32],
3198 modulation: &[f32],
3199 gate: &[f32],
3200) -> bool {
3201 match backend() {
3202 #[cfg(feature = "gpu")]
3203 Backend::Wgpu => crate::gpu_wgpu::qwen_image_mlp_inplace(
3204 model,
3205 w_in,
3206 w_out,
3207 data,
3208 batch,
3209 hidden,
3210 inter,
3211 bias_in,
3212 bias_out,
3213 modulation,
3214 gate,
3215 ),
3216 #[allow(unreachable_patterns)]
3217 _ => false,
3218 }
3219}
3220
3221pub fn fused_dit_block_available() -> bool {
3225 #[cfg(target_os = "macos")]
3226 {
3227 matches!(backend(), Backend::Metal) && fused_block_trusted()
3228 }
3229 #[cfg(not(target_os = "macos"))]
3230 {
3231 false
3232 }
3233}
3234
3235pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
3236 dit_block_seg(model, a, &[a.n], x)
3237}
3238
3239pub fn dit_block_seg(
3243 model: &Arc<CmfModel>,
3244 a: &DitBlockArgs,
3245 segs: &[usize],
3246 x: &mut [f32],
3247) -> bool {
3248 match backend() {
3249 #[cfg(target_os = "macos")]
3250 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
3251 #[cfg(feature = "gpu")]
3258 Backend::Wgpu
3259 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
3260 Some("0") => false,
3261 Some(_) => true,
3262 None => crate::gpu_wgpu::discrete_active(),
3263 } =>
3264 {
3265 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
3266 }
3267 #[allow(unreachable_patterns)]
3268 _ => false,
3269 }
3270}
3271
3272pub struct VaeResnetArgs<'a> {
3276 pub groups: usize,
3277 pub ic: usize,
3278 pub oc: usize,
3279 pub h: usize,
3280 pub w: usize,
3281 pub n1w: &'a [f32],
3282 pub n1b: &'a [f32],
3283 pub c1w: &'a [f32],
3284 pub c1b: &'a [f32],
3285 pub c1k: usize,
3286 pub n2w: &'a [f32],
3287 pub n2b: &'a [f32],
3288 pub c2w: &'a [f32],
3289 pub c2b: &'a [f32],
3290 pub c2k: usize,
3291 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
3292}
3293
3294#[allow(unused_variables)]
3297pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
3298 match backend() {
3299 #[cfg(target_os = "macos")]
3300 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
3301 _ => false,
3302 }
3303}
3304
3305#[allow(unused_variables, clippy::too_many_arguments)]
3308pub fn vae_upsample_conv(
3309 w: &[f32],
3310 bias: &[f32],
3311 x: &[f32],
3312 ic: usize,
3313 oc: usize,
3314 h: usize,
3315 w_img: usize,
3316 k: usize,
3317 out: &mut [f32],
3318) -> bool {
3319 match backend() {
3320 #[cfg(target_os = "macos")]
3321 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3322 #[cfg(feature = "gpu")]
3323 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
3324 #[allow(unreachable_patterns)]
3325 _ => false,
3326 }
3327}
3328
3329#[allow(unused_variables, clippy::too_many_arguments)]
3332pub fn vae_conv2d(
3333 w: &[f32],
3334 bias: &[f32],
3335 x: &[f32],
3336 ic: usize,
3337 oc: usize,
3338 h: usize,
3339 w_img: usize,
3340 k: usize,
3341 out: &mut [f32],
3342) -> bool {
3343 match backend() {
3344 #[cfg(target_os = "macos")]
3345 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3346 #[cfg(feature = "gpu")]
3347 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
3348 #[allow(unreachable_patterns)]
3349 _ => false,
3350 }
3351}
3352
3353#[allow(unused_variables, clippy::too_many_arguments)]
3357#[allow(unused_variables)]
3361#[allow(clippy::too_many_arguments)]
3362#[allow(clippy::too_many_arguments, unused_variables)]
3365pub fn dit_qkv_attention(
3366 model: &Arc<CmfModel>,
3367 qkv_idx: usize,
3368 xn: &[f32],
3369 n: usize,
3370 hidden: usize,
3371 nh: usize,
3372 hd: usize,
3373 scale: f32,
3374 nr: (&[f32], &[f32], &[f32], f32),
3375 out: &mut [f32],
3376) -> bool {
3377 match backend() {
3378 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3379 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
3380 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
3381 ),
3382 #[allow(unreachable_patterns)]
3383 _ => false,
3384 }
3385}
3386
3387#[allow(clippy::too_many_arguments)]
3390pub fn dit_qkv_attn_out(
3391 model: &Arc<CmfModel>,
3392 qkv_idx: usize,
3393 out_idx: usize,
3394 xn: &[f32],
3395 n: usize,
3396 hidden: usize,
3397 nh: usize,
3398 hd: usize,
3399 scale: f32,
3400 nr: (&[f32], &[f32], &[f32], f32),
3401 proj: &mut [f32],
3402) -> bool {
3403 match backend() {
3404 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3405 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
3406 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
3407 ),
3408 #[allow(unreachable_patterns)]
3409 _ => false,
3410 }
3411}
3412
3413#[allow(clippy::too_many_arguments)]
3415pub fn vae_qkv_attn_out(
3416 model: &Arc<CmfModel>,
3417 qkv_idx: usize,
3418 out_idx: usize,
3419 xn: &[f32],
3420 n: usize,
3421 dim: usize,
3422 nh: usize,
3423 hd: usize,
3424 scale: f32,
3425 angles: &[f32],
3426 eps: f32,
3427 qkv_bias: &[f32],
3428 proj: &mut [f32],
3429) -> bool {
3430 match backend() {
3431 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3432 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
3433 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
3434 ),
3435 #[allow(unreachable_patterns)]
3436 _ => false,
3437 }
3438}
3439
3440#[allow(clippy::too_many_arguments)]
3441pub fn vae_attention_packed(
3442 qkv: &[f32],
3443 nh: usize,
3444 n: usize,
3445 hd: usize,
3446 scale: f32,
3447 angles: &[f32],
3448 eps: f32,
3449 out: &mut [f32],
3450) -> bool {
3451 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
3452}
3453
3454#[allow(clippy::too_many_arguments)]
3455pub fn vae_attention_packed_layout(
3456 qkv: &[f32],
3457 nh: usize,
3458 n: usize,
3459 hd: usize,
3460 scale: f32,
3461 angles: &[f32],
3462 eps: f32,
3463 out: &mut [f32],
3464 layout: u32,
3465) -> bool {
3466 match backend() {
3467 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3468 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
3469 qkv, nh, n, hd, scale, angles, eps, out, layout,
3470 ),
3471 #[allow(unreachable_patterns)]
3472 _ => false,
3473 }
3474}
3475
3476#[allow(clippy::too_many_arguments)]
3477pub fn dit_split_only(
3478 qkv: &[f32],
3479 nh: usize,
3480 n: usize,
3481 hd: usize,
3482 layout: u32,
3483 norm: Option<(&[f32], f32)>,
3484 out_q: &mut [f32],
3485) -> bool {
3486 match backend() {
3487 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3488 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
3489 #[allow(unreachable_patterns)]
3490 _ => false,
3491 }
3492}
3493
3494pub fn gemm_nt_f32_transient(
3502 x: &[f32],
3503 w: &[f32],
3504 y: &mut [f32],
3505 n: usize,
3506 k: usize,
3507 m: usize,
3508) -> bool {
3509 match backend() {
3510 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3511 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32_transient(x, w, y, n, k, m),
3512 #[allow(unreachable_patterns)]
3513 _ => false,
3514 }
3515}
3516
3517pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
3518 match backend() {
3519 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3520 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
3521 #[allow(unreachable_patterns)]
3522 _ => false,
3523 }
3524}
3525
3526#[allow(clippy::too_many_arguments)]
3529pub fn music3_ffn(
3530 model: &std::sync::Arc<CmfModel>,
3531 idx_in: usize,
3532 idx_out: usize,
3533 h: &[f32],
3534 bias_in: &[f32],
3535 n: usize,
3536 hs: usize,
3537 inter: usize,
3538 out: &mut [f32],
3539) -> bool {
3540 match backend() {
3541 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3542 Backend::Wgpu => {
3543 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
3544 }
3545 #[allow(unreachable_patterns)]
3546 _ => false,
3547 }
3548}
3549
3550#[allow(clippy::too_many_arguments)]
3554pub fn conv1d_gemm(
3555 x: &[f32],
3556 w: &[f32],
3557 ic: usize,
3558 oc: usize,
3559 n: usize,
3560 k: usize,
3561 pad: usize,
3562 dil: usize,
3563 out_n: usize,
3564 yt: &mut [f32],
3565) -> bool {
3566 match backend() {
3567 #[cfg(target_os = "macos")]
3568 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3569 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3570 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
3571 #[allow(unreachable_patterns)]
3572 _ => false,
3573 }
3574}
3575
3576#[allow(clippy::too_many_arguments)]
3578pub fn vae_conv2d_coop(
3579 w: &[f32],
3580 bias: Option<&[f32]>,
3581 x: &[f32],
3582 ic: usize,
3583 oc: usize,
3584 h: usize,
3585 wi: usize,
3586 k: usize,
3587 out: &mut [f32],
3588) -> bool {
3589 match backend() {
3590 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
3591 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
3592 #[allow(unreachable_patterns)]
3593 _ => false,
3594 }
3595}
3596
3597pub fn dit_attention_packed(
3598 qkv: &[f32],
3599 nh: usize,
3600 n: usize,
3601 hd: usize,
3602 scale: f32,
3603 nr: Option<(&[f32], &[f32], &[f32], f32)>,
3606 out: &mut [f32],
3607) -> bool {
3608 match backend() {
3609 #[cfg(feature = "gpu")]
3616 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
3617 #[allow(unreachable_patterns)]
3618 _ => false,
3619 }
3620}
3621
3622pub fn dit_attention_packed_available() -> bool {
3630 #[allow(unreachable_patterns)]
3631 match backend() {
3632 #[cfg(feature = "gpu")]
3633 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
3634 _ => false,
3635 }
3636}
3637
3638pub fn dit_attention(
3639 qh: &[f32],
3640 kh: &[f32],
3641 vh: &[f32],
3642 nh: usize,
3643 nkv: usize,
3644 n: usize,
3645 hd: usize,
3646 scale: f32,
3647 out: &mut [f32],
3648) -> bool {
3649 match backend() {
3650 #[cfg(target_os = "macos")]
3651 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3652 #[cfg(feature = "gpu")]
3653 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
3654 #[allow(unreachable_patterns)]
3655 _ => false,
3656 }
3657}
3658
3659pub fn gram_alloc(cols: usize) -> Option<u64> {
3662 #[cfg(feature = "gpu")]
3663 if backend() == Backend::Wgpu {
3664 return crate::gpu_wgpu::gram::alloc(cols);
3665 }
3666 let _ = cols;
3667 None
3668}
3669
3670pub fn gram_add(id: u64, xs: &[f32], b: usize) -> bool {
3672 #[cfg(feature = "gpu")]
3673 if backend() == Backend::Wgpu {
3674 return crate::gpu_wgpu::gram::add(id, xs, b);
3675 }
3676 let _ = (id, xs, b);
3677 false
3678}
3679
3680#[cfg(feature = "gpu")]
3682pub fn gram_add_dev(id: u64, x: &wgpu::Buffer, b: usize) -> bool {
3683 if backend() == Backend::Wgpu {
3684 return crate::gpu_wgpu::gram::add_dev(id, x, b);
3685 }
3686 false
3687}
3688
3689pub fn gram_take(id: u64) -> Option<Vec<f64>> {
3691 #[cfg(feature = "gpu")]
3692 if backend() == Backend::Wgpu {
3693 return crate::gpu_wgpu::gram::take(id);
3694 }
3695 let _ = id;
3696 None
3697}
3698
3699pub fn gptq_fold_dev(h_id: u64, n: usize, lambda: f64) -> Option<u64> {
3704 #[cfg(feature = "gpu")]
3705 if backend() == Backend::Wgpu {
3706 return crate::gpu_wgpu::gptq_dev::fold(h_id, n, lambda);
3707 }
3708 let _ = (h_id, n, lambda);
3709 None
3710}
3711
3712pub fn gptq_fold_info(id: u64) -> Option<(Vec<bool>, Vec<f32>, f64)> {
3714 #[cfg(feature = "gpu")]
3715 if backend() == Backend::Wgpu {
3716 return crate::gpu_wgpu::gptq_dev::info(id);
3717 }
3718 let _ = id;
3719 None
3720}
3721
3722pub fn gptq_fold_read(id: u64) -> Option<Vec<f32>> {
3724 #[cfg(feature = "gpu")]
3725 if backend() == Backend::Wgpu {
3726 return crate::gpu_wgpu::gptq_dev::read_u(id);
3727 }
3728 let _ = id;
3729 None
3730}
3731
3732pub fn gptq_fold_free(id: u64) {
3734 #[cfg(feature = "gpu")]
3735 if backend() == Backend::Wgpu {
3736 crate::gpu_wgpu::gptq_dev::free(id);
3737 }
3738 let _ = id;
3739}
3740
3741#[allow(clippy::too_many_arguments)]
3743pub fn gptq_sweep_q4tp_dev(
3744 id: u64,
3745 w: &[f32],
3746 rows: usize,
3747 tab: &[f32],
3748 lost: &[f32],
3749 nib: &mut [u8],
3750 codes: &mut [u32],
3751) -> bool {
3752 #[cfg(feature = "gpu")]
3753 if backend() == Backend::Wgpu {
3754 return crate::gpu_wgpu::gptq_dev::sweep_q4tp(id, w, rows, tab, lost, nib, codes);
3755 }
3756 let _ = (id, w, rows, tab, lost, nib, codes);
3757 false
3758}
3759
3760pub fn gptq_sweep_q2tp_dev(
3762 id: u64,
3763 w: &[f32],
3764 rows: usize,
3765 tab: &[f32],
3766 hw: &[f32],
3767 chunks: &mut [u8],
3768 codes: &mut [u32],
3769) -> bool {
3770 #[cfg(feature = "gpu")]
3771 if backend() == Backend::Wgpu {
3772 return crate::gpu_wgpu::gptq_dev::sweep_q2tp(id, w, rows, tab, hw, chunks, codes);
3773 }
3774 let _ = (id, w, rows, tab, hw, chunks, codes);
3775 false
3776}
3777
3778pub fn gram_read(id: u64) -> Option<Vec<f64>> {
3781 #[cfg(feature = "gpu")]
3782 if backend() == Backend::Wgpu {
3783 return crate::gpu_wgpu::gram::read(id);
3784 }
3785 let _ = id;
3786 None
3787}
3788
3789pub fn gram_clear() {
3791 #[cfg(feature = "gpu")]
3792 if backend() == Backend::Wgpu {
3793 crate::gpu_wgpu::gram::clear();
3794 }
3795}
3796
3797#[allow(unused_variables)]
3802pub fn q4tp_matmat(
3803 model: &Arc<CmfModel>,
3804 idx: usize,
3805 xs: &[f32],
3806 b: usize,
3807 rows: usize,
3808 cols: usize,
3809 out: &mut [f32],
3810) -> bool {
3811 match backend() {
3812 #[cfg(target_os = "macos")]
3813 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3814 #[cfg(feature = "gpu")]
3815 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
3816 #[allow(unreachable_patterns)]
3817 _ => false,
3818 }
3819}
3820
3821pub fn q2tp_matmat(
3824 model: &Arc<CmfModel>,
3825 idx: usize,
3826 xs: &[f32],
3827 b: usize,
3828 rows: usize,
3829 cols: usize,
3830 out: &mut [f32],
3831) -> bool {
3832 match backend() {
3833 #[cfg(target_os = "macos")]
3834 Backend::Metal => crate::gpu_metal::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3835 #[cfg(feature = "gpu")]
3836 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
3837 #[allow(unreachable_patterns)]
3838 _ => false,
3839 }
3840}
3841
3842pub fn q2tp_affine_matmat(
3845 model: &Arc<CmfModel>,
3846 idx: usize,
3847 xs: &[f32],
3848 b: usize,
3849 rows: usize,
3850 cols: usize,
3851 out: &mut [f32],
3852) -> bool {
3853 match backend() {
3854 #[cfg(target_os = "macos")]
3855 Backend::Metal => crate::gpu_metal::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3856 #[cfg(feature = "gpu")]
3857 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matmat(model, idx, xs, b, rows, cols, out),
3858 #[allow(unreachable_patterns)]
3859 _ => false,
3860 }
3861}
3862
3863pub fn blr_matmat(t: &crate::blr::BlrTensor, xs: &[f32], b: usize, out: &mut [f32]) -> bool {
3868 match backend() {
3869 #[cfg(feature = "gpu")]
3870 Backend::Wgpu => crate::gpu_wgpu::blr_matmat(t, xs, b, out),
3871 #[allow(unreachable_patterns)]
3872 _ => {
3873 let _ = (t, xs, b, out);
3874 false
3875 }
3876 }
3877}
3878
3879pub fn q2tp_matvec(
3881 model: &Arc<CmfModel>,
3882 idx: usize,
3883 xs: &[f32],
3884 rows: usize,
3885 cols: usize,
3886 out: &mut [f32],
3887) -> bool {
3888 match backend() {
3889 #[cfg(target_os = "macos")]
3890 Backend::Metal => crate::gpu_metal::q2tp_matvec(model, idx, xs, rows, cols, out),
3891 #[cfg(feature = "gpu")]
3892 Backend::Wgpu => crate::gpu_wgpu::q2tp_matvec(model, idx, xs, rows, cols, out),
3893 #[allow(unreachable_patterns)]
3894 _ => false,
3895 }
3896}
3897
3898pub fn q2tp_affine_matvec(
3902 model: &Arc<CmfModel>,
3903 idx: usize,
3904 xs: &[f32],
3905 rows: usize,
3906 cols: usize,
3907 out: &mut [f32],
3908) -> bool {
3909 match backend() {
3910 #[cfg(target_os = "macos")]
3911 Backend::Metal => crate::gpu_metal::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3912 #[cfg(feature = "gpu")]
3913 Backend::Wgpu => crate::gpu_wgpu::q2tp_affine_matvec(model, idx, xs, rows, cols, out),
3914 #[allow(unreachable_patterns)]
3915 _ => false,
3916 }
3917}
3918
3919pub fn q4tp_matvec(
3924 model: &Arc<CmfModel>,
3925 idx: usize,
3926 xs: &[f32],
3927 rows: usize,
3928 cols: usize,
3929 out: &mut [f32],
3930) -> bool {
3931 match backend() {
3932 #[cfg(target_os = "macos")]
3933 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
3934 #[cfg(feature = "gpu")]
3935 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
3936 #[allow(unreachable_patterns)]
3937 _ => false,
3938 }
3939}
3940
3941pub fn q4t_matvec(
3947 model: &Arc<CmfModel>,
3948 idx: usize,
3949 xs: &[f32],
3950 rows: usize,
3951 cols: usize,
3952 out: &mut [f32],
3953) -> bool {
3954 match backend() {
3955 #[cfg(target_os = "macos")]
3956 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
3957 #[allow(unreachable_patterns)]
3958 _ => false,
3959 }
3960}
3961
3962pub fn q4t_matmat(
3963 model: &Arc<CmfModel>,
3964 idx: usize,
3965 xs: &[f32],
3966 b: usize,
3967 rows: usize,
3968 cols: usize,
3969 out: &mut [f32],
3970) -> bool {
3971 match backend() {
3972 #[cfg(target_os = "macos")]
3973 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
3974 #[cfg(feature = "gpu")]
3975 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
3976 #[allow(unreachable_patterns)]
3977 _ => false,
3978 }
3979}
3980
3981#[cfg(target_os = "macos")]
3983pub use crate::gpu_metal::{
3984 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
3985 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
3986};
3987
3988#[cfg(target_os = "macos")]
3990pub fn gdn_block(
3991 model: &Arc<CmfModel>,
3992 layers: &[GdnGpuLayer],
3993 states: &mut [&mut [f32]],
3994 cfg: &GdnGpuCfg,
3995 h: &mut [f32],
3996) -> bool {
3997 match backend() {
3998 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
3999 _ => false,
4000 }
4001}
4002
4003#[allow(unused_variables)]
4005pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
4006 match backend() {
4007 #[cfg(target_os = "macos")]
4010 Backend::Metal if jobs.iter().any(|j| j.act != 0) => false,
4011 #[cfg(target_os = "macos")]
4012 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
4013 #[cfg(feature = "gpu")]
4014 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
4015 Backend::None => false,
4016 }
4017}
4018
4019#[allow(unused_variables)]
4021pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
4022 match backend() {
4023 #[cfg(target_os = "macos")]
4024 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
4025 #[cfg(feature = "gpu")]
4026 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
4027 Backend::None => false,
4028 }
4029}
4030
4031static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
4047static 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)];
4051
4052const GRAPH_RACE_SAMPLES: u32 = 4;
4054
4055pub fn graph_race_begin_generation() {
4074 #[cfg(feature = "gpu")]
4079 {
4080 static FLUSHED: std::sync::Once = std::sync::Once::new();
4092 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
4093 if FIRST.swap(false, Ordering::Relaxed) {
4094 } else {
4096 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
4097 }
4098 }
4099 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
4100 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4101 return;
4102 }
4103 let (gn, cn) = (
4104 GRAPH_N[1].load(Ordering::Relaxed),
4105 GRAPH_N[0].load(Ordering::Relaxed),
4106 );
4107 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
4108 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
4109 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4110 let verdict = if g_avg < c_avg { 1 } else { 2 };
4111 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
4112 tracing::info!(
4113 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
4114 g_avg as f64 / 1e6,
4115 c_avg as f64 / 1e6,
4116 if verdict == 1 { "graph" } else { "normal path" }
4117 );
4118 return;
4119 }
4120 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
4121 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
4122}
4123
4124pub fn graph_race_use_graph(trusted: bool) -> bool {
4128 if trusted {
4129 return true;
4130 }
4131 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
4132 1 => true,
4133 2 => false,
4134 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
4135 }
4136}
4137
4138pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
4143 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4144 return false;
4145 }
4146 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
4147 let cn = GRAPH_N[0].load(Ordering::Relaxed);
4148 if !first || cn == 0 {
4149 return false;
4150 }
4151 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
4152 let ns = dur.as_nanos() as u64;
4153 if ns > 1_000_000_000 && ns > 4 * c_avg {
4154 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
4155 tracing::info!(
4156 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
4157 ns as f64 / 1e6,
4158 c_avg as f64 / 1e6
4159 );
4160 return true;
4161 }
4162 false
4163}
4164
4165pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
4169 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
4170 return;
4171 }
4172 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
4173 if tok == 0 {
4174 return;
4175 }
4176 let i = used_graph as usize;
4177 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
4178 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
4179}
4180
4181pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
4191 #[inline]
4192 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
4193 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
4194 for c in chunks.chunks_exact(8) {
4195 h ^= u64::from_le_bytes(c.try_into().unwrap());
4196 h = h.wrapping_mul(0x100_0000_01b3);
4197 }
4198 for &b in tail {
4199 h ^= b as u64;
4200 h = h.wrapping_mul(0x100_0000_01b3);
4201 }
4202 h
4203 }
4204 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
4205 if data.len() <= 4096 {
4206 return fnv(h, data);
4207 }
4208 let step = (data.len() - 64) / 63;
4209 for i in 0..64 {
4210 h = fnv(h, &data[i * step..i * step + 64]);
4211 }
4212 h
4213}
4214
4215pub(crate) fn fp_f32(data: &[f32]) -> u64 {
4218 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
4219 fp_bytes(bytes)
4220}
4221
4222#[cfg(test)]
4223mod fp_tests {
4224 use super::fp_bytes;
4225
4226 #[test]
4231 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
4232 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
4234 let h0 = fp_bytes(&base);
4235 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
4236 let mut dense = base.clone();
4239 for b in dense.iter_mut() {
4240 *b = b.wrapping_add(1);
4241 }
4242 assert_ne!(
4243 h0,
4244 fp_bytes(&dense),
4245 "a fully different tensor slipped through"
4246 );
4247 assert_ne!(h0, fp_bytes(&base[..n - 64]));
4250 let mut small = vec![3u8; 4096];
4253 let hs = fp_bytes(&small);
4254 small[2048] ^= 1;
4255 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
4256 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
4258 let v = vec![9u8; n];
4259 let _ = fp_bytes(&v); }
4261 }
4262}
4263
4264pub fn bake_release() {
4268 #[cfg(feature = "gpu")]
4269 crate::gpu_wgpu::bake_release();
4270}
4271
4272pub fn bake_precision_strict(on: bool) {
4276 #[cfg(feature = "gpu")]
4277 crate::gpu_wgpu::bake_precision_strict(on);
4278 #[cfg(not(feature = "gpu"))]
4279 let _ = on;
4280}
4281
4282pub fn hostprof_encode_done(t0: std::time::Instant) {
4288 use std::sync::atomic::{AtomicU64, Ordering};
4289 static ENC: AtomicU64 = AtomicU64::new(0);
4290 static N: AtomicU64 = AtomicU64::new(0);
4291 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4292 return;
4293 }
4294 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4295 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4296 if n % 100 == 0 {
4297 eprintln!(
4298 "hostprof: encode {:.2} ms/token over {n} tokens",
4299 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4300 );
4301 }
4302}
4303
4304pub fn hostprof_total(t0: std::time::Instant) {
4305 use std::sync::atomic::{AtomicU64, Ordering};
4306 static TOT: AtomicU64 = AtomicU64::new(0);
4307 static N: AtomicU64 = AtomicU64::new(0);
4308 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4309 return;
4310 }
4311 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
4312 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4313 if n % 100 == 0 {
4314 eprintln!(
4315 "hostprof: total {:.2} ms/token over {n} tokens",
4316 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4317 );
4318 }
4319}
4320
4321pub fn stageprof(stage: u32, dt: std::time::Duration) {
4325 use std::sync::atomic::{AtomicU64, Ordering};
4326 static NS: [AtomicU64; 4] = [
4327 AtomicU64::new(0),
4328 AtomicU64::new(0),
4329 AtomicU64::new(0),
4330 AtomicU64::new(0),
4331 ];
4332 static N: AtomicU64 = AtomicU64::new(0);
4333 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
4334 return;
4335 }
4336 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
4337 if stage == 1 {
4338 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
4339 if n % 200 == 0 {
4340 eprintln!(
4341 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
4342 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4343 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
4344 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
4345 );
4346 }
4347 }
4348}
4349
4350pub fn weight_bytes_dispatched() -> u64 {
4353 let mut total = 0u64;
4354 #[cfg(target_os = "macos")]
4355 {
4356 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4357 }
4358 #[cfg(feature = "gpu")]
4359 {
4360 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
4361 }
4362 total
4363}
4364
4365pub fn weight_bytes_by() -> [u64; 6] {
4368 #[cfg(target_os = "macos")]
4369 {
4370 let mut o = [0u64; 6];
4371 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
4372 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
4373 }
4374 return o;
4375 }
4376 #[allow(unreachable_code)]
4377 [0; 6]
4378}
4379
4380#[cfg(test)]
4381mod f16_guard_tests {
4382 use super::*;
4383
4384 #[test]
4387 fn abs_max_or_inf_is_the_magnitude_maximum() {
4388 let mut xs: Vec<f32> = (0..37).map(|i| (i as f32 - 18.0) * 0.5).collect();
4389 assert_eq!(abs_max_or_inf(&xs), 9.0);
4390 xs[36] = -70000.0; assert_eq!(abs_max_or_inf(&xs), 70000.0);
4392 xs[3] = f32::NAN; assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4394 xs[3] = 0.0;
4395 xs[35] = f32::NEG_INFINITY;
4396 assert_eq!(abs_max_or_inf(&xs), f32::INFINITY);
4397 assert_eq!(abs_max_or_inf(&[]), 0.0);
4398 assert_eq!(abs_max_or_inf(&[-0.0, -1e-30]), 1e-30);
4399 }
4400}
4401
4402#[cfg(test)]
4403mod probe_warmup_tests {
4404 use super::*;
4405 use std::time::Duration;
4406
4407 fn ms(v: f64) -> Duration {
4408 Duration::from_nanos((v * 1e6) as u64)
4409 }
4410
4411 #[test]
4416 fn one_cold_first_sample_does_not_lose_the_class() {
4417 let p = Probe::new();
4418 probe_record_into(&p, "gemm-nt", None, true, ms(117.01));
4420 probe_record_into(&p, "gemm-nt", None, true, ms(1.1));
4421 probe_record_into(&p, "gemm-nt", None, true, ms(1.0));
4422 probe_record_into(&p, "gemm-nt", None, false, ms(3.19));
4423 probe_record_into(&p, "gemm-nt", None, false, ms(3.20));
4424 assert_eq!(
4425 p.state.load(Ordering::Relaxed),
4426 1,
4427 "the device is 3x faster once warm and must win"
4428 );
4429 }
4430
4431 #[test]
4435 fn the_warmup_is_spent_once_and_never_underflows() {
4436 let p = Probe::new();
4437 for _ in 0..8 {
4438 probe_record_into(&p, "matmat", None, true, ms(10.0));
4439 }
4440 assert_eq!(p.gpu_burn.load(Ordering::Relaxed), 0, "spent, not wrapped");
4441 assert_eq!(
4442 p.gpu_n.load(Ordering::Relaxed),
4443 7,
4444 "one sample burned, the rest counted"
4445 );
4446 }
4447
4448 #[test]
4454 fn a_class_whose_device_always_declines_settles_on_the_host() {
4455 let _probe_guard = probe_test_guard();
4456 let c = OpClass::MatmatWide;
4460 let p = &PROBES[c as usize];
4461 p.state.store(0, Ordering::Relaxed);
4462 p.declines.store(0, Ordering::Relaxed);
4463 for _ in 0..(PROBE_DECLINE_LIMIT - 1) {
4464 probe_note_decline(c);
4465 }
4466 assert_eq!(
4467 p.state.load(Ordering::Relaxed),
4468 0,
4469 "one short of the limit is still a question, not an answer"
4470 );
4471 probe_note_decline(c);
4472 assert_eq!(p.state.load(Ordering::Relaxed), 2, "settled on the host");
4473 assert!(matches!(probe_arm(c), ProbeArm::Cpu));
4474 p.state.store(0, Ordering::Relaxed);
4475 p.declines.store(0, Ordering::Relaxed);
4476 }
4477
4478 #[test]
4481 fn a_slow_device_still_loses_after_the_warmup() {
4482 let p = Probe::new();
4483 for _ in 0..4 {
4484 probe_record_into(&p, "matvec", None, true, ms(40.0));
4485 }
4486 for _ in 0..4 {
4487 probe_record_into(&p, "matvec", None, false, ms(2.0));
4488 }
4489 assert_eq!(p.state.load(Ordering::Relaxed), 2, "host wins on merit");
4490 }
4491}
4492
4493pub(crate) struct ImageStageGuard {
4496 #[cfg(target_os = "macos")]
4497 metal: Option<crate::gpu_metal::ImageStageGuard>,
4498 #[cfg(feature = "gpu")]
4499 wgpu: crate::gpu_wgpu::ImageStageGuard,
4500}
4501
4502pub(crate) fn image_stage_scope() -> ImageStageGuard {
4503 ImageStageGuard {
4504 #[cfg(target_os = "macos")]
4505 metal: if matches!(backend(), Backend::Metal) {
4506 Some(crate::gpu_metal::image_stage_scope())
4507 } else {
4508 None
4509 },
4510 #[cfg(feature = "gpu")]
4511 wgpu: crate::gpu_wgpu::image_stage_scope(),
4512 }
4513}
4514
4515impl ImageStageGuard {
4516 pub(crate) fn track_model(&mut self, uid: u64) {
4517 #[cfg(target_os = "macos")]
4518 if let Some(metal) = &mut self.metal {
4519 metal.track_model(uid);
4520 }
4521 #[cfg(feature = "gpu")]
4522 self.wgpu.track_model(uid);
4523 #[cfg(not(target_os = "macos"))]
4524 let _ = uid;
4525 }
4526}
4527
4528#[derive(Clone, Copy, Debug, PartialEq)]
4548pub struct Qi21Geom {
4549 pub hidden: usize,
4550 pub nh: usize,
4551 pub hd: usize,
4552 pub inter: usize,
4553 pub in_ch: usize,
4554 pub eps: f32,
4555}
4556
4557pub struct Qi21BlockRef<'a> {
4560 pub w: [usize; 7],
4561 pub norm_q: &'a [f32],
4562 pub norm_k: &'a [f32],
4563}
4564
4565pub struct Qi21PrefillArgs<'a> {
4567 pub model: &'a Arc<CmfModel>,
4568 pub geom: Qi21Geom,
4569 pub blocks: &'a [Qi21BlockRef<'a>],
4570 pub img_in: &'a [f32],
4572 pub proj_out: &'a [f32],
4573 pub key: u64,
4574 pub x: &'a [f32],
4576 pub lp: usize,
4577 pub rope_p: (&'a [f32], &'a [f32]),
4579 pub rope_t: (&'a [f32], &'a [f32]),
4580 pub vis: &'a [u32],
4582 pub mods0: &'a [f32],
4584 pub n: usize,
4586}
4587
4588#[allow(unused_variables)]
4590pub fn qi21_prefill(a: &Qi21PrefillArgs) -> bool {
4591 match backend() {
4592 #[cfg(target_os = "macos")]
4593 Backend::Metal => crate::gpu_metal::qi21::prefill(a),
4594 #[cfg(feature = "gpu")]
4595 Backend::Wgpu => crate::gpu_wgpu::qi21::prefill(a),
4596 #[allow(unreachable_patterns)]
4597 _ => false,
4598 }
4599}
4600
4601#[allow(unused_variables)]
4603pub fn qi21_step(key: u64, xtok: &[f32], mods: &[f32], fs: &[f32], out: &mut [f32]) -> bool {
4604 match backend() {
4605 #[cfg(target_os = "macos")]
4606 Backend::Metal => crate::gpu_metal::qi21::step(key, xtok, mods, fs, out),
4607 #[cfg(feature = "gpu")]
4608 Backend::Wgpu => crate::gpu_wgpu::qi21::step(key, xtok, mods, fs, out),
4609 #[allow(unreachable_patterns)]
4610 _ => false,
4611 }
4612}
4613
4614#[allow(unused_variables)]
4617pub fn qi21_release_key(key: u64) {
4618 #[cfg(target_os = "macos")]
4619 if matches!(backend(), Backend::Metal) {
4620 crate::gpu_metal::qi21::release_key(key);
4621 }
4622 #[cfg(feature = "gpu")]
4623 crate::gpu_wgpu::qi21::release_key(key);
4624}
4625
4626pub fn qi21_release() {
4629 #[cfg(target_os = "macos")]
4630 if matches!(backend(), Backend::Metal) {
4631 crate::gpu_metal::qi21::release();
4632 }
4633 #[cfg(feature = "gpu")]
4634 {
4635 crate::gpu_wgpu::qi21::release();
4636 crate::gpu_wgpu::qi21_vae::release();
4637 }
4638}
4639
4640#[derive(Clone, Copy)]
4643pub struct Qi21VaeConvRef<'a> {
4644 pub w: &'a [f32],
4645 pub b: &'a [f32],
4646 pub ci: usize,
4647 pub co: usize,
4648 pub k: usize,
4649}
4650
4651pub struct Qi21VaeResRef<'a> {
4654 pub g1: &'a [f32],
4655 pub c1: Qi21VaeConvRef<'a>,
4656 pub g2: &'a [f32],
4657 pub c2: Qi21VaeConvRef<'a>,
4658 pub shortcut: Option<Qi21VaeConvRef<'a>>,
4659}
4660
4661pub struct Qi21VaeUpRef<'a> {
4665 pub resnets: Vec<Qi21VaeResRef<'a>>,
4666 pub up: Option<(Qi21VaeConvRef<'a>, usize)>,
4667 pub in_dim: usize,
4668 pub out_dim: usize,
4669}
4670
4671pub struct Qi21VaeDecodeArgs<'a> {
4674 pub key: u64,
4676 pub post_quant: Qi21VaeConvRef<'a>,
4677 pub conv_in: Qi21VaeConvRef<'a>,
4678 pub mid_res: [Qi21VaeResRef<'a>; 2],
4679 pub attn_gamma: &'a [f32],
4682 pub attn_qkv: Qi21VaeConvRef<'a>,
4683 pub attn_proj: Qi21VaeConvRef<'a>,
4684 pub ups: Vec<Qi21VaeUpRef<'a>>,
4685 pub norm_out: &'a [f32],
4686 pub conv_out: Qi21VaeConvRef<'a>,
4687}
4688
4689#[allow(unused_variables)]
4693pub fn qi21_vae_decode(a: &Qi21VaeDecodeArgs, z: &[f32], h: usize, w: usize, out: &mut [f32]) -> bool {
4694 match backend() {
4695 #[cfg(feature = "gpu")]
4696 Backend::Wgpu => crate::gpu_wgpu::qi21_vae::decode(a, z, h, w, out),
4697 #[allow(unreachable_patterns)]
4698 _ => false,
4699 }
4700}
4701
4702#[derive(Clone, Copy)]
4710pub struct ZBlockRef<'a> {
4711 pub wq: usize,
4713 pub wk: usize,
4714 pub wv: usize,
4715 pub wo: usize,
4716 pub w1: usize,
4719 pub w3: usize,
4720 pub w2: usize,
4721 pub norm1: &'a [f32],
4723 pub norm2: &'a [f32],
4724 pub ffn_norm1: &'a [f32],
4726 pub ffn_norm2: &'a [f32],
4727 pub norm_q: &'a [f32],
4729 pub norm_k: &'a [f32],
4730}
4731
4732#[derive(Clone, Copy, Debug, PartialEq)]
4736pub struct ZGeom {
4737 pub hidden: usize,
4738 pub nh: usize,
4739 pub hd: usize,
4740 pub inter: usize,
4741 pub eps: f32,
4742 pub final_eps: f32,
4743 pub patch_dim: usize,
4744}
4745
4746pub struct ZPrepareArgs<'a> {
4750 pub model: &'a Arc<CmfModel>,
4751 pub geom: ZGeom,
4752 pub key: u64,
4755 pub n_img: usize,
4758 pub n_img_p: usize,
4759 pub n_cap_p: usize,
4760 pub grid: (usize, usize),
4763 pub cap: &'a [f32],
4765 pub rope_img: (&'a [f32], &'a [f32]),
4768 pub rope_joint: (&'a [f32], &'a [f32]),
4770 pub x_emb_w: &'a [f32],
4773 pub x_emb_b: &'a [f32],
4774 pub x_pad: &'a [f32],
4775 pub final_w: &'a [f32],
4777 pub final_b: &'a [f32],
4778 pub noise_refiner: &'a [ZBlockRef<'a>],
4780 pub layers: &'a [ZBlockRef<'a>],
4781 pub mods_all: Option<&'a [f32]>,
4787 pub final_scale_all: Option<&'a [f32]>,
4788 pub neg: Option<ZNegArgs<'a>>,
4794}
4795
4796pub struct ZNegArgs<'a> {
4800 pub cap: &'a [f32],
4802 pub n_cap_p: usize,
4803 pub rope_img: (&'a [f32], &'a [f32]),
4805 pub rope_joint: (&'a [f32], &'a [f32]),
4807}
4808
4809pub struct ZStepArgs<'a> {
4811 pub key: u64,
4814 pub step: usize,
4817 pub x_tok: &'a [f32],
4821 pub mods: &'a [f32],
4825 pub final_scale: &'a [f32],
4827 pub out: &'a mut [f32],
4830 pub out_neg: Option<&'a mut [f32]>,
4833}
4834
4835#[allow(unused_variables)]
4839pub fn zimage_prepare(a: &ZPrepareArgs) -> bool {
4840 match backend() {
4841 #[cfg(target_os = "macos")]
4842 Backend::Metal => crate::gpu_metal::zimage::prepare(a),
4843 #[cfg(feature = "gpu")]
4844 Backend::Wgpu => crate::gpu_wgpu::zimage::prepare(a),
4845 #[allow(unreachable_patterns)]
4846 _ => false,
4847 }
4848}
4849
4850#[allow(unused_variables)]
4854pub fn zimage_step(a: &mut ZStepArgs) -> bool {
4855 match backend() {
4856 #[cfg(target_os = "macos")]
4857 Backend::Metal => crate::gpu_metal::zimage::step(a),
4858 #[cfg(feature = "gpu")]
4859 Backend::Wgpu => crate::gpu_wgpu::zimage::step(a),
4860 #[allow(unreachable_patterns)]
4861 _ => false,
4862 }
4863}
4864
4865#[allow(unused_variables)]
4870pub fn zimage_preload(
4871 model: &Arc<CmfModel>,
4872 geom: &ZGeom,
4873 noise_refiner: &[ZBlockRef],
4874 layers: &[ZBlockRef],
4875 context_refiner: &[ZBlockRef],
4876) -> bool {
4877 match backend() {
4878 #[cfg(target_os = "macos")]
4879 Backend::Metal => {
4880 crate::gpu_metal::zimage::preload(model, geom, noise_refiner, layers, context_refiner)
4881 }
4882 #[cfg(feature = "gpu")]
4883 Backend::Wgpu => crate::gpu_wgpu::zimage::preload(model, geom, noise_refiner, layers, context_refiner),
4884 #[allow(unreachable_patterns)]
4885 _ => false,
4886 }
4887}
4888
4889pub fn zimage_flush_pipelines() {
4893 #[cfg(feature = "gpu")]
4894 if matches!(backend(), Backend::Wgpu) {
4895 crate::gpu_wgpu::pipeline_cache_flush();
4896 }
4897}
4898
4899pub fn zimage_warmup() -> bool {
4904 match backend() {
4905 #[cfg(target_os = "macos")]
4906 Backend::Metal => crate::gpu_metal::zimage::warmup(),
4907 #[cfg(feature = "gpu")]
4908 Backend::Wgpu => crate::gpu_wgpu::zimage::warmup(),
4909 #[allow(unreachable_patterns)]
4910 _ => false,
4911 }
4912}
4913
4914#[allow(unused_variables)]
4918pub fn vae_prewarm(a: &crate::vae::VaeChainArgs) -> bool {
4919 match backend() {
4920 #[cfg(target_os = "macos")]
4921 Backend::Metal => crate::gpu_metal::zimage::vae_prewarm(a),
4922 #[cfg(feature = "gpu")]
4923 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_prewarm(a),
4924 #[allow(unreachable_patterns)]
4925 _ => false,
4926 }
4927}
4928
4929pub fn zimage_release_dit() {
4932 #[cfg(target_os = "macos")]
4933 crate::gpu_metal::zimage::release_dit();
4934 #[cfg(feature = "gpu")]
4935 crate::gpu_wgpu::zimage::release_dit();
4936}
4937
4938pub fn zimage_release() {
4943 #[cfg(target_os = "macos")]
4944 crate::gpu_metal::zimage::release();
4945 #[cfg(feature = "gpu")]
4946 crate::gpu_wgpu::zimage::release();
4947}
4948
4949#[allow(unused_variables)]
4955pub fn zimage_refine_caption(
4956 model: &Arc<CmfModel>,
4957 geom: &ZGeom,
4958 blocks: &[ZBlockRef],
4959 rope_cap: (&[f32], &[f32]),
4960 cap: &mut [f32],
4961) -> bool {
4962 match backend() {
4963 #[cfg(target_os = "macos")]
4964 Backend::Metal => {
4965 crate::gpu_metal::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4966 }
4967 #[cfg(feature = "gpu")]
4968 Backend::Wgpu => {
4969 crate::gpu_wgpu::zimage::refine_caption(model, geom, blocks, rope_cap, cap)
4970 }
4971 #[allow(unreachable_patterns)]
4972 _ => false,
4973 }
4974}
4975
4976#[allow(unused_variables)]
4982pub fn vae_decode_chain(
4983 a: &crate::vae::VaeChainArgs,
4984 z: &[f32],
4985 h: usize,
4986 w: usize,
4987 out: &mut [f32],
4988) -> bool {
4989 match backend() {
4990 #[cfg(target_os = "macos")]
4991 Backend::Metal => crate::gpu_metal::zimage::vae_decode_chain(a, z, h, w, out),
4992 #[cfg(feature = "gpu")]
4993 Backend::Wgpu => crate::gpu_wgpu::zimage::vae_decode_chain(a, z, h, w, out),
4994 #[allow(unreachable_patterns)]
4995 _ => false,
4996 }
4997}