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 gu_q2: bool,
773 },
774}
775
776#[allow(clippy::too_many_arguments)]
781pub fn forward_token_graph(
782 model: &Arc<CmfModel>,
783 kv_id: u64,
784 layers: &[GraphLayer],
785 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
788 o1_epoch: u64,
789 invf: &[f32],
790 h: &mut [f32],
791 nh: usize,
792 nkv: usize,
793 hd: usize,
794 rd: usize,
795 hidden: usize,
796 inter: usize,
797 position: usize,
798 cap: usize,
799 gemma: bool,
800 eps: f32,
801 lm_head: Option<(&GraphW, usize)>,
802 final_norm: &[f32],
803 logits: &mut Vec<f32>,
804 loop_norm_at: &[usize],
805 steps: usize,
806 embed: Option<(&GraphW, usize, f32)>,
807 ids_out: Option<&mut Vec<u32>>,
808) -> bool {
809 match backend() {
810 #[cfg(feature = "gpu")]
811 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
812 model,
813 kv_id,
814 layers,
815 o1,
816 o1_epoch,
817 invf,
818 h,
819 nh,
820 nkv,
821 hd,
822 rd,
823 hidden,
824 inter,
825 position,
826 cap,
827 gemma,
828 eps,
829 lm_head,
830 final_norm,
831 logits,
832 loop_norm_at,
833 steps,
834 embed,
835 ids_out,
836 ),
837 #[allow(unused_variables)]
838 _ => {
839 let _ = (lm_head, final_norm, logits, loop_norm_at);
840 false
841 }
842 }
843}
844
845#[allow(clippy::too_many_arguments)]
849pub fn forward_batch_graph(
850 model: &Arc<CmfModel>,
851 kv_id: u64,
852 layers: &[GraphLayer],
853 invf: &[f32],
854 h: &mut [f32],
855 nh: usize,
856 nkv: usize,
857 hd: usize,
858 rd: usize,
859 hidden: usize,
860 inter: usize,
861 positions: &[usize],
862 cap: usize,
863 gemma: bool,
864 eps: f32,
865 k: usize,
866) -> bool {
867 match backend() {
868 #[cfg(feature = "gpu")]
869 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
870 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
871 eps, k,
872 ),
873 _ => false,
874 }
875}
876
877pub fn graph_kv_reset(_kv_id: u64) {
879 #[cfg(feature = "gpu")]
880 if backend() == Backend::Wgpu {
881 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
882 }
883}
884
885pub fn q1t_matvec(
889 model: &Arc<CmfModel>,
890 idx: usize,
891 xs: &[f32],
892 rows: usize,
893 cols: usize,
894 out: &mut [f32],
895) -> bool {
896 match backend() {
897 #[cfg(target_os = "macos")]
898 Backend::Metal => {
899 if metal_q1t_enabled() {
900 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
901 } else {
902 false
903 }
904 }
905 #[cfg(feature = "gpu")]
906 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
907 Backend::None => false,
908 }
909}
910
911#[allow(unused_variables)]
914pub fn q4b_matvec(
915 model: &Arc<CmfModel>,
916 idx: usize,
917 xs: &[f32],
918 rows: usize,
919 cols: usize,
920 out: &mut [f32],
921) -> bool {
922 match backend() {
923 #[cfg(target_os = "macos")]
924 Backend::Metal => false,
925 #[cfg(feature = "gpu")]
926 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
927 Backend::None => false,
928 }
929}
930
931pub fn q1t_matmat(
934 model: &Arc<CmfModel>,
935 idx: usize,
936 xs: &[f32],
937 b: usize,
938 rows: usize,
939 cols: usize,
940 out: &mut [f32],
941) -> bool {
942 match backend() {
943 #[cfg(target_os = "macos")]
944 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
948 #[cfg(feature = "gpu")]
949 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
950 Backend::None => false,
951 }
952}
953
954#[cfg(target_os = "macos")]
958pub(crate) fn metal_q1t_enabled() -> bool {
959 std::env::var("CMF_METAL_Q1T")
960 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
961 .unwrap_or(true)
962}
963
964pub fn q1_matmat(
966 model: &Arc<CmfModel>,
967 idx: usize,
968 xs: &[f32],
969 b: usize,
970 rows: usize,
971 cols: usize,
972 out: &mut [f32],
973) -> bool {
974 match backend() {
975 #[cfg(feature = "gpu")]
976 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
977 #[allow(unused_variables)]
978 _ => false,
979 }
980}
981
982static MM_KILL: AtomicBool = AtomicBool::new(false);
987pub(crate) fn mm_killed() -> bool {
988 MM_KILL.load(Ordering::Relaxed)
989}
990pub(crate) fn mm_kill() {
991 MM_KILL.store(true, Ordering::Relaxed);
992}
993
994#[allow(unused_variables, clippy::too_many_arguments)]
999pub fn chunk_attend(
1000 q: &[f32],
1001 k: &[&[f32]],
1002 v: &[&[f32]],
1003 b: usize,
1004 s0: usize,
1005 nh: usize,
1006 nkv: usize,
1007 hd: usize,
1008 scale: f32,
1009 out: &mut [f32],
1010) -> bool {
1011 match backend() {
1012 #[cfg(feature = "gpu")]
1013 Backend::Wgpu => {
1014 crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out)
1015 }
1016 #[allow(unreachable_patterns)]
1017 _ => false,
1018 }
1019}
1020
1021#[allow(unused_variables, clippy::too_many_arguments)]
1025pub fn q4t_qkv(
1026 model: &Arc<CmfModel>,
1027 wq: usize,
1028 wk: usize,
1029 wv: usize,
1030 xs: &[f32],
1031 b: usize,
1032 cols: usize,
1033 rq: usize,
1034 rk: usize,
1035 rv: usize,
1036 out: &mut [f32],
1037) -> bool {
1038 match backend() {
1039 #[cfg(feature = "gpu")]
1040 Backend::Wgpu => {
1041 crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out)
1042 }
1043 #[allow(unreachable_patterns)]
1044 _ => false,
1045 }
1046}
1047
1048#[allow(unused_variables, clippy::too_many_arguments)]
1050pub fn q4tp_ffn(
1051 model: &Arc<CmfModel>,
1052 w1: usize,
1053 w3: usize,
1054 w2: usize,
1055 xs: &[f32],
1056 b: usize,
1057 hidden: usize,
1058 inter: usize,
1059 out: &mut [f32],
1060) -> bool {
1061 match backend() {
1062 #[cfg(target_os = "macos")]
1063 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1064 #[cfg(feature = "gpu")]
1065 Backend::Wgpu => false,
1068 #[allow(unreachable_patterns)]
1069 _ => false,
1070 }
1071}
1072
1073pub fn q4t_ffn(
1074 model: &Arc<CmfModel>,
1075 w1: usize,
1076 w3: usize,
1077 w2: usize,
1078 xs: &[f32],
1079 b: usize,
1080 hidden: usize,
1081 inter: usize,
1082 out: &mut [f32],
1083) -> bool {
1084 match backend() {
1085 #[cfg(target_os = "macos")]
1086 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1087 #[cfg(feature = "gpu")]
1088 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1089 #[allow(unreachable_patterns)]
1090 _ => false,
1091 }
1092}
1093
1094pub struct DitBlockArgs<'a> {
1099 pub n: usize,
1100 pub hidden: usize,
1101 pub inter: usize,
1102 pub nh: usize,
1103 pub nkv: usize,
1104 pub hd: usize,
1105 pub eps: f32,
1106 pub rope_cos: &'a [f32],
1107 pub rope_sin: &'a [f32],
1108 pub norm1: &'a [f32],
1109 pub norm2: &'a [f32],
1110 pub ffn_norm1: &'a [f32],
1111 pub ffn_norm2: &'a [f32],
1112 pub norm_q: &'a [f32],
1113 pub norm_k: &'a [f32],
1114 pub s_msa: &'a [f32],
1115 pub gate_msa: &'a [f32],
1116 pub s_mlp: &'a [f32],
1117 pub gate_mlp: &'a [f32],
1118 pub wq: usize,
1119 pub wk: usize,
1120 pub wv: usize,
1121 pub wo: usize,
1122 pub w1: usize,
1123 pub w3: usize,
1124 pub w2: usize,
1125}
1126
1127#[allow(unused_variables)]
1131pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1132 match backend() {
1133 #[cfg(target_os = "macos")]
1134 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
1135 _ => false,
1136 }
1137}
1138
1139pub struct VaeResnetArgs<'a> {
1143 pub groups: usize,
1144 pub ic: usize,
1145 pub oc: usize,
1146 pub h: usize,
1147 pub w: usize,
1148 pub n1w: &'a [f32],
1149 pub n1b: &'a [f32],
1150 pub c1w: &'a [f32],
1151 pub c1b: &'a [f32],
1152 pub c1k: usize,
1153 pub n2w: &'a [f32],
1154 pub n2b: &'a [f32],
1155 pub c2w: &'a [f32],
1156 pub c2b: &'a [f32],
1157 pub c2k: usize,
1158 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1159}
1160
1161#[allow(unused_variables)]
1164pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1165 match backend() {
1166 #[cfg(target_os = "macos")]
1167 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1168 _ => false,
1169 }
1170}
1171
1172#[allow(unused_variables, clippy::too_many_arguments)]
1175pub fn vae_upsample_conv(
1176 w: &[f32],
1177 bias: &[f32],
1178 x: &[f32],
1179 ic: usize,
1180 oc: usize,
1181 h: usize,
1182 w_img: usize,
1183 k: usize,
1184 out: &mut [f32],
1185) -> bool {
1186 match backend() {
1187 #[cfg(target_os = "macos")]
1188 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1189 _ => false,
1190 }
1191}
1192
1193#[allow(unused_variables, clippy::too_many_arguments)]
1196pub fn vae_conv2d(
1197 w: &[f32],
1198 bias: &[f32],
1199 x: &[f32],
1200 ic: usize,
1201 oc: usize,
1202 h: usize,
1203 w_img: usize,
1204 k: usize,
1205 out: &mut [f32],
1206) -> bool {
1207 match backend() {
1208 #[cfg(target_os = "macos")]
1209 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1210 _ => false,
1211 }
1212}
1213
1214#[allow(unused_variables, clippy::too_many_arguments)]
1218pub fn dit_attention(
1219 qh: &[f32],
1220 kh: &[f32],
1221 vh: &[f32],
1222 nh: usize,
1223 nkv: usize,
1224 n: usize,
1225 hd: usize,
1226 scale: f32,
1227 out: &mut [f32],
1228) -> bool {
1229 match backend() {
1230 #[cfg(target_os = "macos")]
1231 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1232 #[cfg(feature = "gpu")]
1233 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1234 #[allow(unreachable_patterns)]
1235 _ => false,
1236 }
1237}
1238
1239#[allow(unused_variables)]
1244pub fn q4tp_matmat(
1245 model: &Arc<CmfModel>,
1246 idx: usize,
1247 xs: &[f32],
1248 b: usize,
1249 rows: usize,
1250 cols: usize,
1251 out: &mut [f32],
1252) -> bool {
1253 match backend() {
1254 #[cfg(target_os = "macos")]
1255 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1256 #[cfg(feature = "gpu")]
1257 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1258 #[allow(unreachable_patterns)]
1259 _ => false,
1260 }
1261}
1262
1263pub fn q4t_matmat(
1264 model: &Arc<CmfModel>,
1265 idx: usize,
1266 xs: &[f32],
1267 b: usize,
1268 rows: usize,
1269 cols: usize,
1270 out: &mut [f32],
1271) -> bool {
1272 match backend() {
1273 #[cfg(target_os = "macos")]
1274 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1275 #[cfg(feature = "gpu")]
1276 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1277 #[allow(unreachable_patterns)]
1278 _ => false,
1279 }
1280}
1281
1282#[cfg(target_os = "macos")]
1284pub use crate::gpu_metal::{
1285 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1286 kv_mirror_read_last, kv_mirror_take_imp,
1287};
1288
1289#[cfg(target_os = "macos")]
1291pub fn gdn_block(
1292 model: &Arc<CmfModel>,
1293 layers: &[GdnGpuLayer],
1294 states: &mut [&mut [f32]],
1295 cfg: &GdnGpuCfg,
1296 h: &mut [f32],
1297) -> bool {
1298 match backend() {
1299 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1300 _ => false,
1301 }
1302}
1303
1304#[allow(unused_variables)]
1306pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1307 match backend() {
1308 #[cfg(target_os = "macos")]
1309 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1310 #[cfg(feature = "gpu")]
1311 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1312 Backend::None => false,
1313 }
1314}
1315
1316#[allow(unused_variables)]
1318pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1319 match backend() {
1320 #[cfg(target_os = "macos")]
1321 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1322 #[cfg(feature = "gpu")]
1323 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1324 Backend::None => false,
1325 }
1326}
1327
1328static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1344static 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)];
1348
1349const GRAPH_RACE_SAMPLES: u32 = 4;
1351
1352pub fn graph_race_begin_generation() {
1355 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1356 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1357 return;
1358 }
1359 let (gn, cn) = (
1360 GRAPH_N[1].load(Ordering::Relaxed),
1361 GRAPH_N[0].load(Ordering::Relaxed),
1362 );
1363 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1364 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1365 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1366 let verdict = if g_avg < c_avg { 1 } else { 2 };
1367 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1368 tracing::info!(
1369 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1370 g_avg as f64 / 1e6,
1371 c_avg as f64 / 1e6,
1372 if verdict == 1 { "graph" } else { "normal path" }
1373 );
1374 return;
1375 }
1376 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1377 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1378}
1379
1380pub fn graph_race_use_graph(trusted: bool) -> bool {
1384 if trusted {
1385 return true;
1386 }
1387 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1388 1 => true,
1389 2 => false,
1390 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1391 }
1392}
1393
1394pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1399 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1400 return false;
1401 }
1402 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1403 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1404 if !first || cn == 0 {
1405 return false;
1406 }
1407 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1408 let ns = dur.as_nanos() as u64;
1409 if ns > 1_000_000_000 && ns > 4 * c_avg {
1410 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1411 tracing::info!(
1412 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1413 ns as f64 / 1e6,
1414 c_avg as f64 / 1e6
1415 );
1416 return true;
1417 }
1418 false
1419}
1420
1421pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1425 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1426 return;
1427 }
1428 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1429 if tok == 0 {
1430 return;
1431 }
1432 let i = used_graph as usize;
1433 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1434 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1435}