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}
461
462pub struct BatchJob<'a> {
464 pub idx: usize,
465 pub rows: usize,
466 pub cols: usize,
467 pub row_scale: &'a [f32],
468 pub xs: Vec<f32>,
469 pub layout: BatchLayout,
473}
474
475#[derive(Clone, Copy, PartialEq, Eq, Debug)]
478pub enum BatchLayout {
479 Q8,
480 Q1,
481 Q4t,
482 Q4tp,
483}
484
485#[derive(Clone, Copy, PartialEq, Eq)]
486enum Backend {
487 None,
488 #[cfg(target_os = "macos")]
489 Metal,
490 #[cfg(feature = "gpu")]
491 Wgpu,
492}
493
494fn backend() -> Backend {
495 #[cfg(feature = "gpu")]
496 if crate::gpu_wgpu::selected() {
497 return if crate::gpu_wgpu::enabled() {
498 Backend::Wgpu
499 } else {
500 Backend::None
501 };
502 }
503 #[cfg(target_os = "macos")]
504 if crate::gpu_metal::enabled() {
505 return Backend::Metal;
506 }
507 Backend::None
508}
509
510pub fn backend_available() -> bool {
516 #[cfg(target_os = "macos")]
517 {
518 true
520 }
521 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
522 {
523 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
524 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
525 }
526 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
527 {
528 false
529 }
530}
531
532pub fn enabled() -> bool {
533 backend() != Backend::None
534}
535
536pub fn wgpu_active() -> bool {
550 #[cfg(feature = "gpu")]
551 {
552 matches!(backend(), Backend::Wgpu)
553 }
554 #[cfg(not(feature = "gpu"))]
555 {
556 false
557 }
558}
559
560pub fn wgpu_graph_default() -> bool {
561 #[cfg(feature = "gpu")]
562 {
563 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
564 }
565 #[cfg(not(feature = "gpu"))]
566 {
567 false
568 }
569}
570
571#[allow(clippy::too_many_arguments, unused_variables)]
573pub fn q8_matvec_range(
574 model: &Arc<CmfModel>,
575 idx: usize,
576 row0: usize,
577 row_scale: &[f32],
578 xs: &[f32],
579 rows: usize,
580 cols: usize,
581 out: &mut [f32],
582) -> bool {
583 match backend() {
584 #[cfg(target_os = "macos")]
585 Backend::Metal => {
586 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
587 }
588 #[cfg(feature = "gpu")]
589 Backend::Wgpu => {
590 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
591 }
592 Backend::None => false,
593 }
594}
595
596#[allow(clippy::too_many_arguments, unused_variables)]
599pub fn q8_matmat(
600 model: &Arc<CmfModel>,
601 idx: usize,
602 row_scale: &[f32],
603 pre: &[f32],
604 b: usize,
605 rows: usize,
606 cols: usize,
607 out: &mut [f32],
608) -> bool {
609 match backend() {
610 #[cfg(target_os = "macos")]
611 Backend::Metal => {
612 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
613 }
614 #[cfg(feature = "gpu")]
615 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
616 Backend::None => false,
617 }
618}
619
620#[allow(unused_variables)]
623pub fn q1_matvec(
624 model: &Arc<CmfModel>,
625 idx: usize,
626 xs: &[f32],
627 rows: usize,
628 cols: usize,
629 out: &mut [f32],
630) -> bool {
631 match backend() {
632 #[cfg(target_os = "macos")]
633 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
634 #[cfg(feature = "gpu")]
635 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
636 Backend::None => false,
637 }
638}
639
640#[allow(clippy::too_many_arguments)]
644pub fn attn_dropin(
645 model: &Arc<CmfModel>,
646 kv_id: u64,
647 layer: usize,
648 normed: &[f32],
649 wq_idx: usize,
650 wk_idx: usize,
651 wv_idx: usize,
652 wo_idx: usize,
653 q_norm: Option<&[f32]>,
654 k_norm: Option<&[f32]>,
655 invf: &[f32],
656 nh: usize,
657 nkv: usize,
658 hd: usize,
659 rd: usize,
660 hidden: usize,
661 pos: usize,
662 cap: usize,
663 gemma: bool,
664 eps: f32,
665 cpu_k: &[Vec<f32>],
666 cpu_v: &[Vec<f32>],
667 out: &mut [f32],
668) -> bool {
669 match backend() {
670 #[cfg(feature = "gpu")]
671 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
672 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
673 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
674 ),
675 #[allow(unused_variables)]
676 _ => false,
677 }
678}
679
680pub struct GraphW<'a> {
684 pub idx: usize,
685 pub kind: u8,
686 pub row_scale: &'a [f32],
687 pub data: &'a [f32],
688}
689
690pub enum GraphAttn<'a> {
693 Full {
694 wq: GraphW<'a>,
695 wk: GraphW<'a>,
696 wv: GraphW<'a>,
697 wo: GraphW<'a>,
698 q_norm: Option<&'a [f32]>,
699 k_norm: Option<&'a [f32]>,
700 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
702 output_gate: bool,
705 cpu_k: &'a [Vec<f32>],
706 cpu_v: &'a [Vec<f32>],
707 },
708 Gdn {
709 qkv: GraphW<'a>,
710 z: GraphW<'a>,
711 a: GraphW<'a>,
712 b: GraphW<'a>,
713 out: GraphW<'a>,
714 conv1d: &'a [f32],
715 a_log: &'a [f32],
716 dt_bias: &'a [f32],
717 norm: &'a [f32],
718 nv: usize,
719 nk: usize,
720 dk: usize,
721 dv: usize,
722 kk: usize,
723 cpu_state: &'a [f32],
728 },
729}
730
731pub struct GraphLayer<'a> {
733 pub input_norm: &'a [f32],
734 pub attn: GraphAttn<'a>,
735 pub post_norm: &'a [f32],
736 pub ffn: GraphFfn<'a>,
737}
738
739pub enum GraphFfn<'a> {
744 Dense {
745 gate: GraphW<'a>,
746 up: GraphW<'a>,
747 down: GraphW<'a>,
748 },
749 Moe {
750 router: GraphW<'a>,
752 shared_gate: GraphW<'a>,
754 experts: Vec<(usize, usize, usize)>,
758 n_exp: usize,
760 top_k: usize,
761 inter: usize,
762 norm_topk: bool,
763 q4tp: bool,
769 },
770}
771
772#[allow(clippy::too_many_arguments)]
777pub fn forward_token_graph(
778 model: &Arc<CmfModel>,
779 kv_id: u64,
780 layers: &[GraphLayer],
781 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
784 o1_epoch: u64,
785 invf: &[f32],
786 h: &mut [f32],
787 nh: usize,
788 nkv: usize,
789 hd: usize,
790 rd: usize,
791 hidden: usize,
792 inter: usize,
793 position: usize,
794 cap: usize,
795 gemma: bool,
796 eps: f32,
797 lm_head: Option<(&GraphW, usize)>,
798 final_norm: &[f32],
799 logits: &mut Vec<f32>,
800 loop_norm_at: &[usize],
801) -> bool {
802 match backend() {
803 #[cfg(feature = "gpu")]
804 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
805 model,
806 kv_id,
807 layers,
808 o1,
809 o1_epoch,
810 invf,
811 h,
812 nh,
813 nkv,
814 hd,
815 rd,
816 hidden,
817 inter,
818 position,
819 cap,
820 gemma,
821 eps,
822 lm_head,
823 final_norm,
824 logits,
825 loop_norm_at,
826 ),
827 #[allow(unused_variables)]
828 _ => {
829 let _ = (lm_head, final_norm, logits, loop_norm_at);
830 false
831 }
832 }
833}
834
835#[allow(clippy::too_many_arguments)]
839pub fn forward_batch_graph(
840 model: &Arc<CmfModel>,
841 kv_id: u64,
842 layers: &[GraphLayer],
843 invf: &[f32],
844 h: &mut [f32],
845 nh: usize,
846 nkv: usize,
847 hd: usize,
848 rd: usize,
849 hidden: usize,
850 inter: usize,
851 positions: &[usize],
852 cap: usize,
853 gemma: bool,
854 eps: f32,
855 k: usize,
856) -> bool {
857 match backend() {
858 #[cfg(feature = "gpu")]
859 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
860 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
861 eps, k,
862 ),
863 _ => false,
864 }
865}
866
867pub fn graph_kv_reset(_kv_id: u64) {
869 #[cfg(feature = "gpu")]
870 if backend() == Backend::Wgpu {
871 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
872 }
873}
874
875pub fn q1t_matvec(
879 model: &Arc<CmfModel>,
880 idx: usize,
881 xs: &[f32],
882 rows: usize,
883 cols: usize,
884 out: &mut [f32],
885) -> bool {
886 match backend() {
887 #[cfg(target_os = "macos")]
888 Backend::Metal => {
889 if metal_q1t_enabled() {
890 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
891 } else {
892 false
893 }
894 }
895 #[cfg(feature = "gpu")]
896 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
897 Backend::None => false,
898 }
899}
900
901#[allow(unused_variables)]
904pub fn q4b_matvec(
905 model: &Arc<CmfModel>,
906 idx: usize,
907 xs: &[f32],
908 rows: usize,
909 cols: usize,
910 out: &mut [f32],
911) -> bool {
912 match backend() {
913 #[cfg(target_os = "macos")]
914 Backend::Metal => false,
915 #[cfg(feature = "gpu")]
916 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
917 Backend::None => false,
918 }
919}
920
921pub fn q1t_matmat(
924 model: &Arc<CmfModel>,
925 idx: usize,
926 xs: &[f32],
927 b: usize,
928 rows: usize,
929 cols: usize,
930 out: &mut [f32],
931) -> bool {
932 match backend() {
933 #[cfg(target_os = "macos")]
934 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
938 #[cfg(feature = "gpu")]
939 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
940 Backend::None => false,
941 }
942}
943
944#[cfg(target_os = "macos")]
948pub(crate) fn metal_q1t_enabled() -> bool {
949 std::env::var("CMF_METAL_Q1T")
950 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
951 .unwrap_or(true)
952}
953
954pub fn q1_matmat(
956 model: &Arc<CmfModel>,
957 idx: usize,
958 xs: &[f32],
959 b: usize,
960 rows: usize,
961 cols: usize,
962 out: &mut [f32],
963) -> bool {
964 match backend() {
965 #[cfg(feature = "gpu")]
966 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
967 #[allow(unused_variables)]
968 _ => false,
969 }
970}
971
972static MM_KILL: AtomicBool = AtomicBool::new(false);
977pub(crate) fn mm_killed() -> bool {
978 MM_KILL.load(Ordering::Relaxed)
979}
980pub(crate) fn mm_kill() {
981 MM_KILL.store(true, Ordering::Relaxed);
982}
983
984#[allow(unused_variables, clippy::too_many_arguments)]
989pub fn chunk_attend(
990 q: &[f32],
991 k: &[&[f32]],
992 v: &[&[f32]],
993 b: usize,
994 s0: usize,
995 nh: usize,
996 nkv: usize,
997 hd: usize,
998 scale: f32,
999 out: &mut [f32],
1000) -> bool {
1001 match backend() {
1002 #[cfg(feature = "gpu")]
1003 Backend::Wgpu => {
1004 crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out)
1005 }
1006 #[allow(unreachable_patterns)]
1007 _ => false,
1008 }
1009}
1010
1011#[allow(unused_variables, clippy::too_many_arguments)]
1015pub fn q4t_qkv(
1016 model: &Arc<CmfModel>,
1017 wq: usize,
1018 wk: usize,
1019 wv: usize,
1020 xs: &[f32],
1021 b: usize,
1022 cols: usize,
1023 rq: usize,
1024 rk: usize,
1025 rv: usize,
1026 out: &mut [f32],
1027) -> bool {
1028 match backend() {
1029 #[cfg(feature = "gpu")]
1030 Backend::Wgpu => {
1031 crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out)
1032 }
1033 #[allow(unreachable_patterns)]
1034 _ => false,
1035 }
1036}
1037
1038#[allow(unused_variables, clippy::too_many_arguments)]
1040pub fn q4tp_ffn(
1041 model: &Arc<CmfModel>,
1042 w1: usize,
1043 w3: usize,
1044 w2: usize,
1045 xs: &[f32],
1046 b: usize,
1047 hidden: usize,
1048 inter: usize,
1049 out: &mut [f32],
1050) -> bool {
1051 match backend() {
1052 #[cfg(target_os = "macos")]
1053 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1054 #[cfg(feature = "gpu")]
1055 Backend::Wgpu => false,
1058 #[allow(unreachable_patterns)]
1059 _ => false,
1060 }
1061}
1062
1063pub fn q4t_ffn(
1064 model: &Arc<CmfModel>,
1065 w1: usize,
1066 w3: usize,
1067 w2: usize,
1068 xs: &[f32],
1069 b: usize,
1070 hidden: usize,
1071 inter: usize,
1072 out: &mut [f32],
1073) -> bool {
1074 match backend() {
1075 #[cfg(target_os = "macos")]
1076 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1077 #[cfg(feature = "gpu")]
1078 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1079 #[allow(unreachable_patterns)]
1080 _ => false,
1081 }
1082}
1083
1084pub struct DitBlockArgs<'a> {
1089 pub n: usize,
1090 pub hidden: usize,
1091 pub inter: usize,
1092 pub nh: usize,
1093 pub nkv: usize,
1094 pub hd: usize,
1095 pub eps: f32,
1096 pub rope_cos: &'a [f32],
1097 pub rope_sin: &'a [f32],
1098 pub norm1: &'a [f32],
1099 pub norm2: &'a [f32],
1100 pub ffn_norm1: &'a [f32],
1101 pub ffn_norm2: &'a [f32],
1102 pub norm_q: &'a [f32],
1103 pub norm_k: &'a [f32],
1104 pub s_msa: &'a [f32],
1105 pub gate_msa: &'a [f32],
1106 pub s_mlp: &'a [f32],
1107 pub gate_mlp: &'a [f32],
1108 pub wq: usize,
1109 pub wk: usize,
1110 pub wv: usize,
1111 pub wo: usize,
1112 pub w1: usize,
1113 pub w3: usize,
1114 pub w2: usize,
1115}
1116
1117#[allow(unused_variables)]
1121pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1122 match backend() {
1123 #[cfg(target_os = "macos")]
1124 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
1125 _ => false,
1126 }
1127}
1128
1129pub struct VaeResnetArgs<'a> {
1133 pub groups: usize,
1134 pub ic: usize,
1135 pub oc: usize,
1136 pub h: usize,
1137 pub w: usize,
1138 pub n1w: &'a [f32],
1139 pub n1b: &'a [f32],
1140 pub c1w: &'a [f32],
1141 pub c1b: &'a [f32],
1142 pub c1k: usize,
1143 pub n2w: &'a [f32],
1144 pub n2b: &'a [f32],
1145 pub c2w: &'a [f32],
1146 pub c2b: &'a [f32],
1147 pub c2k: usize,
1148 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1149}
1150
1151#[allow(unused_variables)]
1154pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1155 match backend() {
1156 #[cfg(target_os = "macos")]
1157 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1158 _ => false,
1159 }
1160}
1161
1162#[allow(unused_variables, clippy::too_many_arguments)]
1165pub fn vae_upsample_conv(
1166 w: &[f32],
1167 bias: &[f32],
1168 x: &[f32],
1169 ic: usize,
1170 oc: usize,
1171 h: usize,
1172 w_img: usize,
1173 k: usize,
1174 out: &mut [f32],
1175) -> bool {
1176 match backend() {
1177 #[cfg(target_os = "macos")]
1178 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1179 _ => false,
1180 }
1181}
1182
1183#[allow(unused_variables, clippy::too_many_arguments)]
1186pub fn vae_conv2d(
1187 w: &[f32],
1188 bias: &[f32],
1189 x: &[f32],
1190 ic: usize,
1191 oc: usize,
1192 h: usize,
1193 w_img: usize,
1194 k: usize,
1195 out: &mut [f32],
1196) -> bool {
1197 match backend() {
1198 #[cfg(target_os = "macos")]
1199 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1200 _ => false,
1201 }
1202}
1203
1204#[allow(unused_variables, clippy::too_many_arguments)]
1208pub fn dit_attention(
1209 qh: &[f32],
1210 kh: &[f32],
1211 vh: &[f32],
1212 nh: usize,
1213 nkv: usize,
1214 n: usize,
1215 hd: usize,
1216 scale: f32,
1217 out: &mut [f32],
1218) -> bool {
1219 match backend() {
1220 #[cfg(target_os = "macos")]
1221 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1222 #[cfg(feature = "gpu")]
1223 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1224 #[allow(unreachable_patterns)]
1225 _ => false,
1226 }
1227}
1228
1229#[allow(unused_variables)]
1234pub fn q4tp_matmat(
1235 model: &Arc<CmfModel>,
1236 idx: usize,
1237 xs: &[f32],
1238 b: usize,
1239 rows: usize,
1240 cols: usize,
1241 out: &mut [f32],
1242) -> bool {
1243 match backend() {
1244 #[cfg(target_os = "macos")]
1245 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1246 #[cfg(feature = "gpu")]
1247 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1248 #[allow(unreachable_patterns)]
1249 _ => false,
1250 }
1251}
1252
1253pub fn q4t_matmat(
1254 model: &Arc<CmfModel>,
1255 idx: usize,
1256 xs: &[f32],
1257 b: usize,
1258 rows: usize,
1259 cols: usize,
1260 out: &mut [f32],
1261) -> bool {
1262 match backend() {
1263 #[cfg(target_os = "macos")]
1264 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1265 #[cfg(feature = "gpu")]
1266 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1267 #[allow(unreachable_patterns)]
1268 _ => false,
1269 }
1270}
1271
1272#[cfg(target_os = "macos")]
1274pub use crate::gpu_metal::{
1275 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1276 kv_mirror_read_last, kv_mirror_take_imp,
1277};
1278
1279#[cfg(target_os = "macos")]
1281pub fn gdn_block(
1282 model: &Arc<CmfModel>,
1283 layers: &[GdnGpuLayer],
1284 states: &mut [&mut [f32]],
1285 cfg: &GdnGpuCfg,
1286 h: &mut [f32],
1287) -> bool {
1288 match backend() {
1289 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1290 _ => false,
1291 }
1292}
1293
1294#[allow(unused_variables)]
1296pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1297 match backend() {
1298 #[cfg(target_os = "macos")]
1299 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1300 #[cfg(feature = "gpu")]
1301 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1302 Backend::None => false,
1303 }
1304}
1305
1306#[allow(unused_variables)]
1308pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1309 match backend() {
1310 #[cfg(target_os = "macos")]
1311 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1312 #[cfg(feature = "gpu")]
1313 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1314 Backend::None => false,
1315 }
1316}
1317
1318static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1334static 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)];
1338
1339const GRAPH_RACE_SAMPLES: u32 = 4;
1341
1342pub fn graph_race_begin_generation() {
1345 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1346 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1347 return;
1348 }
1349 let (gn, cn) = (
1350 GRAPH_N[1].load(Ordering::Relaxed),
1351 GRAPH_N[0].load(Ordering::Relaxed),
1352 );
1353 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1354 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1355 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1356 let verdict = if g_avg < c_avg { 1 } else { 2 };
1357 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1358 tracing::info!(
1359 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1360 g_avg as f64 / 1e6,
1361 c_avg as f64 / 1e6,
1362 if verdict == 1 { "graph" } else { "normal path" }
1363 );
1364 return;
1365 }
1366 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1367 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1368}
1369
1370pub fn graph_race_use_graph(trusted: bool) -> bool {
1374 if trusted {
1375 return true;
1376 }
1377 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1378 1 => true,
1379 2 => false,
1380 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1381 }
1382}
1383
1384pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1389 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1390 return false;
1391 }
1392 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1393 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1394 if !first || cn == 0 {
1395 return false;
1396 }
1397 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1398 let ns = dur.as_nanos() as u64;
1399 if ns > 1_000_000_000 && ns > 4 * c_avg {
1400 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1401 tracing::info!(
1402 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1403 ns as f64 / 1e6,
1404 c_avg as f64 / 1e6
1405 );
1406 return true;
1407 }
1408 false
1409}
1410
1411pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1415 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1416 return;
1417 }
1418 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1419 if tok == 0 {
1420 return;
1421 }
1422 let i = used_graph as usize;
1423 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1424 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1425}