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}
143
144pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
148 if rows * cols >= 67_108_864 {
149 OpClass::MatvecHead
150 } else {
151 OpClass::Matvec
152 }
153}
154
155pub enum ProbeArm {
157 Gpu,
159 CpuTimed,
161 Cpu,
163}
164
165const PROBE_SAMPLES: u32 = 6;
167
168struct Probe {
169 state: AtomicU8,
171 flip: AtomicU32,
172 gpu_ns: AtomicU64,
173 gpu_n: AtomicU32,
174 cpu_ns: AtomicU64,
175 cpu_n: AtomicU32,
176 gpu_min: AtomicU64,
183 cpu_min: AtomicU64,
184}
185
186impl Probe {
187 const fn new() -> Self {
188 Self {
189 state: AtomicU8::new(0),
190 flip: AtomicU32::new(0),
191 gpu_ns: AtomicU64::new(0),
192 gpu_n: AtomicU32::new(0),
193 cpu_ns: AtomicU64::new(0),
194 cpu_n: AtomicU32::new(0),
195 gpu_min: AtomicU64::new(u64::MAX),
196 cpu_min: AtomicU64::new(u64::MAX),
197 }
198 }
199}
200
201static PROBES: [Probe; 6] = [
202 Probe::new(),
203 Probe::new(),
204 Probe::new(),
205 Probe::new(),
206 Probe::new(),
207 Probe::new(),
208];
209
210fn probe_on() -> bool {
211 static ON: OnceLock<bool> = OnceLock::new();
212 *ON.get_or_init(|| {
213 std::env::var("CMF_GPU_PROBE")
214 .map(|v| v != "0" && v != "off")
215 .unwrap_or(true)
216 })
217}
218
219pub fn q1_force() -> bool {
224 #[cfg(target_os = "macos")]
225 {
226 backend() == Backend::Metal
227 }
228 #[cfg(not(target_os = "macos"))]
229 {
230 false
231 }
232}
233
234pub fn fused_block_trusted() -> bool {
253 #[cfg(target_os = "macos")]
254 if backend() == Backend::Metal {
255 return true;
256 }
257 wgpu_graph_default()
258}
259
260pub fn probe_arm(c: OpClass) -> ProbeArm {
264 PROBE_COLD.with(|f| f.set(false));
269 if !probe_on() {
270 return ProbeArm::Gpu;
271 }
272 let p = &PROBES[c as usize];
273 match p.state.load(Ordering::Relaxed) {
274 1 => ProbeArm::Gpu,
275 2 => ProbeArm::Cpu,
276 _ => {
277 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
278 ProbeArm::Gpu
279 } else {
280 ProbeArm::CpuTimed
281 }
282 }
283 }
284}
285
286pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
289 let p = &PROBES[c as usize];
290 if p.state.load(Ordering::Relaxed) != 0 {
291 return;
292 }
293 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
294 return; }
296 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
297 if gpu {
298 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
299 p.gpu_n.fetch_add(1, Ordering::Relaxed);
300 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
301 } else {
302 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
303 p.cpu_n.fetch_add(1, Ordering::Relaxed);
304 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
305 }
306 let (gn, cn) = (
307 p.gpu_n.load(Ordering::Relaxed),
308 p.cpu_n.load(Ordering::Relaxed),
309 );
310 if gn >= 2 && cn >= 2 {
311 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
315 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
316 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 3.0 && cp < g * 3.0 {
319 return;
320 }
321 let winner = if g <= cp { 1 } else { 2 };
322 if p.state
323 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
324 .is_ok()
325 {
326 tracing::info!(
327 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
328 ["ffn", "matvec", "matmat", "qkv-batch", "matmat-wide", "lm-head"][c as usize],
329 g / 1e6,
330 cp / 1e6,
331 if winner == 1 { "gpu" } else { "cpu" },
332 );
333 }
334 }
335}
336
337pub fn probe_deciding(c: OpClass) -> bool {
340 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
341}
342
343#[allow(unused_variables)]
353pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
354 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
355 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
356 let resident = match backend() {
357 #[cfg(target_os = "macos")]
358 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
359 #[cfg(feature = "gpu")]
360 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
361 Backend::None => false,
362 };
363 if !resident && may_upload {
364 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
365 }
366 resident
367}
368
369#[cfg(test)]
371pub(crate) fn probe_reset() {
372 for p in &PROBES {
373 p.state.store(0, Ordering::Relaxed);
374 p.flip.store(0, Ordering::Relaxed);
375 p.gpu_ns.store(0, Ordering::Relaxed);
376 p.gpu_n.store(0, Ordering::Relaxed);
377 p.cpu_ns.store(0, Ordering::Relaxed);
378 p.cpu_n.store(0, Ordering::Relaxed);
379 }
380}
381
382#[cfg(test)]
383mod probe_tests {
384 use super::*;
385 use std::time::Duration;
386
387 #[test]
390 fn probe_alternates_discards_cold_and_decides() {
391 probe_reset();
392 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
394 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
395
396 probe_note_cold();
400 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
401 for _ in 0..PROBE_SAMPLES {
402 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
403 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
404 }
405 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
406
407 for _ in 0..PROBE_SAMPLES {
409 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
410 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
411 }
412 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
413
414 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
416 CPU_ONLY.with(|c| assert!(!c.get()));
417 cpu_scope(|| {
418 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
419 CPU_ONLY.with(|c| assert!(c.get()));
420 });
421 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
422 CPU_ONLY.with(|c| assert!(!c.get()));
423 probe_reset();
424 }
425}
426
427pub const GPU_MIN_ROWS: usize = 65_536;
430
431pub fn min_rows() -> usize {
438 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
439 .ok()
440 .and_then(|v| v.parse().ok())
441 {
442 return v;
443 }
444 if discrete() { 4096 } else { GPU_MIN_ROWS }
445}
446
447pub fn discrete() -> bool {
449 match backend() {
450 #[cfg(feature = "gpu")]
451 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
452 #[cfg(target_os = "macos")]
453 Backend::Metal => false, Backend::None => false,
455 }
456}
457
458pub struct MoeJob<'a> {
462 pub gate: (usize, usize, usize, &'a [f32]),
463 pub up: (usize, usize, usize, &'a [f32]),
464 pub down: (usize, usize, usize, &'a [f32]),
465 pub xs_gate: Vec<f32>,
466 pub xs_up: Vec<f32>,
467 pub down_col: &'a [f32],
468 pub w: f32,
469 pub q1: bool,
472 pub q4t: bool,
475 pub q4tp: bool,
479 pub swiglu_limit: f32,
484}
485
486pub struct BatchJob<'a> {
488 pub idx: usize,
489 pub rows: usize,
490 pub cols: usize,
491 pub row_scale: &'a [f32],
492 pub xs: Vec<f32>,
493 pub layout: BatchLayout,
497}
498
499#[derive(Clone, Copy, PartialEq, Eq, Debug)]
502pub enum BatchLayout {
503 Q8,
504 Q1,
505 Q4t,
506 Q4tp,
507}
508
509#[derive(Clone, Copy, PartialEq, Eq)]
510enum Backend {
511 None,
512 #[cfg(target_os = "macos")]
513 Metal,
514 #[cfg(feature = "gpu")]
515 Wgpu,
516}
517
518fn backend() -> Backend {
519 #[cfg(feature = "gpu")]
520 if crate::gpu_wgpu::selected() {
521 return if crate::gpu_wgpu::enabled() {
522 Backend::Wgpu
523 } else {
524 Backend::None
525 };
526 }
527 #[cfg(target_os = "macos")]
528 if crate::gpu_metal::enabled() {
529 return Backend::Metal;
530 }
531 Backend::None
532}
533
534pub fn backend_available() -> bool {
540 #[cfg(target_os = "macos")]
541 {
542 true
544 }
545 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
546 {
547 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
548 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
549 }
550 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
551 {
552 false
553 }
554}
555
556pub fn enabled() -> bool {
557 backend() != Backend::None
558}
559
560pub fn wgpu_active() -> bool {
574 #[cfg(feature = "gpu")]
575 {
576 matches!(backend(), Backend::Wgpu)
577 }
578 #[cfg(not(feature = "gpu"))]
579 {
580 false
581 }
582}
583
584pub fn wgpu_graph_default() -> bool {
585 #[cfg(feature = "gpu")]
586 {
587 matches!(backend(), Backend::Wgpu)
593 && (crate::gpu_wgpu::discrete_active()
594 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
595 }
596 #[cfg(not(feature = "gpu"))]
597 {
598 false
599 }
600}
601
602#[allow(clippy::too_many_arguments, unused_variables)]
604pub fn q8_matvec_range(
605 model: &Arc<CmfModel>,
606 idx: usize,
607 row0: usize,
608 row_scale: &[f32],
609 xs: &[f32],
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_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
618 }
619 #[cfg(feature = "gpu")]
620 Backend::Wgpu => {
621 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
622 }
623 Backend::None => false,
624 }
625}
626
627#[allow(clippy::too_many_arguments, unused_variables)]
630pub fn q8_matmat(
631 model: &Arc<CmfModel>,
632 idx: usize,
633 row_scale: &[f32],
634 pre: &[f32],
635 b: usize,
636 rows: usize,
637 cols: usize,
638 out: &mut [f32],
639) -> bool {
640 match backend() {
641 #[cfg(target_os = "macos")]
642 Backend::Metal => {
643 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
644 }
645 #[cfg(feature = "gpu")]
646 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
647 Backend::None => false,
648 }
649}
650
651#[allow(unused_variables)]
654pub fn q1_matvec(
655 model: &Arc<CmfModel>,
656 idx: usize,
657 xs: &[f32],
658 rows: usize,
659 cols: usize,
660 out: &mut [f32],
661) -> bool {
662 match backend() {
663 #[cfg(target_os = "macos")]
664 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
665 #[cfg(feature = "gpu")]
666 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
667 Backend::None => false,
668 }
669}
670
671#[allow(clippy::too_many_arguments)]
675pub fn attn_dropin(
676 model: &Arc<CmfModel>,
677 kv_id: u64,
678 layer: usize,
679 normed: &[f32],
680 wq_idx: usize,
681 wk_idx: usize,
682 wv_idx: usize,
683 wo_idx: usize,
684 q_norm: Option<&[f32]>,
685 k_norm: Option<&[f32]>,
686 invf: &[f32],
687 nh: usize,
688 nkv: usize,
689 hd: usize,
690 rd: usize,
691 hidden: usize,
692 pos: usize,
693 cap: usize,
694 gemma: bool,
695 eps: f32,
696 cpu_k: &[Vec<f32>],
697 cpu_v: &[Vec<f32>],
698 out: &mut [f32],
699) -> bool {
700 match backend() {
701 #[cfg(feature = "gpu")]
702 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
703 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
704 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
705 ),
706 #[allow(unused_variables)]
707 _ => false,
708 }
709}
710
711pub struct GraphW<'a> {
715 pub idx: usize,
716 pub kind: u8,
717 pub row_scale: &'a [f32],
718 pub data: &'a [f32],
719}
720
721pub enum GraphAttn<'a> {
724 Full {
725 wq: GraphW<'a>,
726 wk: GraphW<'a>,
727 wv: GraphW<'a>,
728 wo: GraphW<'a>,
729 q_norm: Option<&'a [f32]>,
730 k_norm: Option<&'a [f32]>,
731 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
733 output_gate: bool,
736 cpu_k: &'a [Vec<f32>],
737 cpu_v: &'a [Vec<f32>],
738 },
739 Gdn {
740 qkv: GraphW<'a>,
741 z: GraphW<'a>,
742 a: GraphW<'a>,
743 b: GraphW<'a>,
744 out: GraphW<'a>,
745 conv1d: &'a [f32],
746 a_log: &'a [f32],
747 dt_bias: &'a [f32],
748 norm: &'a [f32],
749 nv: usize,
750 nk: usize,
751 dk: usize,
752 dv: usize,
753 kk: usize,
754 cpu_state: &'a [f32],
759 },
760}
761
762pub struct GraphLayer<'a> {
764 pub input_norm: &'a [f32],
765 pub attn: GraphAttn<'a>,
766 pub post_norm: &'a [f32],
767 pub ffn: GraphFfn<'a>,
768}
769
770pub enum GraphFfn<'a> {
775 Dense {
776 gate: GraphW<'a>,
777 up: GraphW<'a>,
778 down: GraphW<'a>,
779 },
780 Moe {
781 router: GraphW<'a>,
783 shared_gate: GraphW<'a>,
785 experts: Vec<(usize, usize, usize)>,
789 n_exp: usize,
791 top_k: usize,
792 inter: usize,
793 norm_topk: bool,
794 q4tp: bool,
800 gu_q2: bool,
804 },
805}
806
807#[allow(clippy::too_many_arguments)]
812pub fn forward_token_graph(
813 model: &Arc<CmfModel>,
814 kv_id: u64,
815 layers: &[GraphLayer],
816 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
819 o1_epoch: u64,
820 invf: &[f32],
821 h: &mut [f32],
822 nh: usize,
823 nkv: usize,
824 hd: usize,
825 rd: usize,
826 hidden: usize,
827 inter: usize,
828 position: usize,
829 cap: usize,
830 gemma: bool,
831 eps: f32,
832 lm_head: Option<(&GraphW, usize)>,
833 final_norm: &[f32],
834 logits: &mut Vec<f32>,
835 loop_norm_at: &[usize],
836 steps: usize,
837 embed: Option<(&GraphW, usize, f32)>,
838 ids_out: Option<&mut Vec<u32>>,
839 layers_run: Option<&mut usize>,
842) -> bool {
843 match backend() {
844 #[cfg(feature = "gpu")]
845 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
846 model,
847 kv_id,
848 layers,
849 o1,
850 o1_epoch,
851 invf,
852 h,
853 nh,
854 nkv,
855 hd,
856 rd,
857 hidden,
858 inter,
859 position,
860 cap,
861 gemma,
862 eps,
863 lm_head,
864 final_norm,
865 logits,
866 loop_norm_at,
867 steps,
868 embed,
869 ids_out,
870 layers_run,
871 ),
872 #[allow(unused_variables)]
873 _ => {
874 let _ = (lm_head, final_norm, logits, loop_norm_at, layers_run);
875 false
876 }
877 }
878}
879
880pub struct SpecTail<'a> {
884 pub lm: GraphW<'a>,
885 pub lm_rows: usize,
886 pub final_norm: &'a [f32],
887 pub logits_out: &'a mut Vec<f32>,
888}
889
890#[allow(clippy::too_many_arguments)]
894pub fn forward_batch_graph(
895 model: &Arc<CmfModel>,
896 kv_id: u64,
897 layers: &[GraphLayer],
898 invf: &[f32],
899 h: &mut [f32],
900 nh: usize,
901 nkv: usize,
902 hd: usize,
903 rd: usize,
904 hidden: usize,
905 inter: usize,
906 positions: &[usize],
907 cap: usize,
908 gemma: bool,
909 eps: f32,
910 k: usize,
911 spec: Option<SpecTail<'_>>,
912) -> bool {
913 match backend() {
914 #[cfg(feature = "gpu")]
915 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
916 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
917 eps, k, spec,
918 ),
919 #[allow(unreachable_patterns)]
920 _ => {
921 let _ = spec;
922 false
923 }
924 }
925}
926
927pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
930 #[cfg(feature = "gpu")]
931 if backend() == Backend::Wgpu {
932 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
933 }
934 #[allow(unreachable_code)]
935 {
936 let _ = (kv_id, slot);
937 false
938 }
939}
940
941pub fn graph_kv_reset(_kv_id: u64) {
943 #[cfg(feature = "gpu")]
944 if backend() == Backend::Wgpu {
945 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
946 }
947}
948
949pub fn q1t_matvec(
953 model: &Arc<CmfModel>,
954 idx: usize,
955 xs: &[f32],
956 rows: usize,
957 cols: usize,
958 out: &mut [f32],
959) -> bool {
960 match backend() {
961 #[cfg(target_os = "macos")]
962 Backend::Metal => {
963 if metal_q1t_enabled() {
964 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
965 } else {
966 false
967 }
968 }
969 #[cfg(feature = "gpu")]
970 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
971 Backend::None => false,
972 }
973}
974
975#[allow(unused_variables)]
978pub fn q4b_matvec(
979 model: &Arc<CmfModel>,
980 idx: usize,
981 xs: &[f32],
982 rows: usize,
983 cols: usize,
984 out: &mut [f32],
985) -> bool {
986 match backend() {
987 #[cfg(target_os = "macos")]
988 Backend::Metal => false,
989 #[cfg(feature = "gpu")]
990 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
991 Backend::None => false,
992 }
993}
994
995pub fn q1t_matmat(
998 model: &Arc<CmfModel>,
999 idx: usize,
1000 xs: &[f32],
1001 b: usize,
1002 rows: usize,
1003 cols: usize,
1004 out: &mut [f32],
1005) -> bool {
1006 match backend() {
1007 #[cfg(target_os = "macos")]
1008 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1012 #[cfg(feature = "gpu")]
1013 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1014 Backend::None => false,
1015 }
1016}
1017
1018#[cfg(target_os = "macos")]
1022pub(crate) fn metal_q1t_enabled() -> bool {
1023 std::env::var("CMF_METAL_Q1T")
1024 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1025 .unwrap_or(true)
1026}
1027
1028pub fn q1_matmat(
1030 model: &Arc<CmfModel>,
1031 idx: usize,
1032 xs: &[f32],
1033 b: usize,
1034 rows: usize,
1035 cols: usize,
1036 out: &mut [f32],
1037) -> bool {
1038 match backend() {
1039 #[cfg(feature = "gpu")]
1040 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1041 #[allow(unused_variables)]
1042 _ => false,
1043 }
1044}
1045
1046static MM_KILL: AtomicBool = AtomicBool::new(false);
1051pub(crate) fn mm_killed() -> bool {
1052 MM_KILL.load(Ordering::Relaxed)
1053}
1054pub(crate) fn mm_kill() {
1055 MM_KILL.store(true, Ordering::Relaxed);
1056}
1057
1058#[allow(unused_variables, clippy::too_many_arguments)]
1063pub fn chunk_attend(
1064 q: &[f32],
1065 k: &[&[f32]],
1066 v: &[&[f32]],
1067 b: usize,
1068 s0: usize,
1069 nh: usize,
1070 nkv: usize,
1071 hd: usize,
1072 scale: f32,
1073 out: &mut [f32],
1074) -> bool {
1075 match backend() {
1076 #[cfg(feature = "gpu")]
1077 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1078 #[allow(unreachable_patterns)]
1079 _ => false,
1080 }
1081}
1082
1083#[allow(unused_variables, clippy::too_many_arguments)]
1087pub fn q4t_qkv(
1088 model: &Arc<CmfModel>,
1089 wq: usize,
1090 wk: usize,
1091 wv: usize,
1092 xs: &[f32],
1093 b: usize,
1094 cols: usize,
1095 rq: usize,
1096 rk: usize,
1097 rv: usize,
1098 out: &mut [f32],
1099) -> bool {
1100 match backend() {
1101 #[cfg(feature = "gpu")]
1102 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1103 #[allow(unreachable_patterns)]
1104 _ => false,
1105 }
1106}
1107
1108#[allow(unused_variables, clippy::too_many_arguments)]
1110pub fn q4tp_ffn(
1111 model: &Arc<CmfModel>,
1112 w1: usize,
1113 w3: usize,
1114 w2: usize,
1115 xs: &[f32],
1116 b: usize,
1117 hidden: usize,
1118 inter: usize,
1119 out: &mut [f32],
1120) -> bool {
1121 match backend() {
1122 #[cfg(target_os = "macos")]
1123 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1124 #[cfg(feature = "gpu")]
1125 Backend::Wgpu => false,
1128 #[allow(unreachable_patterns)]
1129 _ => false,
1130 }
1131}
1132
1133pub fn q4t_ffn(
1134 model: &Arc<CmfModel>,
1135 w1: usize,
1136 w3: usize,
1137 w2: usize,
1138 xs: &[f32],
1139 b: usize,
1140 hidden: usize,
1141 inter: usize,
1142 out: &mut [f32],
1143) -> bool {
1144 match backend() {
1145 #[cfg(target_os = "macos")]
1146 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1147 #[cfg(feature = "gpu")]
1148 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1149 #[allow(unreachable_patterns)]
1150 _ => false,
1151 }
1152}
1153
1154pub struct DitBlockArgs<'a> {
1159 pub n: usize,
1160 pub hidden: usize,
1161 pub inter: usize,
1162 pub nh: usize,
1163 pub nkv: usize,
1164 pub hd: usize,
1165 pub eps: f32,
1166 pub rope_cos: &'a [f32],
1167 pub rope_sin: &'a [f32],
1168 pub norm1: &'a [f32],
1169 pub norm2: &'a [f32],
1170 pub ffn_norm1: &'a [f32],
1171 pub ffn_norm2: &'a [f32],
1172 pub norm_q: &'a [f32],
1173 pub norm_k: &'a [f32],
1174 pub s_msa: &'a [f32],
1175 pub gate_msa: &'a [f32],
1176 pub s_mlp: &'a [f32],
1177 pub gate_mlp: &'a [f32],
1178 pub wq: usize,
1179 pub wk: usize,
1180 pub wv: usize,
1181 pub wo: usize,
1182 pub w1: usize,
1183 pub w3: usize,
1184 pub w2: usize,
1185}
1186
1187#[allow(unused_variables)]
1191pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1192 match backend() {
1193 #[cfg(target_os = "macos")]
1194 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
1195 _ => false,
1196 }
1197}
1198
1199pub struct VaeResnetArgs<'a> {
1203 pub groups: usize,
1204 pub ic: usize,
1205 pub oc: usize,
1206 pub h: usize,
1207 pub w: usize,
1208 pub n1w: &'a [f32],
1209 pub n1b: &'a [f32],
1210 pub c1w: &'a [f32],
1211 pub c1b: &'a [f32],
1212 pub c1k: usize,
1213 pub n2w: &'a [f32],
1214 pub n2b: &'a [f32],
1215 pub c2w: &'a [f32],
1216 pub c2b: &'a [f32],
1217 pub c2k: usize,
1218 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1219}
1220
1221#[allow(unused_variables)]
1224pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1225 match backend() {
1226 #[cfg(target_os = "macos")]
1227 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1228 _ => false,
1229 }
1230}
1231
1232#[allow(unused_variables, clippy::too_many_arguments)]
1235pub fn vae_upsample_conv(
1236 w: &[f32],
1237 bias: &[f32],
1238 x: &[f32],
1239 ic: usize,
1240 oc: usize,
1241 h: usize,
1242 w_img: usize,
1243 k: usize,
1244 out: &mut [f32],
1245) -> bool {
1246 match backend() {
1247 #[cfg(target_os = "macos")]
1248 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1249 _ => false,
1250 }
1251}
1252
1253#[allow(unused_variables, clippy::too_many_arguments)]
1256pub fn vae_conv2d(
1257 w: &[f32],
1258 bias: &[f32],
1259 x: &[f32],
1260 ic: usize,
1261 oc: usize,
1262 h: usize,
1263 w_img: usize,
1264 k: usize,
1265 out: &mut [f32],
1266) -> bool {
1267 match backend() {
1268 #[cfg(target_os = "macos")]
1269 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1270 _ => false,
1271 }
1272}
1273
1274#[allow(unused_variables, clippy::too_many_arguments)]
1278pub fn dit_attention(
1279 qh: &[f32],
1280 kh: &[f32],
1281 vh: &[f32],
1282 nh: usize,
1283 nkv: usize,
1284 n: usize,
1285 hd: usize,
1286 scale: f32,
1287 out: &mut [f32],
1288) -> bool {
1289 match backend() {
1290 #[cfg(target_os = "macos")]
1291 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1292 #[cfg(feature = "gpu")]
1293 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1294 #[allow(unreachable_patterns)]
1295 _ => false,
1296 }
1297}
1298
1299#[allow(unused_variables)]
1304pub fn q4tp_matmat(
1305 model: &Arc<CmfModel>,
1306 idx: usize,
1307 xs: &[f32],
1308 b: usize,
1309 rows: usize,
1310 cols: usize,
1311 out: &mut [f32],
1312) -> bool {
1313 match backend() {
1314 #[cfg(target_os = "macos")]
1315 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1316 #[cfg(feature = "gpu")]
1317 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1318 #[allow(unreachable_patterns)]
1319 _ => false,
1320 }
1321}
1322
1323pub fn q4tp_matvec(
1328 model: &Arc<CmfModel>,
1329 idx: usize,
1330 xs: &[f32],
1331 rows: usize,
1332 cols: usize,
1333 out: &mut [f32],
1334) -> bool {
1335 match backend() {
1336 #[cfg(target_os = "macos")]
1337 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
1338 #[cfg(feature = "gpu")]
1339 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
1340 #[allow(unreachable_patterns)]
1341 _ => false,
1342 }
1343}
1344
1345pub fn q4t_matmat(
1346 model: &Arc<CmfModel>,
1347 idx: usize,
1348 xs: &[f32],
1349 b: usize,
1350 rows: usize,
1351 cols: usize,
1352 out: &mut [f32],
1353) -> bool {
1354 match backend() {
1355 #[cfg(target_os = "macos")]
1356 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1357 #[cfg(feature = "gpu")]
1358 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1359 #[allow(unreachable_patterns)]
1360 _ => false,
1361 }
1362}
1363
1364#[cfg(target_os = "macos")]
1366pub use crate::gpu_metal::{
1367 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1368 kv_mirror_read_last, kv_mirror_take_imp,
1369};
1370
1371#[cfg(target_os = "macos")]
1373pub fn gdn_block(
1374 model: &Arc<CmfModel>,
1375 layers: &[GdnGpuLayer],
1376 states: &mut [&mut [f32]],
1377 cfg: &GdnGpuCfg,
1378 h: &mut [f32],
1379) -> bool {
1380 match backend() {
1381 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1382 _ => false,
1383 }
1384}
1385
1386#[allow(unused_variables)]
1388pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1389 match backend() {
1390 #[cfg(target_os = "macos")]
1391 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1392 #[cfg(feature = "gpu")]
1393 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1394 Backend::None => false,
1395 }
1396}
1397
1398#[allow(unused_variables)]
1400pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1401 match backend() {
1402 #[cfg(target_os = "macos")]
1403 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1404 #[cfg(feature = "gpu")]
1405 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1406 Backend::None => false,
1407 }
1408}
1409
1410static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1426static 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)];
1430
1431const GRAPH_RACE_SAMPLES: u32 = 4;
1433
1434pub fn graph_race_begin_generation() {
1437 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1438 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1439 return;
1440 }
1441 let (gn, cn) = (
1442 GRAPH_N[1].load(Ordering::Relaxed),
1443 GRAPH_N[0].load(Ordering::Relaxed),
1444 );
1445 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1446 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1447 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1448 let verdict = if g_avg < c_avg { 1 } else { 2 };
1449 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1450 tracing::info!(
1451 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1452 g_avg as f64 / 1e6,
1453 c_avg as f64 / 1e6,
1454 if verdict == 1 { "graph" } else { "normal path" }
1455 );
1456 return;
1457 }
1458 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1459 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1460}
1461
1462pub fn graph_race_use_graph(trusted: bool) -> bool {
1466 if trusted {
1467 return true;
1468 }
1469 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1470 1 => true,
1471 2 => false,
1472 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1473 }
1474}
1475
1476pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1481 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1482 return false;
1483 }
1484 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1485 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1486 if !first || cn == 0 {
1487 return false;
1488 }
1489 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1490 let ns = dur.as_nanos() as u64;
1491 if ns > 1_000_000_000 && ns > 4 * c_avg {
1492 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1493 tracing::info!(
1494 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1495 ns as f64 / 1e6,
1496 c_avg as f64 / 1e6
1497 );
1498 return true;
1499 }
1500 false
1501}
1502
1503pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1507 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1508 return;
1509 }
1510 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1511 if tok == 0 {
1512 return;
1513 }
1514 let i = used_graph as usize;
1515 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1516 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1517}