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}
457
458pub struct BatchJob<'a> {
460 pub idx: usize,
461 pub rows: usize,
462 pub cols: usize,
463 pub row_scale: &'a [f32],
464 pub xs: Vec<f32>,
465 pub q1: bool,
467}
468
469#[derive(Clone, Copy, PartialEq, Eq)]
470enum Backend {
471 None,
472 #[cfg(target_os = "macos")]
473 Metal,
474 #[cfg(feature = "gpu")]
475 Wgpu,
476}
477
478fn backend() -> Backend {
479 #[cfg(feature = "gpu")]
480 if crate::gpu_wgpu::selected() {
481 return if crate::gpu_wgpu::enabled() {
482 Backend::Wgpu
483 } else {
484 Backend::None
485 };
486 }
487 #[cfg(target_os = "macos")]
488 if crate::gpu_metal::enabled() {
489 return Backend::Metal;
490 }
491 Backend::None
492}
493
494pub fn backend_available() -> bool {
500 #[cfg(target_os = "macos")]
501 {
502 true
504 }
505 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
506 {
507 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
508 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
509 }
510 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
511 {
512 false
513 }
514}
515
516pub fn enabled() -> bool {
517 backend() != Backend::None
518}
519
520pub fn wgpu_active() -> bool {
534 #[cfg(feature = "gpu")]
535 {
536 matches!(backend(), Backend::Wgpu)
537 }
538 #[cfg(not(feature = "gpu"))]
539 {
540 false
541 }
542}
543
544pub fn wgpu_graph_default() -> bool {
545 #[cfg(feature = "gpu")]
546 {
547 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
548 }
549 #[cfg(not(feature = "gpu"))]
550 {
551 false
552 }
553}
554
555#[allow(clippy::too_many_arguments, unused_variables)]
557pub fn q8_matvec_range(
558 model: &Arc<CmfModel>,
559 idx: usize,
560 row0: usize,
561 row_scale: &[f32],
562 xs: &[f32],
563 rows: usize,
564 cols: usize,
565 out: &mut [f32],
566) -> bool {
567 match backend() {
568 #[cfg(target_os = "macos")]
569 Backend::Metal => {
570 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
571 }
572 #[cfg(feature = "gpu")]
573 Backend::Wgpu => {
574 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
575 }
576 Backend::None => false,
577 }
578}
579
580#[allow(clippy::too_many_arguments, unused_variables)]
583pub fn q8_matmat(
584 model: &Arc<CmfModel>,
585 idx: usize,
586 row_scale: &[f32],
587 pre: &[f32],
588 b: usize,
589 rows: usize,
590 cols: usize,
591 out: &mut [f32],
592) -> bool {
593 match backend() {
594 #[cfg(target_os = "macos")]
595 Backend::Metal => {
596 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
597 }
598 #[cfg(feature = "gpu")]
599 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
600 Backend::None => false,
601 }
602}
603
604#[allow(unused_variables)]
607pub fn q1_matvec(
608 model: &Arc<CmfModel>,
609 idx: usize,
610 xs: &[f32],
611 rows: usize,
612 cols: usize,
613 out: &mut [f32],
614) -> bool {
615 match backend() {
616 #[cfg(target_os = "macos")]
617 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
618 #[cfg(feature = "gpu")]
619 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
620 Backend::None => false,
621 }
622}
623
624#[allow(clippy::too_many_arguments)]
628pub fn attn_dropin(
629 model: &Arc<CmfModel>,
630 kv_id: u64,
631 layer: usize,
632 normed: &[f32],
633 wq_idx: usize,
634 wk_idx: usize,
635 wv_idx: usize,
636 wo_idx: usize,
637 q_norm: Option<&[f32]>,
638 k_norm: Option<&[f32]>,
639 invf: &[f32],
640 nh: usize,
641 nkv: usize,
642 hd: usize,
643 rd: usize,
644 hidden: usize,
645 pos: usize,
646 cap: usize,
647 gemma: bool,
648 eps: f32,
649 cpu_k: &[Vec<f32>],
650 cpu_v: &[Vec<f32>],
651 out: &mut [f32],
652) -> bool {
653 match backend() {
654 #[cfg(feature = "gpu")]
655 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
656 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
657 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
658 ),
659 #[allow(unused_variables)]
660 _ => false,
661 }
662}
663
664pub struct GraphW<'a> {
668 pub idx: usize,
669 pub kind: u8,
670 pub row_scale: &'a [f32],
671 pub data: &'a [f32],
672}
673
674pub enum GraphAttn<'a> {
677 Full {
678 wq: GraphW<'a>,
679 wk: GraphW<'a>,
680 wv: GraphW<'a>,
681 wo: GraphW<'a>,
682 q_norm: Option<&'a [f32]>,
683 k_norm: Option<&'a [f32]>,
684 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
686 output_gate: bool,
689 cpu_k: &'a [Vec<f32>],
690 cpu_v: &'a [Vec<f32>],
691 },
692 Gdn {
693 qkv: GraphW<'a>,
694 z: GraphW<'a>,
695 a: GraphW<'a>,
696 b: GraphW<'a>,
697 out: GraphW<'a>,
698 conv1d: &'a [f32],
699 a_log: &'a [f32],
700 dt_bias: &'a [f32],
701 norm: &'a [f32],
702 nv: usize,
703 nk: usize,
704 dk: usize,
705 dv: usize,
706 kk: usize,
707 },
708}
709
710pub struct GraphLayer<'a> {
712 pub input_norm: &'a [f32],
713 pub attn: GraphAttn<'a>,
714 pub post_norm: &'a [f32],
715 pub ffn: GraphFfn<'a>,
716}
717
718pub enum GraphFfn<'a> {
723 Dense {
724 gate: GraphW<'a>,
725 up: GraphW<'a>,
726 down: GraphW<'a>,
727 },
728 Moe {
729 router: GraphW<'a>,
731 shared_gate: GraphW<'a>,
733 experts: Vec<(usize, usize, usize)>,
737 n_exp: usize,
739 top_k: usize,
740 inter: usize,
741 norm_topk: bool,
742 },
743}
744
745#[allow(clippy::too_many_arguments)]
750pub fn forward_token_graph(
751 model: &Arc<CmfModel>,
752 kv_id: u64,
753 layers: &[GraphLayer],
754 invf: &[f32],
755 h: &mut [f32],
756 nh: usize,
757 nkv: usize,
758 hd: usize,
759 rd: usize,
760 hidden: usize,
761 inter: usize,
762 position: usize,
763 cap: usize,
764 gemma: bool,
765 eps: f32,
766 lm_head: Option<(&GraphW, usize)>,
767 final_norm: &[f32],
768 logits: &mut Vec<f32>,
769 loop_norm_at: &[usize],
770) -> bool {
771 match backend() {
772 #[cfg(feature = "gpu")]
773 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
774 model,
775 kv_id,
776 layers,
777 invf,
778 h,
779 nh,
780 nkv,
781 hd,
782 rd,
783 hidden,
784 inter,
785 position,
786 cap,
787 gemma,
788 eps,
789 lm_head,
790 final_norm,
791 logits,
792 loop_norm_at,
793 ),
794 #[allow(unused_variables)]
795 _ => {
796 let _ = (lm_head, final_norm, logits, loop_norm_at);
797 false
798 }
799 }
800}
801
802#[allow(clippy::too_many_arguments)]
806pub fn forward_batch_graph(
807 model: &Arc<CmfModel>,
808 kv_id: u64,
809 layers: &[GraphLayer],
810 invf: &[f32],
811 h: &mut [f32],
812 nh: usize,
813 nkv: usize,
814 hd: usize,
815 rd: usize,
816 hidden: usize,
817 inter: usize,
818 positions: &[usize],
819 cap: usize,
820 gemma: bool,
821 eps: f32,
822 k: usize,
823) -> bool {
824 match backend() {
825 #[cfg(feature = "gpu")]
826 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
827 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
828 eps, k,
829 ),
830 _ => false,
831 }
832}
833
834pub fn graph_kv_reset(_kv_id: u64) {
836 #[cfg(feature = "gpu")]
837 if backend() == Backend::Wgpu {
838 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
839 }
840}
841
842pub fn q1t_matvec(
846 model: &Arc<CmfModel>,
847 idx: usize,
848 xs: &[f32],
849 rows: usize,
850 cols: usize,
851 out: &mut [f32],
852) -> bool {
853 match backend() {
854 #[cfg(target_os = "macos")]
855 Backend::Metal => {
856 if metal_q1t_enabled() {
857 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
858 } else {
859 false
860 }
861 }
862 #[cfg(feature = "gpu")]
863 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
864 Backend::None => false,
865 }
866}
867
868#[allow(unused_variables)]
871pub fn q4b_matvec(
872 model: &Arc<CmfModel>,
873 idx: usize,
874 xs: &[f32],
875 rows: usize,
876 cols: usize,
877 out: &mut [f32],
878) -> bool {
879 match backend() {
880 #[cfg(target_os = "macos")]
881 Backend::Metal => false,
882 #[cfg(feature = "gpu")]
883 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
884 Backend::None => false,
885 }
886}
887
888pub fn q1t_matmat(
891 model: &Arc<CmfModel>,
892 idx: usize,
893 xs: &[f32],
894 b: usize,
895 rows: usize,
896 cols: usize,
897 out: &mut [f32],
898) -> bool {
899 match backend() {
900 #[cfg(target_os = "macos")]
901 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
905 #[cfg(feature = "gpu")]
906 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
907 Backend::None => false,
908 }
909}
910
911#[cfg(target_os = "macos")]
915pub(crate) fn metal_q1t_enabled() -> bool {
916 std::env::var("CMF_METAL_Q1T")
917 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
918 .unwrap_or(true)
919}
920
921pub fn q1_matmat(
923 model: &Arc<CmfModel>,
924 idx: usize,
925 xs: &[f32],
926 b: usize,
927 rows: usize,
928 cols: usize,
929 out: &mut [f32],
930) -> bool {
931 match backend() {
932 #[cfg(feature = "gpu")]
933 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
934 #[allow(unused_variables)]
935 _ => false,
936 }
937}
938
939static MM_KILL: AtomicBool = AtomicBool::new(false);
944pub(crate) fn mm_killed() -> bool {
945 MM_KILL.load(Ordering::Relaxed)
946}
947pub(crate) fn mm_kill() {
948 MM_KILL.store(true, Ordering::Relaxed);
949}
950
951#[allow(unused_variables, clippy::too_many_arguments)]
956pub fn chunk_attend(
957 q: &[f32],
958 k: &[&[f32]],
959 v: &[&[f32]],
960 b: usize,
961 s0: usize,
962 nh: usize,
963 nkv: usize,
964 hd: usize,
965 scale: f32,
966 out: &mut [f32],
967) -> bool {
968 match backend() {
969 #[cfg(feature = "gpu")]
970 Backend::Wgpu => {
971 crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out)
972 }
973 #[allow(unreachable_patterns)]
974 _ => false,
975 }
976}
977
978#[allow(unused_variables, clippy::too_many_arguments)]
982pub fn q4t_qkv(
983 model: &Arc<CmfModel>,
984 wq: usize,
985 wk: usize,
986 wv: usize,
987 xs: &[f32],
988 b: usize,
989 cols: usize,
990 rq: usize,
991 rk: usize,
992 rv: usize,
993 out: &mut [f32],
994) -> bool {
995 match backend() {
996 #[cfg(feature = "gpu")]
997 Backend::Wgpu => {
998 crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out)
999 }
1000 #[allow(unreachable_patterns)]
1001 _ => false,
1002 }
1003}
1004
1005#[allow(unused_variables, clippy::too_many_arguments)]
1007pub fn q4t_ffn(
1008 model: &Arc<CmfModel>,
1009 w1: usize,
1010 w3: usize,
1011 w2: usize,
1012 xs: &[f32],
1013 b: usize,
1014 hidden: usize,
1015 inter: usize,
1016 out: &mut [f32],
1017) -> bool {
1018 match backend() {
1019 #[cfg(target_os = "macos")]
1020 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1021 #[cfg(feature = "gpu")]
1022 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1023 #[allow(unreachable_patterns)]
1024 _ => false,
1025 }
1026}
1027
1028pub struct DitBlockArgs<'a> {
1033 pub n: usize,
1034 pub hidden: usize,
1035 pub inter: usize,
1036 pub nh: usize,
1037 pub nkv: usize,
1038 pub hd: usize,
1039 pub eps: f32,
1040 pub rope_cos: &'a [f32],
1041 pub rope_sin: &'a [f32],
1042 pub norm1: &'a [f32],
1043 pub norm2: &'a [f32],
1044 pub ffn_norm1: &'a [f32],
1045 pub ffn_norm2: &'a [f32],
1046 pub norm_q: &'a [f32],
1047 pub norm_k: &'a [f32],
1048 pub s_msa: &'a [f32],
1049 pub gate_msa: &'a [f32],
1050 pub s_mlp: &'a [f32],
1051 pub gate_mlp: &'a [f32],
1052 pub wq: usize,
1053 pub wk: usize,
1054 pub wv: usize,
1055 pub wo: usize,
1056 pub w1: usize,
1057 pub w3: usize,
1058 pub w2: usize,
1059}
1060
1061#[allow(unused_variables)]
1065pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1066 match backend() {
1067 #[cfg(target_os = "macos")]
1068 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
1069 _ => false,
1070 }
1071}
1072
1073pub struct VaeResnetArgs<'a> {
1077 pub groups: usize,
1078 pub ic: usize,
1079 pub oc: usize,
1080 pub h: usize,
1081 pub w: usize,
1082 pub n1w: &'a [f32],
1083 pub n1b: &'a [f32],
1084 pub c1w: &'a [f32],
1085 pub c1b: &'a [f32],
1086 pub c1k: usize,
1087 pub n2w: &'a [f32],
1088 pub n2b: &'a [f32],
1089 pub c2w: &'a [f32],
1090 pub c2b: &'a [f32],
1091 pub c2k: usize,
1092 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1093}
1094
1095#[allow(unused_variables)]
1098pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1099 match backend() {
1100 #[cfg(target_os = "macos")]
1101 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1102 _ => false,
1103 }
1104}
1105
1106#[allow(unused_variables, clippy::too_many_arguments)]
1109pub fn vae_upsample_conv(
1110 w: &[f32],
1111 bias: &[f32],
1112 x: &[f32],
1113 ic: usize,
1114 oc: usize,
1115 h: usize,
1116 w_img: usize,
1117 k: usize,
1118 out: &mut [f32],
1119) -> bool {
1120 match backend() {
1121 #[cfg(target_os = "macos")]
1122 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1123 _ => false,
1124 }
1125}
1126
1127#[allow(unused_variables, clippy::too_many_arguments)]
1130pub fn vae_conv2d(
1131 w: &[f32],
1132 bias: &[f32],
1133 x: &[f32],
1134 ic: usize,
1135 oc: usize,
1136 h: usize,
1137 w_img: usize,
1138 k: usize,
1139 out: &mut [f32],
1140) -> bool {
1141 match backend() {
1142 #[cfg(target_os = "macos")]
1143 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1144 _ => false,
1145 }
1146}
1147
1148#[allow(unused_variables, clippy::too_many_arguments)]
1152pub fn dit_attention(
1153 qh: &[f32],
1154 kh: &[f32],
1155 vh: &[f32],
1156 nh: usize,
1157 nkv: usize,
1158 n: usize,
1159 hd: usize,
1160 scale: f32,
1161 out: &mut [f32],
1162) -> bool {
1163 match backend() {
1164 #[cfg(target_os = "macos")]
1165 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1166 #[cfg(feature = "gpu")]
1167 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1168 #[allow(unreachable_patterns)]
1169 _ => false,
1170 }
1171}
1172
1173#[allow(unused_variables)]
1178pub fn q4tp_matmat(
1179 model: &Arc<CmfModel>,
1180 idx: usize,
1181 xs: &[f32],
1182 b: usize,
1183 rows: usize,
1184 cols: usize,
1185 out: &mut [f32],
1186) -> bool {
1187 match backend() {
1188 #[cfg(target_os = "macos")]
1189 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1190 #[cfg(feature = "gpu")]
1191 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
1192 #[allow(unreachable_patterns)]
1193 _ => false,
1194 }
1195}
1196
1197pub fn q4t_matmat(
1198 model: &Arc<CmfModel>,
1199 idx: usize,
1200 xs: &[f32],
1201 b: usize,
1202 rows: usize,
1203 cols: usize,
1204 out: &mut [f32],
1205) -> bool {
1206 match backend() {
1207 #[cfg(target_os = "macos")]
1208 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1209 #[cfg(feature = "gpu")]
1210 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1211 #[allow(unreachable_patterns)]
1212 _ => false,
1213 }
1214}
1215
1216#[cfg(target_os = "macos")]
1218pub use crate::gpu_metal::{
1219 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1220 kv_mirror_read_last, kv_mirror_take_imp,
1221};
1222
1223#[cfg(target_os = "macos")]
1225pub fn gdn_block(
1226 model: &Arc<CmfModel>,
1227 layers: &[GdnGpuLayer],
1228 states: &mut [&mut [f32]],
1229 cfg: &GdnGpuCfg,
1230 h: &mut [f32],
1231) -> bool {
1232 match backend() {
1233 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1234 _ => false,
1235 }
1236}
1237
1238#[allow(unused_variables)]
1240pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1241 match backend() {
1242 #[cfg(target_os = "macos")]
1243 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1244 #[cfg(feature = "gpu")]
1245 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1246 Backend::None => false,
1247 }
1248}
1249
1250#[allow(unused_variables)]
1252pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1253 match backend() {
1254 #[cfg(target_os = "macos")]
1255 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1256 #[cfg(feature = "gpu")]
1257 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1258 Backend::None => false,
1259 }
1260}
1261
1262static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1278static 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)];
1282
1283const GRAPH_RACE_SAMPLES: u32 = 4;
1285
1286pub fn graph_race_begin_generation() {
1289 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1290 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1291 return;
1292 }
1293 let (gn, cn) = (
1294 GRAPH_N[1].load(Ordering::Relaxed),
1295 GRAPH_N[0].load(Ordering::Relaxed),
1296 );
1297 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1298 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1299 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1300 let verdict = if g_avg < c_avg { 1 } else { 2 };
1301 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1302 tracing::info!(
1303 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1304 g_avg as f64 / 1e6,
1305 c_avg as f64 / 1e6,
1306 if verdict == 1 { "graph" } else { "normal path" }
1307 );
1308 return;
1309 }
1310 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1311 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1312}
1313
1314pub fn graph_race_use_graph(trusted: bool) -> bool {
1318 if trusted {
1319 return true;
1320 }
1321 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1322 1 => true,
1323 2 => false,
1324 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1325 }
1326}
1327
1328pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1333 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1334 return false;
1335 }
1336 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1337 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1338 if !first || cn == 0 {
1339 return false;
1340 }
1341 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1342 let ns = dur.as_nanos() as u64;
1343 if ns > 1_000_000_000 && ns > 4 * c_avg {
1344 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1345 tracing::info!(
1346 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1347 ns as f64 / 1e6,
1348 c_avg as f64 / 1e6
1349 );
1350 return true;
1351 }
1352 false
1353}
1354
1355pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1359 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1360 return;
1361 }
1362 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1363 if tok == 0 {
1364 return;
1365 }
1366 let i = used_graph as usize;
1367 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1368 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1369}