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}
136
137pub enum ProbeArm {
139 Gpu,
141 CpuTimed,
143 Cpu,
145}
146
147const PROBE_SAMPLES: u32 = 6;
149
150struct Probe {
151 state: AtomicU8,
153 flip: AtomicU32,
154 gpu_ns: AtomicU64,
155 gpu_n: AtomicU32,
156 cpu_ns: AtomicU64,
157 cpu_n: AtomicU32,
158 gpu_min: AtomicU64,
165 cpu_min: AtomicU64,
166}
167
168impl Probe {
169 const fn new() -> Self {
170 Self {
171 state: AtomicU8::new(0),
172 flip: AtomicU32::new(0),
173 gpu_ns: AtomicU64::new(0),
174 gpu_n: AtomicU32::new(0),
175 cpu_ns: AtomicU64::new(0),
176 cpu_n: AtomicU32::new(0),
177 gpu_min: AtomicU64::new(u64::MAX),
178 cpu_min: AtomicU64::new(u64::MAX),
179 }
180 }
181}
182
183static PROBES: [Probe; 5] = [
184 Probe::new(),
185 Probe::new(),
186 Probe::new(),
187 Probe::new(),
188 Probe::new(),
189];
190
191fn probe_on() -> bool {
192 static ON: OnceLock<bool> = OnceLock::new();
193 *ON.get_or_init(|| {
194 std::env::var("CMF_GPU_PROBE")
195 .map(|v| v != "0" && v != "off")
196 .unwrap_or(true)
197 })
198}
199
200pub fn q1_force() -> bool {
205 #[cfg(target_os = "macos")]
206 {
207 backend() == Backend::Metal
208 }
209 #[cfg(not(target_os = "macos"))]
210 {
211 false
212 }
213}
214
215pub fn fused_block_trusted() -> bool {
234 #[cfg(target_os = "macos")]
235 if backend() == Backend::Metal {
236 return true;
237 }
238 wgpu_graph_default()
239}
240
241pub fn probe_arm(c: OpClass) -> ProbeArm {
245 PROBE_COLD.with(|f| f.set(false));
250 if !probe_on() {
251 return ProbeArm::Gpu;
252 }
253 let p = &PROBES[c as usize];
254 match p.state.load(Ordering::Relaxed) {
255 1 => ProbeArm::Gpu,
256 2 => ProbeArm::Cpu,
257 _ => {
258 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
259 ProbeArm::Gpu
260 } else {
261 ProbeArm::CpuTimed
262 }
263 }
264 }
265}
266
267pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
270 let p = &PROBES[c as usize];
271 if p.state.load(Ordering::Relaxed) != 0 {
272 return;
273 }
274 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
275 return; }
277 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
278 if gpu {
279 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
280 p.gpu_n.fetch_add(1, Ordering::Relaxed);
281 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
282 } else {
283 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
284 p.cpu_n.fetch_add(1, Ordering::Relaxed);
285 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
286 }
287 let (gn, cn) = (
288 p.gpu_n.load(Ordering::Relaxed),
289 p.cpu_n.load(Ordering::Relaxed),
290 );
291 if gn >= 2 && cn >= 2 {
292 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
296 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
297 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 3.0 && cp < g * 3.0 {
300 return;
301 }
302 let winner = if g <= cp { 1 } else { 2 };
303 if p.state
304 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
305 .is_ok()
306 {
307 tracing::info!(
308 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
309 ["ffn", "matvec", "matmat", "qkv-batch", "matmat-wide"][c as usize],
310 g / 1e6,
311 cp / 1e6,
312 if winner == 1 { "gpu" } else { "cpu" },
313 );
314 }
315 }
316}
317
318pub fn probe_deciding(c: OpClass) -> bool {
321 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
322}
323
324#[allow(unused_variables)]
334pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
335 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
336 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
337 let resident = match backend() {
338 #[cfg(target_os = "macos")]
339 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
340 #[cfg(feature = "gpu")]
341 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
342 Backend::None => false,
343 };
344 if !resident && may_upload {
345 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
346 }
347 resident
348}
349
350#[cfg(test)]
352pub(crate) fn probe_reset() {
353 for p in &PROBES {
354 p.state.store(0, Ordering::Relaxed);
355 p.flip.store(0, Ordering::Relaxed);
356 p.gpu_ns.store(0, Ordering::Relaxed);
357 p.gpu_n.store(0, Ordering::Relaxed);
358 p.cpu_ns.store(0, Ordering::Relaxed);
359 p.cpu_n.store(0, Ordering::Relaxed);
360 }
361}
362
363#[cfg(test)]
364mod probe_tests {
365 use super::*;
366 use std::time::Duration;
367
368 #[test]
371 fn probe_alternates_discards_cold_and_decides() {
372 probe_reset();
373 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
375 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
376
377 probe_note_cold();
381 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
382 for _ in 0..PROBE_SAMPLES {
383 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
384 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
385 }
386 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
387
388 for _ in 0..PROBE_SAMPLES {
390 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
391 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
392 }
393 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
394
395 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
397 CPU_ONLY.with(|c| assert!(!c.get()));
398 cpu_scope(|| {
399 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
400 CPU_ONLY.with(|c| assert!(c.get()));
401 });
402 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
403 CPU_ONLY.with(|c| assert!(!c.get()));
404 probe_reset();
405 }
406}
407
408pub const GPU_MIN_ROWS: usize = 65_536;
411
412pub fn min_rows() -> usize {
419 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
420 .ok()
421 .and_then(|v| v.parse().ok())
422 {
423 return v;
424 }
425 if discrete() { 4096 } else { GPU_MIN_ROWS }
426}
427
428pub fn discrete() -> bool {
430 match backend() {
431 #[cfg(feature = "gpu")]
432 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
433 #[cfg(target_os = "macos")]
434 Backend::Metal => false, Backend::None => false,
436 }
437}
438
439pub struct MoeJob<'a> {
443 pub gate: (usize, usize, usize, &'a [f32]),
444 pub up: (usize, usize, usize, &'a [f32]),
445 pub down: (usize, usize, usize, &'a [f32]),
446 pub xs_gate: Vec<f32>,
447 pub xs_up: Vec<f32>,
448 pub down_col: &'a [f32],
449 pub w: f32,
450 pub q1: bool,
453 pub q4t: bool,
456 pub q4tp: bool,
460 pub swiglu_limit: f32,
465}
466
467pub struct BatchJob<'a> {
469 pub idx: usize,
470 pub rows: usize,
471 pub cols: usize,
472 pub row_scale: &'a [f32],
473 pub xs: Vec<f32>,
474 pub layout: BatchLayout,
478}
479
480#[derive(Clone, Copy, PartialEq, Eq, Debug)]
483pub enum BatchLayout {
484 Q8,
485 Q1,
486 Q4t,
487 Q4tp,
488}
489
490#[derive(Clone, Copy, PartialEq, Eq)]
491enum Backend {
492 None,
493 #[cfg(target_os = "macos")]
494 Metal,
495 #[cfg(feature = "gpu")]
496 Wgpu,
497}
498
499fn backend() -> Backend {
500 #[cfg(feature = "gpu")]
501 if crate::gpu_wgpu::selected() {
502 return if crate::gpu_wgpu::enabled() {
503 Backend::Wgpu
504 } else {
505 Backend::None
506 };
507 }
508 #[cfg(target_os = "macos")]
509 if crate::gpu_metal::enabled() {
510 return Backend::Metal;
511 }
512 Backend::None
513}
514
515pub fn backend_available() -> bool {
521 #[cfg(target_os = "macos")]
522 {
523 true
525 }
526 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
527 {
528 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
529 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
530 }
531 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
532 {
533 false
534 }
535}
536
537pub fn enabled() -> bool {
538 backend() != Backend::None
539}
540
541pub fn wgpu_active() -> bool {
555 #[cfg(feature = "gpu")]
556 {
557 matches!(backend(), Backend::Wgpu)
558 }
559 #[cfg(not(feature = "gpu"))]
560 {
561 false
562 }
563}
564
565pub fn wgpu_graph_default() -> bool {
566 #[cfg(feature = "gpu")]
567 {
568 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
569 }
570 #[cfg(not(feature = "gpu"))]
571 {
572 false
573 }
574}
575
576#[allow(clippy::too_many_arguments, unused_variables)]
578pub fn q8_matvec_range(
579 model: &Arc<CmfModel>,
580 idx: usize,
581 row0: usize,
582 row_scale: &[f32],
583 xs: &[f32],
584 rows: usize,
585 cols: usize,
586 out: &mut [f32],
587) -> bool {
588 match backend() {
589 #[cfg(target_os = "macos")]
590 Backend::Metal => {
591 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
592 }
593 #[cfg(feature = "gpu")]
594 Backend::Wgpu => {
595 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
596 }
597 Backend::None => false,
598 }
599}
600
601#[allow(clippy::too_many_arguments, unused_variables)]
604pub fn q8_matmat(
605 model: &Arc<CmfModel>,
606 idx: usize,
607 row_scale: &[f32],
608 pre: &[f32],
609 b: usize,
610 rows: usize,
611 cols: usize,
612 out: &mut [f32],
613) -> bool {
614 match backend() {
615 #[cfg(target_os = "macos")]
616 Backend::Metal => {
617 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
618 }
619 #[cfg(feature = "gpu")]
620 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
621 Backend::None => false,
622 }
623}
624
625#[allow(unused_variables)]
628pub fn q1_matvec(
629 model: &Arc<CmfModel>,
630 idx: usize,
631 xs: &[f32],
632 rows: usize,
633 cols: usize,
634 out: &mut [f32],
635) -> bool {
636 match backend() {
637 #[cfg(target_os = "macos")]
638 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
639 #[cfg(feature = "gpu")]
640 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
641 Backend::None => false,
642 }
643}
644
645#[allow(clippy::too_many_arguments)]
649pub fn attn_dropin(
650 model: &Arc<CmfModel>,
651 kv_id: u64,
652 layer: usize,
653 normed: &[f32],
654 wq_idx: usize,
655 wk_idx: usize,
656 wv_idx: usize,
657 wo_idx: usize,
658 q_norm: Option<&[f32]>,
659 k_norm: Option<&[f32]>,
660 invf: &[f32],
661 nh: usize,
662 nkv: usize,
663 hd: usize,
664 rd: usize,
665 hidden: usize,
666 pos: usize,
667 cap: usize,
668 gemma: bool,
669 eps: f32,
670 cpu_k: &[Vec<f32>],
671 cpu_v: &[Vec<f32>],
672 out: &mut [f32],
673) -> bool {
674 match backend() {
675 #[cfg(feature = "gpu")]
676 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
677 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
678 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
679 ),
680 #[allow(unused_variables)]
681 _ => false,
682 }
683}
684
685pub struct GraphW<'a> {
689 pub idx: usize,
690 pub kind: u8,
691 pub row_scale: &'a [f32],
692 pub data: &'a [f32],
693}
694
695pub enum GraphAttn<'a> {
698 Full {
699 wq: GraphW<'a>,
700 wk: GraphW<'a>,
701 wv: GraphW<'a>,
702 wo: GraphW<'a>,
703 q_norm: Option<&'a [f32]>,
704 k_norm: Option<&'a [f32]>,
705 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
707 output_gate: bool,
710 cpu_k: &'a [Vec<f32>],
711 cpu_v: &'a [Vec<f32>],
712 },
713 Gdn {
714 qkv: GraphW<'a>,
715 z: GraphW<'a>,
716 a: GraphW<'a>,
717 b: GraphW<'a>,
718 out: GraphW<'a>,
719 conv1d: &'a [f32],
720 a_log: &'a [f32],
721 dt_bias: &'a [f32],
722 norm: &'a [f32],
723 nv: usize,
724 nk: usize,
725 dk: usize,
726 dv: usize,
727 kk: usize,
728 cpu_state: &'a [f32],
733 },
734}
735
736pub struct GraphLayer<'a> {
738 pub input_norm: &'a [f32],
739 pub attn: GraphAttn<'a>,
740 pub post_norm: &'a [f32],
741 pub ffn: GraphFfn<'a>,
742}
743
744pub enum GraphFfn<'a> {
749 Dense {
750 gate: GraphW<'a>,
751 up: GraphW<'a>,
752 down: GraphW<'a>,
753 },
754 Moe {
755 router: GraphW<'a>,
757 shared_gate: GraphW<'a>,
759 experts: Vec<(usize, usize, usize)>,
763 n_exp: usize,
765 top_k: usize,
766 inter: usize,
767 norm_topk: bool,
768 q4tp: bool,
774 gu_q2: bool,
778 },
779}
780
781#[allow(clippy::too_many_arguments)]
786pub fn forward_token_graph(
787 model: &Arc<CmfModel>,
788 kv_id: u64,
789 layers: &[GraphLayer],
790 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
793 o1_epoch: u64,
794 invf: &[f32],
795 h: &mut [f32],
796 nh: usize,
797 nkv: usize,
798 hd: usize,
799 rd: usize,
800 hidden: usize,
801 inter: usize,
802 position: usize,
803 cap: usize,
804 gemma: bool,
805 eps: f32,
806 lm_head: Option<(&GraphW, usize)>,
807 final_norm: &[f32],
808 logits: &mut Vec<f32>,
809 loop_norm_at: &[usize],
810 steps: usize,
811 embed: Option<(&GraphW, usize, f32)>,
812 ids_out: Option<&mut Vec<u32>>,
813) -> bool {
814 match backend() {
815 #[cfg(feature = "gpu")]
816 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
817 model,
818 kv_id,
819 layers,
820 o1,
821 o1_epoch,
822 invf,
823 h,
824 nh,
825 nkv,
826 hd,
827 rd,
828 hidden,
829 inter,
830 position,
831 cap,
832 gemma,
833 eps,
834 lm_head,
835 final_norm,
836 logits,
837 loop_norm_at,
838 steps,
839 embed,
840 ids_out,
841 ),
842 #[allow(unused_variables)]
843 _ => {
844 let _ = (lm_head, final_norm, logits, loop_norm_at);
845 false
846 }
847 }
848}
849
850#[allow(clippy::too_many_arguments)]
854pub fn forward_batch_graph(
855 model: &Arc<CmfModel>,
856 kv_id: u64,
857 layers: &[GraphLayer],
858 invf: &[f32],
859 h: &mut [f32],
860 nh: usize,
861 nkv: usize,
862 hd: usize,
863 rd: usize,
864 hidden: usize,
865 inter: usize,
866 positions: &[usize],
867 cap: usize,
868 gemma: bool,
869 eps: f32,
870 k: usize,
871) -> bool {
872 match backend() {
873 #[cfg(feature = "gpu")]
874 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
875 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
876 eps, k,
877 ),
878 _ => false,
879 }
880}
881
882pub fn graph_kv_reset(_kv_id: u64) {
884 #[cfg(feature = "gpu")]
885 if backend() == Backend::Wgpu {
886 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
887 }
888}
889
890pub fn q1t_matvec(
894 model: &Arc<CmfModel>,
895 idx: usize,
896 xs: &[f32],
897 rows: usize,
898 cols: usize,
899 out: &mut [f32],
900) -> bool {
901 match backend() {
902 #[cfg(target_os = "macos")]
903 Backend::Metal => {
904 if metal_q1t_enabled() {
905 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
906 } else {
907 false
908 }
909 }
910 #[cfg(feature = "gpu")]
911 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
912 Backend::None => false,
913 }
914}
915
916#[allow(unused_variables)]
919pub fn q4b_matvec(
920 model: &Arc<CmfModel>,
921 idx: usize,
922 xs: &[f32],
923 rows: usize,
924 cols: usize,
925 out: &mut [f32],
926) -> bool {
927 match backend() {
928 #[cfg(target_os = "macos")]
929 Backend::Metal => false,
930 #[cfg(feature = "gpu")]
931 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
932 Backend::None => false,
933 }
934}
935
936pub fn q1t_matmat(
939 model: &Arc<CmfModel>,
940 idx: usize,
941 xs: &[f32],
942 b: usize,
943 rows: usize,
944 cols: usize,
945 out: &mut [f32],
946) -> bool {
947 match backend() {
948 #[cfg(target_os = "macos")]
949 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
953 #[cfg(feature = "gpu")]
954 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
955 Backend::None => false,
956 }
957}
958
959#[cfg(target_os = "macos")]
963pub(crate) fn metal_q1t_enabled() -> bool {
964 std::env::var("CMF_METAL_Q1T")
965 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
966 .unwrap_or(true)
967}
968
969pub fn q1_matmat(
971 model: &Arc<CmfModel>,
972 idx: usize,
973 xs: &[f32],
974 b: usize,
975 rows: usize,
976 cols: usize,
977 out: &mut [f32],
978) -> bool {
979 match backend() {
980 #[cfg(feature = "gpu")]
981 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
982 #[allow(unused_variables)]
983 _ => false,
984 }
985}
986
987static MM_KILL: AtomicBool = AtomicBool::new(false);
992pub(crate) fn mm_killed() -> bool {
993 MM_KILL.load(Ordering::Relaxed)
994}
995pub(crate) fn mm_kill() {
996 MM_KILL.store(true, Ordering::Relaxed);
997}
998
999#[allow(unused_variables, clippy::too_many_arguments)]
1004pub fn chunk_attend(
1005 q: &[f32],
1006 k: &[&[f32]],
1007 v: &[&[f32]],
1008 b: usize,
1009 s0: usize,
1010 nh: usize,
1011 nkv: usize,
1012 hd: usize,
1013 scale: f32,
1014 out: &mut [f32],
1015) -> bool {
1016 match backend() {
1017 #[cfg(feature = "gpu")]
1018 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1019 #[allow(unreachable_patterns)]
1020 _ => false,
1021 }
1022}
1023
1024#[allow(unused_variables, clippy::too_many_arguments)]
1028pub fn q4t_qkv(
1029 model: &Arc<CmfModel>,
1030 wq: usize,
1031 wk: usize,
1032 wv: usize,
1033 xs: &[f32],
1034 b: usize,
1035 cols: usize,
1036 rq: usize,
1037 rk: usize,
1038 rv: usize,
1039 out: &mut [f32],
1040) -> bool {
1041 match backend() {
1042 #[cfg(feature = "gpu")]
1043 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1044 #[allow(unreachable_patterns)]
1045 _ => false,
1046 }
1047}
1048
1049#[allow(unused_variables, clippy::too_many_arguments)]
1051pub fn q4tp_ffn(
1052 model: &Arc<CmfModel>,
1053 w1: usize,
1054 w3: usize,
1055 w2: usize,
1056 xs: &[f32],
1057 b: usize,
1058 hidden: usize,
1059 inter: usize,
1060 out: &mut [f32],
1061) -> bool {
1062 match backend() {
1063 #[cfg(target_os = "macos")]
1064 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1065 #[cfg(feature = "gpu")]
1066 Backend::Wgpu => false,
1069 #[allow(unreachable_patterns)]
1070 _ => false,
1071 }
1072}
1073
1074pub fn q4t_ffn(
1075 model: &Arc<CmfModel>,
1076 w1: usize,
1077 w3: usize,
1078 w2: usize,
1079 xs: &[f32],
1080 b: usize,
1081 hidden: usize,
1082 inter: usize,
1083 out: &mut [f32],
1084) -> bool {
1085 match backend() {
1086 #[cfg(target_os = "macos")]
1087 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1088 #[cfg(feature = "gpu")]
1089 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1090 #[allow(unreachable_patterns)]
1091 _ => false,
1092 }
1093}
1094
1095pub struct DitBlockArgs<'a> {
1100 pub n: usize,
1101 pub hidden: usize,
1102 pub inter: usize,
1103 pub nh: usize,
1104 pub nkv: usize,
1105 pub hd: usize,
1106 pub eps: f32,
1107 pub rope_cos: &'a [f32],
1108 pub rope_sin: &'a [f32],
1109 pub norm1: &'a [f32],
1110 pub norm2: &'a [f32],
1111 pub ffn_norm1: &'a [f32],
1112 pub ffn_norm2: &'a [f32],
1113 pub norm_q: &'a [f32],
1114 pub norm_k: &'a [f32],
1115 pub s_msa: &'a [f32],
1116 pub gate_msa: &'a [f32],
1117 pub s_mlp: &'a [f32],
1118 pub gate_mlp: &'a [f32],
1119 pub wq: usize,
1120 pub wk: usize,
1121 pub wv: usize,
1122 pub wo: usize,
1123 pub w1: usize,
1124 pub w3: usize,
1125 pub w2: usize,
1126}
1127
1128#[allow(unused_variables)]
1132pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1133 match backend() {
1134 #[cfg(target_os = "macos")]
1135 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
1136 _ => false,
1137 }
1138}
1139
1140pub struct VaeResnetArgs<'a> {
1144 pub groups: usize,
1145 pub ic: usize,
1146 pub oc: usize,
1147 pub h: usize,
1148 pub w: usize,
1149 pub n1w: &'a [f32],
1150 pub n1b: &'a [f32],
1151 pub c1w: &'a [f32],
1152 pub c1b: &'a [f32],
1153 pub c1k: usize,
1154 pub n2w: &'a [f32],
1155 pub n2b: &'a [f32],
1156 pub c2w: &'a [f32],
1157 pub c2b: &'a [f32],
1158 pub c2k: usize,
1159 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1160}
1161
1162#[allow(unused_variables)]
1165pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1166 match backend() {
1167 #[cfg(target_os = "macos")]
1168 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1169 _ => false,
1170 }
1171}
1172
1173#[allow(unused_variables, clippy::too_many_arguments)]
1176pub fn vae_upsample_conv(
1177 w: &[f32],
1178 bias: &[f32],
1179 x: &[f32],
1180 ic: usize,
1181 oc: usize,
1182 h: usize,
1183 w_img: usize,
1184 k: usize,
1185 out: &mut [f32],
1186) -> bool {
1187 match backend() {
1188 #[cfg(target_os = "macos")]
1189 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1190 _ => false,
1191 }
1192}
1193
1194#[allow(unused_variables, clippy::too_many_arguments)]
1197pub fn vae_conv2d(
1198 w: &[f32],
1199 bias: &[f32],
1200 x: &[f32],
1201 ic: usize,
1202 oc: usize,
1203 h: usize,
1204 w_img: usize,
1205 k: usize,
1206 out: &mut [f32],
1207) -> bool {
1208 match backend() {
1209 #[cfg(target_os = "macos")]
1210 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1211 _ => false,
1212 }
1213}
1214
1215#[allow(unused_variables, clippy::too_many_arguments)]
1219pub fn dit_attention(
1220 qh: &[f32],
1221 kh: &[f32],
1222 vh: &[f32],
1223 nh: usize,
1224 nkv: usize,
1225 n: usize,
1226 hd: usize,
1227 scale: f32,
1228 out: &mut [f32],
1229) -> bool {
1230 match backend() {
1231 #[cfg(target_os = "macos")]
1232 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1233 #[cfg(feature = "gpu")]
1234 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1235 #[allow(unreachable_patterns)]
1236 _ => false,
1237 }
1238}
1239
1240#[allow(unused_variables)]
1245pub fn q4tp_matmat(
1246 model: &Arc<CmfModel>,
1247 idx: usize,
1248 xs: &[f32],
1249 b: usize,
1250 rows: usize,
1251 cols: usize,
1252 out: &mut [f32],
1253) -> bool {
1254 match backend() {
1255 #[cfg(target_os = "macos")]
1256 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1257 #[cfg(feature = "gpu")]
1258 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1259 #[allow(unreachable_patterns)]
1260 _ => false,
1261 }
1262}
1263
1264pub fn q4t_matmat(
1265 model: &Arc<CmfModel>,
1266 idx: usize,
1267 xs: &[f32],
1268 b: usize,
1269 rows: usize,
1270 cols: usize,
1271 out: &mut [f32],
1272) -> bool {
1273 match backend() {
1274 #[cfg(target_os = "macos")]
1275 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1276 #[cfg(feature = "gpu")]
1277 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1278 #[allow(unreachable_patterns)]
1279 _ => false,
1280 }
1281}
1282
1283#[cfg(target_os = "macos")]
1285pub use crate::gpu_metal::{
1286 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1287 kv_mirror_read_last, kv_mirror_take_imp,
1288};
1289
1290#[cfg(target_os = "macos")]
1292pub fn gdn_block(
1293 model: &Arc<CmfModel>,
1294 layers: &[GdnGpuLayer],
1295 states: &mut [&mut [f32]],
1296 cfg: &GdnGpuCfg,
1297 h: &mut [f32],
1298) -> bool {
1299 match backend() {
1300 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1301 _ => false,
1302 }
1303}
1304
1305#[allow(unused_variables)]
1307pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1308 match backend() {
1309 #[cfg(target_os = "macos")]
1310 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1311 #[cfg(feature = "gpu")]
1312 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1313 Backend::None => false,
1314 }
1315}
1316
1317#[allow(unused_variables)]
1319pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1320 match backend() {
1321 #[cfg(target_os = "macos")]
1322 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1323 #[cfg(feature = "gpu")]
1324 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1325 Backend::None => false,
1326 }
1327}
1328
1329static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1345static 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)];
1349
1350const GRAPH_RACE_SAMPLES: u32 = 4;
1352
1353pub fn graph_race_begin_generation() {
1356 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1357 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1358 return;
1359 }
1360 let (gn, cn) = (
1361 GRAPH_N[1].load(Ordering::Relaxed),
1362 GRAPH_N[0].load(Ordering::Relaxed),
1363 );
1364 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1365 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1366 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1367 let verdict = if g_avg < c_avg { 1 } else { 2 };
1368 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1369 tracing::info!(
1370 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1371 g_avg as f64 / 1e6,
1372 c_avg as f64 / 1e6,
1373 if verdict == 1 { "graph" } else { "normal path" }
1374 );
1375 return;
1376 }
1377 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1378 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1379}
1380
1381pub fn graph_race_use_graph(trusted: bool) -> bool {
1385 if trusted {
1386 return true;
1387 }
1388 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1389 1 => true,
1390 2 => false,
1391 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1392 }
1393}
1394
1395pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1400 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1401 return false;
1402 }
1403 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1404 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1405 if !first || cn == 0 {
1406 return false;
1407 }
1408 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1409 let ns = dur.as_nanos() as u64;
1410 if ns > 1_000_000_000 && ns > 4 * c_avg {
1411 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1412 tracing::info!(
1413 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1414 ns as f64 / 1e6,
1415 c_avg as f64 / 1e6
1416 );
1417 return true;
1418 }
1419 false
1420}
1421
1422pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1426 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1427 return;
1428 }
1429 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1430 if tok == 0 {
1431 return;
1432 }
1433 let i = used_graph as usize;
1434 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1435 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1436}