1use cortiq_core::CmfModel;
14use std::cell::Cell;
15use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU32, AtomicU64, Ordering};
16use std::sync::{Arc, OnceLock};
17
18thread_local! {
19 static CUR_LAYER: Cell<i64> = const { Cell::new(-1) };
23 static CPU_ONLY: Cell<bool> = const { Cell::new(false) };
28 static PROBE_COLD: Cell<bool> = const { Cell::new(false) };
32}
33
34pub fn cpu_scope<R>(f: impl FnOnce() -> R) -> R {
36 struct Restore(bool);
37 impl Drop for Restore {
38 fn drop(&mut self) {
39 CPU_ONLY.with(|c| c.set(self.0));
40 }
41 }
42 let previous = CPU_ONLY.with(|c| c.replace(true));
43 let _restore = Restore(previous);
44 f()
45}
46
47pub(crate) fn probe_note_cold() {
50 PROBE_COLD.with(|c| c.set(true));
51}
52
53pub(crate) fn probe_was_cold() -> bool {
57 PROBE_COLD.with(|c| c.get())
58}
59
60pub fn set_layer(l: i64) {
62 CUR_LAYER.with(|c| c.set(l));
63}
64
65pub fn cur_layer() -> i64 {
67 CUR_LAYER.with(|c| c.get())
68}
69
70fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
73 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
74 R.get_or_init(|| {
75 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
76 let mut v = Vec::new();
77 for part in s.split(',') {
78 let part = part.trim();
79 match part.split_once('-') {
80 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
81 None => {
82 let x: i64 = part.parse().ok()?;
83 v.push((x, x));
84 }
85 }
86 }
87 Some(v)
88 })
89}
90
91fn layer_allowed() -> bool {
92 match layer_ranges() {
93 None => true,
94 Some(ranges) => {
95 let cur = CUR_LAYER.with(|c| c.get());
96 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
97 }
98 }
99}
100
101pub fn enabled_here() -> bool {
105 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
106}
107
108#[derive(Clone, Copy)]
120pub enum OpClass {
121 Ffn = 0,
123 Matvec = 1,
125 Matmat = 2,
127 Batch = 3,
129 MatmatWide = 4,
135 MatvecHead = 5,
142 GemmNt = 6,
149}
150
151pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
155 if rows * cols >= 67_108_864 {
156 OpClass::MatvecHead
157 } else {
158 OpClass::Matvec
159 }
160}
161
162pub enum ProbeArm {
164 Gpu,
166 CpuTimed,
168 Cpu,
170}
171
172const PROBE_SAMPLES: u32 = 6;
174
175struct Probe {
176 state: AtomicU8,
178 flip: AtomicU32,
179 gpu_ns: AtomicU64,
180 gpu_n: AtomicU32,
181 cpu_ns: AtomicU64,
182 cpu_n: AtomicU32,
183 gpu_min: AtomicU64,
190 cpu_min: AtomicU64,
191}
192
193impl Probe {
194 const fn new() -> Self {
195 Self {
196 state: AtomicU8::new(0),
197 flip: AtomicU32::new(0),
198 gpu_ns: AtomicU64::new(0),
199 gpu_n: AtomicU32::new(0),
200 cpu_ns: AtomicU64::new(0),
201 cpu_n: AtomicU32::new(0),
202 gpu_min: AtomicU64::new(u64::MAX),
203 cpu_min: AtomicU64::new(u64::MAX),
204 }
205 }
206}
207
208static PROBES: [Probe; 7] = [
209 Probe::new(),
210 Probe::new(),
211 Probe::new(),
212 Probe::new(),
213 Probe::new(),
214 Probe::new(),
215 Probe::new(),
216];
217
218fn probe_on() -> bool {
219 static ON: OnceLock<bool> = OnceLock::new();
220 *ON.get_or_init(|| {
221 std::env::var("CMF_GPU_PROBE")
222 .map(|v| v != "0" && v != "off")
223 .unwrap_or(true)
224 })
225}
226
227pub fn q1_force() -> bool {
232 #[cfg(target_os = "macos")]
233 {
234 backend() == Backend::Metal
235 }
236 #[cfg(not(target_os = "macos"))]
237 {
238 false
239 }
240}
241
242pub fn fused_block_trusted() -> bool {
261 #[cfg(target_os = "macos")]
262 if backend() == Backend::Metal {
263 return true;
264 }
265 wgpu_graph_default()
266}
267
268pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
280 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
281 {
282 return crate::gpu_wgpu::weight_is_resident(model, idx);
283 }
284 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
285 {
286 let _ = (model, idx);
287 true
288 }
289}
290
291pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
292 if !weights_resident && probe_deciding(c) {
293 return ProbeArm::Gpu;
294 }
295 probe_arm(c)
296}
297
298pub fn probe_arm(c: OpClass) -> ProbeArm {
299 PROBE_COLD.with(|f| f.set(false));
304 if !probe_on() {
305 return ProbeArm::Gpu;
306 }
307 let p = &PROBES[c as usize];
308 match p.state.load(Ordering::Relaxed) {
309 1 => ProbeArm::Gpu,
310 2 => ProbeArm::Cpu,
311 _ => {
312 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
313 ProbeArm::Gpu
314 } else {
315 ProbeArm::CpuTimed
316 }
317 }
318 }
319}
320
321pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
324 let p = &PROBES[c as usize];
325 if p.state.load(Ordering::Relaxed) != 0 {
326 return;
327 }
328 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
329 return; }
331 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
332 if gpu {
333 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
334 p.gpu_n.fetch_add(1, Ordering::Relaxed);
335 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
336 } else {
337 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
338 p.cpu_n.fetch_add(1, Ordering::Relaxed);
339 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
340 }
341 let (gn, cn) = (
342 p.gpu_n.load(Ordering::Relaxed),
343 p.cpu_n.load(Ordering::Relaxed),
344 );
345 if gn >= 2 && cn >= 2 {
346 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
350 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
351 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
361 return;
362 }
363 let winner = if g <= cp { 1 } else { 2 };
364 if p.state
365 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
366 .is_ok()
367 {
368 tracing::info!(
369 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
370 ["ffn", "matvec", "matmat", "qkv-batch", "matmat-wide", "lm-head", "gemm-nt"]
371 [c as usize],
372 g / 1e6,
373 cp / 1e6,
374 if winner == 1 { "gpu" } else { "cpu" },
375 );
376 }
377 }
378}
379
380pub fn probe_deciding(c: OpClass) -> bool {
383 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
384}
385
386#[allow(unused_variables)]
396pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
397 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
398 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
399 let resident = match backend() {
400 #[cfg(target_os = "macos")]
401 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
402 #[cfg(feature = "gpu")]
403 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
404 Backend::None => false,
405 };
406 if !resident && may_upload {
407 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
408 }
409 resident
410}
411
412#[cfg(test)]
414pub(crate) fn probe_reset() {
415 for p in &PROBES {
416 p.state.store(0, Ordering::Relaxed);
417 p.flip.store(0, Ordering::Relaxed);
418 p.gpu_ns.store(0, Ordering::Relaxed);
419 p.gpu_n.store(0, Ordering::Relaxed);
420 p.cpu_ns.store(0, Ordering::Relaxed);
421 p.cpu_n.store(0, Ordering::Relaxed);
422 }
423}
424
425#[cfg(test)]
426mod probe_tests {
427 use super::*;
428 use std::time::Duration;
429
430 #[test]
433 fn probe_alternates_discards_cold_and_decides() {
434 probe_reset();
435 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
437 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
438
439 probe_note_cold();
443 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
444 for _ in 0..PROBE_SAMPLES {
445 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
446 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
447 }
448 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
449
450 for _ in 0..PROBE_SAMPLES {
452 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
453 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
454 }
455 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
456
457 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
459 CPU_ONLY.with(|c| assert!(!c.get()));
460 cpu_scope(|| {
461 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
462 CPU_ONLY.with(|c| assert!(c.get()));
463 });
464 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
465 CPU_ONLY.with(|c| assert!(!c.get()));
466 probe_reset();
467 }
468}
469
470pub const GPU_MIN_ROWS: usize = 65_536;
473
474pub fn min_rows() -> usize {
481 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
482 .ok()
483 .and_then(|v| v.parse().ok())
484 {
485 return v;
486 }
487 if discrete() { 4096 } else { GPU_MIN_ROWS }
488}
489
490pub fn discrete() -> bool {
492 match backend() {
493 #[cfg(feature = "gpu")]
494 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
495 #[cfg(target_os = "macos")]
496 Backend::Metal => false, Backend::None => false,
498 }
499}
500
501pub struct MoeJob<'a> {
505 pub gate: (usize, usize, usize, &'a [f32]),
506 pub up: (usize, usize, usize, &'a [f32]),
507 pub down: (usize, usize, usize, &'a [f32]),
508 pub xs_gate: Vec<f32>,
509 pub xs_up: Vec<f32>,
510 pub down_col: &'a [f32],
511 pub w: f32,
512 pub q1: bool,
515 pub q4t: bool,
518 pub q4tp: bool,
522 pub gu_q2: bool,
526 pub swiglu_limit: f32,
531}
532
533pub struct BatchJob<'a> {
535 pub idx: usize,
536 pub rows: usize,
537 pub cols: usize,
538 pub row_scale: &'a [f32],
539 pub xs: Vec<f32>,
540 pub layout: BatchLayout,
544}
545
546#[derive(Clone, Copy, PartialEq, Eq, Debug)]
549pub enum BatchLayout {
550 Q8,
551 Q1,
552 Q4t,
553 Q4tp,
554}
555
556#[derive(Clone, Copy, PartialEq, Eq)]
557enum Backend {
558 None,
559 #[cfg(target_os = "macos")]
560 Metal,
561 #[cfg(feature = "gpu")]
562 Wgpu,
563}
564
565fn backend() -> Backend {
566 #[cfg(feature = "gpu")]
567 if crate::gpu_wgpu::selected() {
568 return if crate::gpu_wgpu::enabled() {
569 Backend::Wgpu
570 } else {
571 Backend::None
572 };
573 }
574 #[cfg(target_os = "macos")]
575 if crate::gpu_metal::enabled() {
576 return Backend::Metal;
577 }
578 Backend::None
579}
580
581pub fn backend_available() -> bool {
587 #[cfg(target_os = "macos")]
588 {
589 true
591 }
592 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
593 {
594 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
595 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
596 }
597 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
598 {
599 false
600 }
601}
602
603pub fn enabled() -> bool {
604 backend() != Backend::None
605}
606
607pub fn wgpu_active() -> bool {
621 #[cfg(feature = "gpu")]
622 {
623 matches!(backend(), Backend::Wgpu)
624 }
625 #[cfg(not(feature = "gpu"))]
626 {
627 false
628 }
629}
630
631pub fn default_device() -> usize {
638 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
639 *D.get_or_init(|| {
640 std::env::var("CMF_GPU_ADAPTER")
641 .ok()
642 .and_then(|v| v.trim().parse::<usize>().ok())
643 .unwrap_or(0)
644 })
645}
646
647thread_local! {
648 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
649}
650
651pub fn current_device() -> usize {
653 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
654}
655
656pub fn set_current_device(i: usize) {
660 CUR_DEV.with(|c| c.set(Some(i)));
661}
662
663pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
665 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
666 let r = f();
667 CUR_DEV.with(|c| c.set(prev));
668 r
669}
670
671pub fn device_count() -> usize {
674 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
675 {
676 return crate::gpu_wgpu::adapter_count();
677 }
678 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
679 {
680 usize::from(backend_available())
681 }
682}
683
684pub fn vram_budget() -> u64 {
688 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
689 {
690 return crate::gpu_wgpu::device_vram_budget();
691 }
692 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
693 {
694 if backend_available() { u64::MAX } else { 0 }
695 }
696}
697
698pub fn upload_bytes() -> u64 {
702 #[cfg(feature = "gpu")]
703 {
704 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
705 }
706 #[cfg(not(feature = "gpu"))]
707 0
708}
709
710pub fn wgpu_graph_default() -> bool {
711 #[cfg(feature = "gpu")]
712 {
713 matches!(backend(), Backend::Wgpu)
719 && (crate::gpu_wgpu::discrete_active()
720 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
721 }
722 #[cfg(not(feature = "gpu"))]
723 {
724 false
725 }
726}
727
728#[allow(clippy::too_many_arguments, unused_variables)]
730pub fn q8_matvec_range(
731 model: &Arc<CmfModel>,
732 idx: usize,
733 row0: usize,
734 row_scale: &[f32],
735 xs: &[f32],
736 rows: usize,
737 cols: usize,
738 out: &mut [f32],
739) -> bool {
740 match backend() {
741 #[cfg(target_os = "macos")]
742 Backend::Metal => {
743 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
744 }
745 #[cfg(feature = "gpu")]
746 Backend::Wgpu => {
747 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
748 }
749 Backend::None => false,
750 }
751}
752
753#[allow(clippy::too_many_arguments, unused_variables)]
756pub fn q8_matmat(
757 model: &Arc<CmfModel>,
758 idx: usize,
759 row_scale: &[f32],
760 pre: &[f32],
761 b: usize,
762 rows: usize,
763 cols: usize,
764 out: &mut [f32],
765) -> bool {
766 match backend() {
767 #[cfg(target_os = "macos")]
768 Backend::Metal => {
769 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
770 }
771 #[cfg(feature = "gpu")]
772 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
773 Backend::None => false,
774 }
775}
776
777#[allow(unused_variables)]
780pub fn q1_matvec(
781 model: &Arc<CmfModel>,
782 idx: usize,
783 xs: &[f32],
784 rows: usize,
785 cols: usize,
786 out: &mut [f32],
787) -> bool {
788 match backend() {
789 #[cfg(target_os = "macos")]
790 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
791 #[cfg(feature = "gpu")]
792 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
793 Backend::None => false,
794 }
795}
796
797#[allow(clippy::too_many_arguments)]
801pub fn attn_dropin(
802 model: &Arc<CmfModel>,
803 kv_id: u64,
804 layer: usize,
805 normed: &[f32],
806 wq_idx: usize,
807 wk_idx: usize,
808 wv_idx: usize,
809 wo_idx: usize,
810 q_norm: Option<&[f32]>,
811 k_norm: Option<&[f32]>,
812 invf: &[f32],
813 nh: usize,
814 nkv: usize,
815 hd: usize,
816 rd: usize,
817 hidden: usize,
818 pos: usize,
819 cap: usize,
820 gemma: bool,
821 eps: f32,
822 cpu_k: &[Vec<f32>],
823 cpu_v: &[Vec<f32>],
824 out: &mut [f32],
825) -> bool {
826 match backend() {
827 #[cfg(feature = "gpu")]
828 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
829 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
830 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
831 ),
832 #[allow(unused_variables)]
833 _ => false,
834 }
835}
836
837pub struct GraphW<'a> {
841 pub idx: usize,
842 pub kind: u8,
843 pub row_scale: &'a [f32],
844 pub data: &'a [f32],
845}
846
847pub enum GraphAttn<'a> {
850 Full {
851 wq: GraphW<'a>,
852 wk: GraphW<'a>,
853 wv: GraphW<'a>,
854 wo: GraphW<'a>,
855 q_norm: Option<&'a [f32]>,
856 k_norm: Option<&'a [f32]>,
857 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
859 output_gate: bool,
862 cpu_k: &'a [Vec<f32>],
863 cpu_v: &'a [Vec<f32>],
864 },
865 Gdn {
866 qkv: GraphW<'a>,
867 z: GraphW<'a>,
868 a: GraphW<'a>,
869 b: GraphW<'a>,
870 out: GraphW<'a>,
871 conv1d: &'a [f32],
872 a_log: &'a [f32],
873 dt_bias: &'a [f32],
874 norm: &'a [f32],
875 nv: usize,
876 nk: usize,
877 dk: usize,
878 dv: usize,
879 kk: usize,
880 cpu_state: &'a [f32],
885 },
886}
887
888pub struct GraphLayer<'a> {
890 pub input_norm: &'a [f32],
891 pub attn: GraphAttn<'a>,
892 pub post_norm: &'a [f32],
893 pub ffn: GraphFfn<'a>,
894}
895
896pub enum GraphFfn<'a> {
901 Dense {
902 gate: GraphW<'a>,
903 up: GraphW<'a>,
904 down: GraphW<'a>,
905 },
906 Moe {
907 router: GraphW<'a>,
909 shared_gate: GraphW<'a>,
911 experts: Vec<(usize, usize, usize)>,
915 n_exp: usize,
917 top_k: usize,
918 inter: usize,
919 norm_topk: bool,
920 q4tp: bool,
926 gu_q2: bool,
930 },
931}
932
933#[allow(clippy::too_many_arguments)]
938pub fn forward_token_graph(
939 model: &Arc<CmfModel>,
940 kv_id: u64,
941 layers: &[GraphLayer],
942 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
945 o1_epoch: u64,
946 invf: &[f32],
947 h: &mut [f32],
948 nh: usize,
949 nkv: usize,
950 hd: usize,
951 rd: usize,
952 hidden: usize,
953 inter: usize,
954 position: usize,
955 cap: usize,
956 gemma: bool,
957 eps: f32,
958 lm_head: Option<(&GraphW, usize)>,
959 final_norm: &[f32],
960 logits: &mut Vec<f32>,
961 loop_norm_at: &[usize],
962 steps: usize,
963 embed: Option<(&GraphW, usize, f32)>,
964 ids_out: Option<&mut Vec<u32>>,
965 layers_run: Option<&mut usize>,
968 layer_base: usize,
972) -> bool {
973 match backend() {
974 #[cfg(feature = "gpu")]
975 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
976 model,
977 kv_id,
978 layers,
979 o1,
980 o1_epoch,
981 invf,
982 h,
983 nh,
984 nkv,
985 hd,
986 rd,
987 hidden,
988 inter,
989 position,
990 cap,
991 gemma,
992 eps,
993 lm_head,
994 final_norm,
995 logits,
996 loop_norm_at,
997 steps,
998 embed,
999 ids_out,
1000 layers_run,
1001 layer_base,
1002 ),
1003 #[allow(unused_variables)]
1004 _ => {
1005 let _ = (lm_head, final_norm, logits, loop_norm_at, layers_run, layer_base);
1006 false
1007 }
1008 }
1009}
1010
1011pub struct SpecTail<'a> {
1015 pub lm: GraphW<'a>,
1016 pub lm_rows: usize,
1017 pub final_norm: &'a [f32],
1018 pub logits_out: &'a mut Vec<f32>,
1019}
1020
1021#[allow(clippy::too_many_arguments)]
1025pub fn forward_batch_graph(
1026 model: &Arc<CmfModel>,
1027 kv_id: u64,
1028 layers: &[GraphLayer],
1029 invf: &[f32],
1030 h: &mut [f32],
1031 nh: usize,
1032 nkv: usize,
1033 hd: usize,
1034 rd: usize,
1035 hidden: usize,
1036 inter: usize,
1037 positions: &[usize],
1038 cap: usize,
1039 gemma: bool,
1040 eps: f32,
1041 k: usize,
1042 spec: Option<SpecTail<'_>>,
1043) -> bool {
1044 match backend() {
1045 #[cfg(feature = "gpu")]
1046 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1047 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1048 eps, k, spec,
1049 ),
1050 #[allow(unreachable_patterns)]
1051 _ => {
1052 let _ = spec;
1053 false
1054 }
1055 }
1056}
1057
1058pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1061 #[cfg(feature = "gpu")]
1062 if backend() == Backend::Wgpu {
1063 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1064 }
1065 #[allow(unreachable_code)]
1066 {
1067 let _ = (kv_id, slot);
1068 false
1069 }
1070}
1071
1072pub fn graph_kv_reset(_kv_id: u64) {
1074 #[cfg(feature = "gpu")]
1075 if backend() == Backend::Wgpu {
1076 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1077 }
1078}
1079
1080pub fn q1t_matvec(
1084 model: &Arc<CmfModel>,
1085 idx: usize,
1086 xs: &[f32],
1087 rows: usize,
1088 cols: usize,
1089 out: &mut [f32],
1090) -> bool {
1091 match backend() {
1092 #[cfg(target_os = "macos")]
1093 Backend::Metal => {
1094 if metal_q1t_enabled() {
1095 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1096 } else {
1097 false
1098 }
1099 }
1100 #[cfg(feature = "gpu")]
1101 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1102 Backend::None => false,
1103 }
1104}
1105
1106#[allow(unused_variables)]
1109pub fn q4b_matvec(
1110 model: &Arc<CmfModel>,
1111 idx: usize,
1112 xs: &[f32],
1113 rows: usize,
1114 cols: usize,
1115 out: &mut [f32],
1116) -> bool {
1117 match backend() {
1118 #[cfg(target_os = "macos")]
1119 Backend::Metal => false,
1120 #[cfg(feature = "gpu")]
1121 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1122 Backend::None => false,
1123 }
1124}
1125
1126pub fn q1t_matmat(
1129 model: &Arc<CmfModel>,
1130 idx: usize,
1131 xs: &[f32],
1132 b: usize,
1133 rows: usize,
1134 cols: usize,
1135 out: &mut [f32],
1136) -> bool {
1137 match backend() {
1138 #[cfg(target_os = "macos")]
1139 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1143 #[cfg(feature = "gpu")]
1144 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1145 Backend::None => false,
1146 }
1147}
1148
1149#[cfg(target_os = "macos")]
1153pub(crate) fn metal_q1t_enabled() -> bool {
1154 std::env::var("CMF_METAL_Q1T")
1155 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1156 .unwrap_or(true)
1157}
1158
1159pub fn q1_matmat(
1161 model: &Arc<CmfModel>,
1162 idx: usize,
1163 xs: &[f32],
1164 b: usize,
1165 rows: usize,
1166 cols: usize,
1167 out: &mut [f32],
1168) -> bool {
1169 match backend() {
1170 #[cfg(feature = "gpu")]
1171 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1172 #[allow(unused_variables)]
1173 _ => false,
1174 }
1175}
1176
1177static MM_KILL: AtomicBool = AtomicBool::new(false);
1182pub(crate) fn mm_killed() -> bool {
1183 MM_KILL.load(Ordering::Relaxed)
1184}
1185pub(crate) fn mm_kill() {
1186 MM_KILL.store(true, Ordering::Relaxed);
1187}
1188
1189#[allow(unused_variables, clippy::too_many_arguments)]
1194pub fn chunk_attend(
1195 q: &[f32],
1196 k: &[&[f32]],
1197 v: &[&[f32]],
1198 b: usize,
1199 s0: usize,
1200 nh: usize,
1201 nkv: usize,
1202 hd: usize,
1203 scale: f32,
1204 out: &mut [f32],
1205) -> bool {
1206 match backend() {
1207 #[cfg(feature = "gpu")]
1208 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1209 #[allow(unreachable_patterns)]
1210 _ => false,
1211 }
1212}
1213
1214#[allow(unused_variables, clippy::too_many_arguments)]
1218pub fn q4t_qkv(
1219 model: &Arc<CmfModel>,
1220 wq: usize,
1221 wk: usize,
1222 wv: usize,
1223 xs: &[f32],
1224 b: usize,
1225 cols: usize,
1226 rq: usize,
1227 rk: usize,
1228 rv: usize,
1229 out: &mut [f32],
1230) -> bool {
1231 match backend() {
1232 #[cfg(feature = "gpu")]
1233 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1234 #[allow(unreachable_patterns)]
1235 _ => false,
1236 }
1237}
1238
1239#[allow(unused_variables, clippy::too_many_arguments)]
1241#[allow(clippy::too_many_arguments, unused_variables)]
1245pub fn q4tp_ffn_packed(
1246 model: &Arc<CmfModel>,
1247 w1: usize,
1248 w2: usize,
1249 xs: &[f32],
1250 b: usize,
1251 hidden: usize,
1252 inter: usize,
1253 bias: Option<&[f32]>,
1254 out: &mut [f32],
1255) -> bool {
1256 match backend() {
1257 #[cfg(feature = "gpu")]
1258 Backend::Wgpu => {
1259 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1260 }
1261 #[allow(unreachable_patterns)]
1262 _ => false,
1263 }
1264}
1265
1266pub fn q4tp_ffn(
1267 model: &Arc<CmfModel>,
1268 w1: usize,
1269 w3: usize,
1270 w2: usize,
1271 xs: &[f32],
1272 b: usize,
1273 hidden: usize,
1274 inter: usize,
1275 out: &mut [f32],
1276) -> bool {
1277 match backend() {
1278 #[cfg(target_os = "macos")]
1279 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1280 #[cfg(feature = "gpu")]
1281 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1282 #[allow(unreachable_patterns)]
1283 _ => false,
1284 }
1285}
1286
1287pub fn q4t_ffn(
1288 model: &Arc<CmfModel>,
1289 w1: usize,
1290 w3: usize,
1291 w2: usize,
1292 xs: &[f32],
1293 b: usize,
1294 hidden: usize,
1295 inter: usize,
1296 out: &mut [f32],
1297) -> bool {
1298 match backend() {
1299 #[cfg(target_os = "macos")]
1300 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1301 #[cfg(feature = "gpu")]
1302 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1303 #[allow(unreachable_patterns)]
1304 _ => false,
1305 }
1306}
1307
1308pub struct DitBlockArgs<'a> {
1313 pub n: usize,
1314 pub hidden: usize,
1315 pub inter: usize,
1316 pub nh: usize,
1317 pub nkv: usize,
1318 pub hd: usize,
1319 pub eps: f32,
1320 pub rope_cos: &'a [f32],
1321 pub rope_sin: &'a [f32],
1322 pub norm1: &'a [f32],
1323 pub norm2: &'a [f32],
1324 pub ffn_norm1: &'a [f32],
1325 pub ffn_norm2: &'a [f32],
1326 pub norm_q: &'a [f32],
1327 pub norm_k: &'a [f32],
1328 pub s_msa: &'a [f32],
1329 pub gate_msa: &'a [f32],
1330 pub s_mlp: &'a [f32],
1331 pub gate_mlp: &'a [f32],
1332 pub wq: usize,
1333 pub wk: usize,
1334 pub wv: usize,
1335 pub wo: usize,
1336 pub w1: usize,
1337 pub w3: usize,
1338 pub w2: usize,
1339 pub q4tp: bool,
1343 pub resident_in: bool,
1346 pub resident_out: bool,
1350}
1351
1352pub fn dit_chain_supported() -> bool {
1356 #[cfg(feature = "gpu")]
1357 {
1358 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1359 }
1360 #[allow(unreachable_code)]
1361 false
1362}
1363
1364pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1367 #[cfg(feature = "gpu")]
1368 {
1369 if matches!(backend(), Backend::Wgpu) {
1370 return crate::gpu_wgpu::dit_state_fetch(_x);
1371 }
1372 }
1373 false
1374}
1375
1376#[allow(unused_variables)]
1380#[allow(unused_variables, clippy::too_many_arguments)]
1384pub fn dit_qkv(
1385 model: &Arc<CmfModel>,
1386 wq: usize,
1387 wk: usize,
1388 wv: usize,
1389 xs: &[f32],
1390 b: usize,
1391 hidden: usize,
1392 qrows: usize,
1393 kvrows: usize,
1394 q_out: &mut [f32],
1395 k_out: &mut [f32],
1396 v_out: &mut [f32],
1397) -> bool {
1398 match backend() {
1399 #[cfg(feature = "gpu")]
1400 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1401 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1402 ),
1403 #[allow(unreachable_patterns)]
1404 _ => false,
1405 }
1406}
1407
1408pub fn fused_dit_block_available() -> bool {
1412 #[cfg(target_os = "macos")]
1413 {
1414 matches!(backend(), Backend::Metal) && fused_block_trusted()
1415 }
1416 #[cfg(not(target_os = "macos"))]
1417 {
1418 false
1419 }
1420}
1421
1422pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1423 dit_block_seg(model, a, &[a.n], x)
1424}
1425
1426pub fn dit_block_seg(
1430 model: &Arc<CmfModel>,
1431 a: &DitBlockArgs,
1432 segs: &[usize],
1433 x: &mut [f32],
1434) -> bool {
1435 match backend() {
1436 #[cfg(target_os = "macos")]
1437 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1438 #[cfg(feature = "gpu")]
1445 Backend::Wgpu
1446 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1447 Some("0") => false,
1448 Some(_) => true,
1449 None => crate::gpu_wgpu::discrete_active(),
1450 } =>
1451 {
1452 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1453 }
1454 #[allow(unreachable_patterns)]
1455 _ => false,
1456 }
1457}
1458
1459pub struct VaeResnetArgs<'a> {
1463 pub groups: usize,
1464 pub ic: usize,
1465 pub oc: usize,
1466 pub h: usize,
1467 pub w: usize,
1468 pub n1w: &'a [f32],
1469 pub n1b: &'a [f32],
1470 pub c1w: &'a [f32],
1471 pub c1b: &'a [f32],
1472 pub c1k: usize,
1473 pub n2w: &'a [f32],
1474 pub n2b: &'a [f32],
1475 pub c2w: &'a [f32],
1476 pub c2b: &'a [f32],
1477 pub c2k: usize,
1478 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1479}
1480
1481#[allow(unused_variables)]
1484pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1485 match backend() {
1486 #[cfg(target_os = "macos")]
1487 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1488 _ => false,
1489 }
1490}
1491
1492#[allow(unused_variables, clippy::too_many_arguments)]
1495pub fn vae_upsample_conv(
1496 w: &[f32],
1497 bias: &[f32],
1498 x: &[f32],
1499 ic: usize,
1500 oc: usize,
1501 h: usize,
1502 w_img: usize,
1503 k: usize,
1504 out: &mut [f32],
1505) -> bool {
1506 match backend() {
1507 #[cfg(target_os = "macos")]
1508 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1509 #[cfg(feature = "gpu")]
1510 Backend::Wgpu => {
1511 crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out)
1512 }
1513 #[allow(unreachable_patterns)]
1514 _ => false,
1515 }
1516}
1517
1518#[allow(unused_variables, clippy::too_many_arguments)]
1521pub fn vae_conv2d(
1522 w: &[f32],
1523 bias: &[f32],
1524 x: &[f32],
1525 ic: usize,
1526 oc: usize,
1527 h: usize,
1528 w_img: usize,
1529 k: usize,
1530 out: &mut [f32],
1531) -> bool {
1532 match backend() {
1533 #[cfg(target_os = "macos")]
1534 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1535 #[cfg(feature = "gpu")]
1536 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1537 #[allow(unreachable_patterns)]
1538 _ => false,
1539 }
1540}
1541
1542#[allow(unused_variables, clippy::too_many_arguments)]
1546#[allow(unused_variables)]
1550#[allow(clippy::too_many_arguments)]
1551#[allow(clippy::too_many_arguments, unused_variables)]
1554pub fn dit_qkv_attention(
1555 model: &Arc<CmfModel>,
1556 qkv_idx: usize,
1557 xn: &[f32],
1558 n: usize,
1559 hidden: usize,
1560 nh: usize,
1561 hd: usize,
1562 scale: f32,
1563 nr: (&[f32], &[f32], &[f32], f32),
1564 out: &mut [f32],
1565) -> bool {
1566 match backend() {
1567 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1568 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1569 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1570 ),
1571 #[allow(unreachable_patterns)]
1572 _ => false,
1573 }
1574}
1575
1576#[allow(clippy::too_many_arguments)]
1579pub fn dit_qkv_attn_out(
1580 model: &Arc<CmfModel>,
1581 qkv_idx: usize,
1582 out_idx: usize,
1583 xn: &[f32],
1584 n: usize,
1585 hidden: usize,
1586 nh: usize,
1587 hd: usize,
1588 scale: f32,
1589 nr: (&[f32], &[f32], &[f32], f32),
1590 proj: &mut [f32],
1591) -> bool {
1592 match backend() {
1593 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1594 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1595 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1596 ),
1597 #[allow(unreachable_patterns)]
1598 _ => false,
1599 }
1600}
1601
1602#[allow(clippy::too_many_arguments)]
1604pub fn vae_qkv_attn_out(
1605 model: &Arc<CmfModel>,
1606 qkv_idx: usize,
1607 out_idx: usize,
1608 xn: &[f32],
1609 n: usize,
1610 dim: usize,
1611 nh: usize,
1612 hd: usize,
1613 scale: f32,
1614 angles: &[f32],
1615 eps: f32,
1616 qkv_bias: &[f32],
1617 proj: &mut [f32],
1618) -> bool {
1619 match backend() {
1620 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1621 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1622 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1623 ),
1624 #[allow(unreachable_patterns)]
1625 _ => false,
1626 }
1627}
1628
1629#[allow(clippy::too_many_arguments)]
1630pub fn vae_attention_packed(
1631 qkv: &[f32],
1632 nh: usize,
1633 n: usize,
1634 hd: usize,
1635 scale: f32,
1636 angles: &[f32],
1637 eps: f32,
1638 out: &mut [f32],
1639) -> bool {
1640 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
1641}
1642
1643#[allow(clippy::too_many_arguments)]
1644pub fn vae_attention_packed_layout(
1645 qkv: &[f32],
1646 nh: usize,
1647 n: usize,
1648 hd: usize,
1649 scale: f32,
1650 angles: &[f32],
1651 eps: f32,
1652 out: &mut [f32],
1653 layout: u32,
1654) -> bool {
1655 match backend() {
1656 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1657 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
1658 qkv, nh, n, hd, scale, angles, eps, out, layout,
1659 ),
1660 #[allow(unreachable_patterns)]
1661 _ => false,
1662 }
1663}
1664
1665#[allow(clippy::too_many_arguments)]
1666pub fn dit_split_only(
1667 qkv: &[f32],
1668 nh: usize,
1669 n: usize,
1670 hd: usize,
1671 layout: u32,
1672 norm: Option<(&[f32], f32)>,
1673 out_q: &mut [f32],
1674) -> bool {
1675 match backend() {
1676 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1677 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
1678 #[allow(unreachable_patterns)]
1679 _ => false,
1680 }
1681}
1682
1683pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
1688 match backend() {
1689 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1690 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
1691 #[allow(unreachable_patterns)]
1692 _ => false,
1693 }
1694}
1695
1696#[allow(clippy::too_many_arguments)]
1698pub fn vae_conv2d_coop(
1699 w: &[f32],
1700 bias: Option<&[f32]>,
1701 x: &[f32],
1702 ic: usize,
1703 oc: usize,
1704 h: usize,
1705 wi: usize,
1706 k: usize,
1707 out: &mut [f32],
1708) -> bool {
1709 match backend() {
1710 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1711 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
1712 #[allow(unreachable_patterns)]
1713 _ => false,
1714 }
1715}
1716
1717pub fn dit_attention_packed(
1718 qkv: &[f32],
1719 nh: usize,
1720 n: usize,
1721 hd: usize,
1722 scale: f32,
1723 nr: Option<(&[f32], &[f32], &[f32], f32)>,
1726 out: &mut [f32],
1727) -> bool {
1728 match backend() {
1729 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1730 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
1731 #[allow(unreachable_patterns)]
1732 _ => false,
1733 }
1734}
1735
1736pub fn dit_attention(
1737 qh: &[f32],
1738 kh: &[f32],
1739 vh: &[f32],
1740 nh: usize,
1741 nkv: usize,
1742 n: usize,
1743 hd: usize,
1744 scale: f32,
1745 out: &mut [f32],
1746) -> bool {
1747 match backend() {
1748 #[cfg(target_os = "macos")]
1749 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1750 #[cfg(feature = "gpu")]
1751 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1752 #[allow(unreachable_patterns)]
1753 _ => false,
1754 }
1755}
1756
1757#[allow(unused_variables)]
1762pub fn q4tp_matmat(
1763 model: &Arc<CmfModel>,
1764 idx: usize,
1765 xs: &[f32],
1766 b: usize,
1767 rows: usize,
1768 cols: usize,
1769 out: &mut [f32],
1770) -> bool {
1771 match backend() {
1772 #[cfg(target_os = "macos")]
1773 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1774 #[cfg(feature = "gpu")]
1775 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1776 #[allow(unreachable_patterns)]
1777 _ => false,
1778 }
1779}
1780
1781pub fn q2tp_matmat(
1784 model: &Arc<CmfModel>,
1785 idx: usize,
1786 xs: &[f32],
1787 b: usize,
1788 rows: usize,
1789 cols: usize,
1790 out: &mut [f32],
1791) -> bool {
1792 match backend() {
1793 #[cfg(feature = "gpu")]
1794 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
1795 #[allow(unreachable_patterns)]
1796 _ => false,
1797 }
1798}
1799
1800pub fn q4tp_matvec(
1805 model: &Arc<CmfModel>,
1806 idx: usize,
1807 xs: &[f32],
1808 rows: usize,
1809 cols: usize,
1810 out: &mut [f32],
1811) -> bool {
1812 match backend() {
1813 #[cfg(target_os = "macos")]
1814 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
1815 #[cfg(feature = "gpu")]
1816 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
1817 #[allow(unreachable_patterns)]
1818 _ => false,
1819 }
1820}
1821
1822pub fn q4t_matvec(
1828 model: &Arc<CmfModel>,
1829 idx: usize,
1830 xs: &[f32],
1831 rows: usize,
1832 cols: usize,
1833 out: &mut [f32],
1834) -> bool {
1835 match backend() {
1836 #[cfg(target_os = "macos")]
1837 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
1838 #[allow(unreachable_patterns)]
1839 _ => false,
1840 }
1841}
1842
1843pub fn q4t_matmat(
1844 model: &Arc<CmfModel>,
1845 idx: usize,
1846 xs: &[f32],
1847 b: usize,
1848 rows: usize,
1849 cols: usize,
1850 out: &mut [f32],
1851) -> bool {
1852 match backend() {
1853 #[cfg(target_os = "macos")]
1854 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1855 #[cfg(feature = "gpu")]
1856 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1857 #[allow(unreachable_patterns)]
1858 _ => false,
1859 }
1860}
1861
1862#[cfg(target_os = "macos")]
1864pub use crate::gpu_metal::{
1865 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
1866 TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
1867};
1868
1869#[cfg(target_os = "macos")]
1871pub fn gdn_block(
1872 model: &Arc<CmfModel>,
1873 layers: &[GdnGpuLayer],
1874 states: &mut [&mut [f32]],
1875 cfg: &GdnGpuCfg,
1876 h: &mut [f32],
1877) -> bool {
1878 match backend() {
1879 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1880 _ => false,
1881 }
1882}
1883
1884#[allow(unused_variables)]
1886pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1887 match backend() {
1888 #[cfg(target_os = "macos")]
1889 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1890 #[cfg(feature = "gpu")]
1891 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1892 Backend::None => false,
1893 }
1894}
1895
1896#[allow(unused_variables)]
1898pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1899 match backend() {
1900 #[cfg(target_os = "macos")]
1901 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1902 #[cfg(feature = "gpu")]
1903 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1904 Backend::None => false,
1905 }
1906}
1907
1908static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1924static 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)];
1928
1929const GRAPH_RACE_SAMPLES: u32 = 4;
1931
1932pub fn graph_race_begin_generation() {
1935 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1936 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1937 return;
1938 }
1939 let (gn, cn) = (
1940 GRAPH_N[1].load(Ordering::Relaxed),
1941 GRAPH_N[0].load(Ordering::Relaxed),
1942 );
1943 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1944 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1945 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1946 let verdict = if g_avg < c_avg { 1 } else { 2 };
1947 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1948 tracing::info!(
1949 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1950 g_avg as f64 / 1e6,
1951 c_avg as f64 / 1e6,
1952 if verdict == 1 { "graph" } else { "normal path" }
1953 );
1954 return;
1955 }
1956 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1957 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1958}
1959
1960pub fn graph_race_use_graph(trusted: bool) -> bool {
1964 if trusted {
1965 return true;
1966 }
1967 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1968 1 => true,
1969 2 => false,
1970 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1971 }
1972}
1973
1974pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1979 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1980 return false;
1981 }
1982 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1983 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1984 if !first || cn == 0 {
1985 return false;
1986 }
1987 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1988 let ns = dur.as_nanos() as u64;
1989 if ns > 1_000_000_000 && ns > 4 * c_avg {
1990 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1991 tracing::info!(
1992 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1993 ns as f64 / 1e6,
1994 c_avg as f64 / 1e6
1995 );
1996 return true;
1997 }
1998 false
1999}
2000
2001pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2005 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2006 return;
2007 }
2008 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2009 if tok == 0 {
2010 return;
2011 }
2012 let i = used_graph as usize;
2013 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2014 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2015}
2016
2017pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2027 #[inline]
2028 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2029 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2030 for c in chunks.chunks_exact(8) {
2031 h ^= u64::from_le_bytes(c.try_into().unwrap());
2032 h = h.wrapping_mul(0x100_0000_01b3);
2033 }
2034 for &b in tail {
2035 h ^= b as u64;
2036 h = h.wrapping_mul(0x100_0000_01b3);
2037 }
2038 h
2039 }
2040 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2041 if data.len() <= 4096 {
2042 return fnv(h, data);
2043 }
2044 let step = (data.len() - 64) / 63;
2045 for i in 0..64 {
2046 h = fnv(h, &data[i * step..i * step + 64]);
2047 }
2048 h
2049}
2050
2051pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2054 let bytes =
2055 unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2056 fp_bytes(bytes)
2057}
2058
2059#[cfg(test)]
2060mod fp_tests {
2061 use super::fp_bytes;
2062
2063 #[test]
2068 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2069 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2071 let h0 = fp_bytes(&base);
2072 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2073 let mut dense = base.clone();
2076 for b in dense.iter_mut() {
2077 *b = b.wrapping_add(1);
2078 }
2079 assert_ne!(h0, fp_bytes(&dense), "a fully different tensor slipped through");
2080 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2083 let mut small = vec![3u8; 4096];
2086 let hs = fp_bytes(&small);
2087 small[2048] ^= 1;
2088 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2089 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2091 let v = vec![9u8; n];
2092 let _ = fp_bytes(&v); }
2094 }
2095}
2096
2097pub fn bake_release() {
2101 #[cfg(feature = "gpu")]
2102 crate::gpu_wgpu::bake_release();
2103}
2104
2105pub fn bake_precision_strict(on: bool) {
2109 #[cfg(feature = "gpu")]
2110 crate::gpu_wgpu::bake_precision_strict(on);
2111 #[cfg(not(feature = "gpu"))]
2112 let _ = on;
2113}