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}
159
160impl Probe {
161 const fn new() -> Self {
162 Self {
163 state: AtomicU8::new(0),
164 flip: AtomicU32::new(0),
165 gpu_ns: AtomicU64::new(0),
166 gpu_n: AtomicU32::new(0),
167 cpu_ns: AtomicU64::new(0),
168 cpu_n: AtomicU32::new(0),
169 }
170 }
171}
172
173static PROBES: [Probe; 5] = [
174 Probe::new(),
175 Probe::new(),
176 Probe::new(),
177 Probe::new(),
178 Probe::new(),
179];
180
181fn probe_on() -> bool {
182 static ON: OnceLock<bool> = OnceLock::new();
183 *ON.get_or_init(|| {
184 std::env::var("CMF_GPU_PROBE")
185 .map(|v| v != "0" && v != "off")
186 .unwrap_or(true)
187 })
188}
189
190pub fn q1_force() -> bool {
195 #[cfg(target_os = "macos")]
196 {
197 backend() == Backend::Metal
198 }
199 #[cfg(not(target_os = "macos"))]
200 {
201 false
202 }
203}
204
205pub fn probe_arm(c: OpClass) -> ProbeArm {
209 PROBE_COLD.with(|f| f.set(false));
214 if !probe_on() {
215 return ProbeArm::Gpu;
216 }
217 let p = &PROBES[c as usize];
218 match p.state.load(Ordering::Relaxed) {
219 1 => ProbeArm::Gpu,
220 2 => ProbeArm::Cpu,
221 _ => {
222 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
223 ProbeArm::Gpu
224 } else {
225 ProbeArm::CpuTimed
226 }
227 }
228 }
229}
230
231pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
234 let p = &PROBES[c as usize];
235 if p.state.load(Ordering::Relaxed) != 0 {
236 return;
237 }
238 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
239 return; }
241 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
242 if gpu {
243 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
244 p.gpu_n.fetch_add(1, Ordering::Relaxed);
245 } else {
246 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
247 p.cpu_n.fetch_add(1, Ordering::Relaxed);
248 }
249 let (gn, cn) = (
250 p.gpu_n.load(Ordering::Relaxed),
251 p.cpu_n.load(Ordering::Relaxed),
252 );
253 if gn >= 2 && cn >= 2 {
254 let g = p.gpu_ns.load(Ordering::Relaxed) as f64 / gn as f64;
255 let cp = p.cpu_ns.load(Ordering::Relaxed) as f64 / cn as f64;
256 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 3.0 && cp < g * 3.0 {
259 return;
260 }
261 let winner = if g <= cp { 1 } else { 2 };
262 if p.state
263 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
264 .is_ok()
265 {
266 tracing::info!(
267 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
268 ["ffn", "matvec", "matmat", "qkv-batch", "matmat-wide"][c as usize],
269 g / 1e6,
270 cp / 1e6,
271 if winner == 1 { "gpu" } else { "cpu" },
272 );
273 }
274 }
275}
276
277pub fn probe_deciding(c: OpClass) -> bool {
280 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
281}
282
283#[allow(unused_variables)]
293pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
294 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
295 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
296 let resident = match backend() {
297 #[cfg(target_os = "macos")]
298 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
299 #[cfg(feature = "gpu")]
300 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
301 Backend::None => false,
302 };
303 if !resident && may_upload {
304 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
305 }
306 resident
307}
308
309#[cfg(test)]
311pub(crate) fn probe_reset() {
312 for p in &PROBES {
313 p.state.store(0, Ordering::Relaxed);
314 p.flip.store(0, Ordering::Relaxed);
315 p.gpu_ns.store(0, Ordering::Relaxed);
316 p.gpu_n.store(0, Ordering::Relaxed);
317 p.cpu_ns.store(0, Ordering::Relaxed);
318 p.cpu_n.store(0, Ordering::Relaxed);
319 }
320}
321
322#[cfg(test)]
323mod probe_tests {
324 use super::*;
325 use std::time::Duration;
326
327 #[test]
330 fn probe_alternates_discards_cold_and_decides() {
331 probe_reset();
332 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
334 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
335
336 probe_note_cold();
340 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
341 for _ in 0..PROBE_SAMPLES {
342 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
343 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
344 }
345 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
346
347 for _ in 0..PROBE_SAMPLES {
349 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
350 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
351 }
352 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
353
354 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
356 CPU_ONLY.with(|c| assert!(!c.get()));
357 cpu_scope(|| {
358 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
359 CPU_ONLY.with(|c| assert!(c.get()));
360 });
361 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
362 CPU_ONLY.with(|c| assert!(!c.get()));
363 probe_reset();
364 }
365}
366
367pub const GPU_MIN_ROWS: usize = 65_536;
370
371pub fn min_rows() -> usize {
378 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
379 .ok()
380 .and_then(|v| v.parse().ok())
381 {
382 return v;
383 }
384 if discrete() { 4096 } else { GPU_MIN_ROWS }
385}
386
387pub fn discrete() -> bool {
389 match backend() {
390 #[cfg(feature = "gpu")]
391 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
392 #[cfg(target_os = "macos")]
393 Backend::Metal => false, Backend::None => false,
395 }
396}
397
398pub struct MoeJob<'a> {
402 pub gate: (usize, usize, usize, &'a [f32]),
403 pub up: (usize, usize, usize, &'a [f32]),
404 pub down: (usize, usize, usize, &'a [f32]),
405 pub xs_gate: Vec<f32>,
406 pub xs_up: Vec<f32>,
407 pub down_col: &'a [f32],
408 pub w: f32,
409 pub q1: bool,
412}
413
414pub struct BatchJob<'a> {
416 pub idx: usize,
417 pub rows: usize,
418 pub cols: usize,
419 pub row_scale: &'a [f32],
420 pub xs: Vec<f32>,
421 pub q1: bool,
423}
424
425#[derive(Clone, Copy, PartialEq, Eq)]
426enum Backend {
427 None,
428 #[cfg(target_os = "macos")]
429 Metal,
430 #[cfg(feature = "gpu")]
431 Wgpu,
432}
433
434fn backend() -> Backend {
435 #[cfg(feature = "gpu")]
436 if crate::gpu_wgpu::selected() {
437 return if crate::gpu_wgpu::enabled() {
438 Backend::Wgpu
439 } else {
440 Backend::None
441 };
442 }
443 #[cfg(target_os = "macos")]
444 if crate::gpu_metal::enabled() {
445 return Backend::Metal;
446 }
447 Backend::None
448}
449
450pub fn enabled() -> bool {
452 backend() != Backend::None
453}
454
455pub fn wgpu_active() -> bool {
469 #[cfg(feature = "gpu")]
470 {
471 matches!(backend(), Backend::Wgpu)
472 }
473 #[cfg(not(feature = "gpu"))]
474 {
475 false
476 }
477}
478
479pub fn wgpu_graph_default() -> bool {
480 #[cfg(feature = "gpu")]
481 {
482 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
483 }
484 #[cfg(not(feature = "gpu"))]
485 {
486 false
487 }
488}
489
490#[allow(clippy::too_many_arguments, unused_variables)]
492pub fn q8_matvec_range(
493 model: &Arc<CmfModel>,
494 idx: usize,
495 row0: usize,
496 row_scale: &[f32],
497 xs: &[f32],
498 rows: usize,
499 cols: usize,
500 out: &mut [f32],
501) -> bool {
502 match backend() {
503 #[cfg(target_os = "macos")]
504 Backend::Metal => {
505 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
506 }
507 #[cfg(feature = "gpu")]
508 Backend::Wgpu => {
509 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
510 }
511 Backend::None => false,
512 }
513}
514
515#[allow(clippy::too_many_arguments, unused_variables)]
518pub fn q8_matmat(
519 model: &Arc<CmfModel>,
520 idx: usize,
521 row_scale: &[f32],
522 pre: &[f32],
523 b: usize,
524 rows: usize,
525 cols: usize,
526 out: &mut [f32],
527) -> bool {
528 match backend() {
529 #[cfg(target_os = "macos")]
530 Backend::Metal => {
531 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
532 }
533 #[cfg(feature = "gpu")]
534 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
535 Backend::None => false,
536 }
537}
538
539#[allow(unused_variables)]
542pub fn q1_matvec(
543 model: &Arc<CmfModel>,
544 idx: usize,
545 xs: &[f32],
546 rows: usize,
547 cols: usize,
548 out: &mut [f32],
549) -> bool {
550 match backend() {
551 #[cfg(target_os = "macos")]
552 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
553 #[cfg(feature = "gpu")]
554 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
555 Backend::None => false,
556 }
557}
558
559#[allow(clippy::too_many_arguments)]
563pub fn attn_dropin(
564 model: &Arc<CmfModel>,
565 kv_id: u64,
566 layer: usize,
567 normed: &[f32],
568 wq_idx: usize,
569 wk_idx: usize,
570 wv_idx: usize,
571 wo_idx: usize,
572 q_norm: Option<&[f32]>,
573 k_norm: Option<&[f32]>,
574 invf: &[f32],
575 nh: usize,
576 nkv: usize,
577 hd: usize,
578 rd: usize,
579 hidden: usize,
580 pos: usize,
581 cap: usize,
582 gemma: bool,
583 eps: f32,
584 cpu_k: &[Vec<f32>],
585 cpu_v: &[Vec<f32>],
586 out: &mut [f32],
587) -> bool {
588 match backend() {
589 #[cfg(feature = "gpu")]
590 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
591 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
592 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
593 ),
594 #[allow(unused_variables)]
595 _ => false,
596 }
597}
598
599pub struct GraphW<'a> {
603 pub idx: usize,
604 pub kind: u8,
605 pub row_scale: &'a [f32],
606 pub data: &'a [f32],
607}
608
609pub enum GraphAttn<'a> {
612 Full {
613 wq: GraphW<'a>,
614 wk: GraphW<'a>,
615 wv: GraphW<'a>,
616 wo: GraphW<'a>,
617 q_norm: Option<&'a [f32]>,
618 k_norm: Option<&'a [f32]>,
619 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
621 output_gate: bool,
624 cpu_k: &'a [Vec<f32>],
625 cpu_v: &'a [Vec<f32>],
626 },
627 Gdn {
628 qkv: GraphW<'a>,
629 z: GraphW<'a>,
630 a: GraphW<'a>,
631 b: GraphW<'a>,
632 out: GraphW<'a>,
633 conv1d: &'a [f32],
634 a_log: &'a [f32],
635 dt_bias: &'a [f32],
636 norm: &'a [f32],
637 nv: usize,
638 nk: usize,
639 dk: usize,
640 dv: usize,
641 kk: usize,
642 },
643}
644
645pub struct GraphLayer<'a> {
647 pub input_norm: &'a [f32],
648 pub attn: GraphAttn<'a>,
649 pub post_norm: &'a [f32],
650 pub gate: GraphW<'a>,
651 pub up: GraphW<'a>,
652 pub down: GraphW<'a>,
653}
654
655#[allow(clippy::too_many_arguments)]
660pub fn forward_token_graph(
661 model: &Arc<CmfModel>,
662 kv_id: u64,
663 layers: &[GraphLayer],
664 invf: &[f32],
665 h: &mut [f32],
666 nh: usize,
667 nkv: usize,
668 hd: usize,
669 rd: usize,
670 hidden: usize,
671 inter: usize,
672 position: usize,
673 cap: usize,
674 gemma: bool,
675 eps: f32,
676 lm_head: Option<(&GraphW, usize)>,
677 final_norm: &[f32],
678 logits: &mut Vec<f32>,
679 loop_norm_at: &[usize],
680) -> bool {
681 match backend() {
682 #[cfg(feature = "gpu")]
683 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
684 model,
685 kv_id,
686 layers,
687 invf,
688 h,
689 nh,
690 nkv,
691 hd,
692 rd,
693 hidden,
694 inter,
695 position,
696 cap,
697 gemma,
698 eps,
699 lm_head,
700 final_norm,
701 logits,
702 loop_norm_at,
703 ),
704 #[allow(unused_variables)]
705 _ => {
706 let _ = (lm_head, final_norm, logits, loop_norm_at);
707 false
708 }
709 }
710}
711
712#[allow(clippy::too_many_arguments)]
716pub fn forward_batch_graph(
717 model: &Arc<CmfModel>,
718 kv_id: u64,
719 layers: &[GraphLayer],
720 invf: &[f32],
721 h: &mut [f32],
722 nh: usize,
723 nkv: usize,
724 hd: usize,
725 rd: usize,
726 hidden: usize,
727 inter: usize,
728 positions: &[usize],
729 cap: usize,
730 gemma: bool,
731 eps: f32,
732 k: usize,
733) -> bool {
734 match backend() {
735 #[cfg(feature = "gpu")]
736 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
737 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
738 eps, k,
739 ),
740 _ => false,
741 }
742}
743
744pub fn graph_kv_reset(_kv_id: u64) {
746 #[cfg(feature = "gpu")]
747 if backend() == Backend::Wgpu {
748 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
749 }
750}
751
752pub fn q1t_matvec(
756 model: &Arc<CmfModel>,
757 idx: usize,
758 xs: &[f32],
759 rows: usize,
760 cols: usize,
761 out: &mut [f32],
762) -> bool {
763 match backend() {
764 #[cfg(target_os = "macos")]
765 Backend::Metal => {
766 if metal_q1t_enabled() {
767 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
768 } else {
769 false
770 }
771 }
772 #[cfg(feature = "gpu")]
773 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
774 Backend::None => false,
775 }
776}
777
778#[allow(unused_variables)]
781pub fn q4b_matvec(
782 model: &Arc<CmfModel>,
783 idx: usize,
784 xs: &[f32],
785 rows: usize,
786 cols: usize,
787 out: &mut [f32],
788) -> bool {
789 match backend() {
790 #[cfg(target_os = "macos")]
791 Backend::Metal => false,
792 #[cfg(feature = "gpu")]
793 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
794 Backend::None => false,
795 }
796}
797
798pub fn q1t_matmat(
801 model: &Arc<CmfModel>,
802 idx: usize,
803 xs: &[f32],
804 b: usize,
805 rows: usize,
806 cols: usize,
807 out: &mut [f32],
808) -> bool {
809 match backend() {
810 #[cfg(target_os = "macos")]
811 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
815 #[cfg(feature = "gpu")]
816 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
817 Backend::None => false,
818 }
819}
820
821#[cfg(target_os = "macos")]
825pub(crate) fn metal_q1t_enabled() -> bool {
826 std::env::var("CMF_METAL_Q1T")
827 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
828 .unwrap_or(true)
829}
830
831pub fn q1_matmat(
833 model: &Arc<CmfModel>,
834 idx: usize,
835 xs: &[f32],
836 b: usize,
837 rows: usize,
838 cols: usize,
839 out: &mut [f32],
840) -> bool {
841 match backend() {
842 #[cfg(feature = "gpu")]
843 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
844 #[allow(unused_variables)]
845 _ => false,
846 }
847}
848
849static MM_KILL: AtomicBool = AtomicBool::new(false);
854pub(crate) fn mm_killed() -> bool {
855 MM_KILL.load(Ordering::Relaxed)
856}
857pub(crate) fn mm_kill() {
858 MM_KILL.store(true, Ordering::Relaxed);
859}
860
861#[allow(unused_variables, clippy::too_many_arguments)]
864pub fn q4t_ffn(
865 model: &Arc<CmfModel>,
866 w1: usize,
867 w3: usize,
868 w2: usize,
869 xs: &[f32],
870 b: usize,
871 hidden: usize,
872 inter: usize,
873 out: &mut [f32],
874) -> bool {
875 match backend() {
876 #[cfg(target_os = "macos")]
877 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
878 #[cfg(feature = "gpu")]
879 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
880 #[allow(unreachable_patterns)]
881 _ => false,
882 }
883}
884
885pub struct DitBlockArgs<'a> {
890 pub n: usize,
891 pub hidden: usize,
892 pub inter: usize,
893 pub nh: usize,
894 pub nkv: usize,
895 pub hd: usize,
896 pub eps: f32,
897 pub rope_cos: &'a [f32],
898 pub rope_sin: &'a [f32],
899 pub norm1: &'a [f32],
900 pub norm2: &'a [f32],
901 pub ffn_norm1: &'a [f32],
902 pub ffn_norm2: &'a [f32],
903 pub norm_q: &'a [f32],
904 pub norm_k: &'a [f32],
905 pub s_msa: &'a [f32],
906 pub gate_msa: &'a [f32],
907 pub s_mlp: &'a [f32],
908 pub gate_mlp: &'a [f32],
909 pub wq: usize,
910 pub wk: usize,
911 pub wv: usize,
912 pub wo: usize,
913 pub w1: usize,
914 pub w3: usize,
915 pub w2: usize,
916}
917
918#[allow(unused_variables)]
922pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
923 match backend() {
924 #[cfg(target_os = "macos")]
925 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
926 _ => false,
927 }
928}
929
930pub struct VaeResnetArgs<'a> {
934 pub groups: usize,
935 pub ic: usize,
936 pub oc: usize,
937 pub h: usize,
938 pub w: usize,
939 pub n1w: &'a [f32],
940 pub n1b: &'a [f32],
941 pub c1w: &'a [f32],
942 pub c1b: &'a [f32],
943 pub c1k: usize,
944 pub n2w: &'a [f32],
945 pub n2b: &'a [f32],
946 pub c2w: &'a [f32],
947 pub c2b: &'a [f32],
948 pub c2k: usize,
949 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
950}
951
952#[allow(unused_variables)]
955pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
956 match backend() {
957 #[cfg(target_os = "macos")]
958 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
959 _ => false,
960 }
961}
962
963#[allow(unused_variables, clippy::too_many_arguments)]
966pub fn vae_upsample_conv(
967 w: &[f32],
968 bias: &[f32],
969 x: &[f32],
970 ic: usize,
971 oc: usize,
972 h: usize,
973 w_img: usize,
974 k: usize,
975 out: &mut [f32],
976) -> bool {
977 match backend() {
978 #[cfg(target_os = "macos")]
979 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
980 _ => false,
981 }
982}
983
984#[allow(unused_variables, clippy::too_many_arguments)]
987pub fn vae_conv2d(
988 w: &[f32],
989 bias: &[f32],
990 x: &[f32],
991 ic: usize,
992 oc: usize,
993 h: usize,
994 w_img: usize,
995 k: usize,
996 out: &mut [f32],
997) -> bool {
998 match backend() {
999 #[cfg(target_os = "macos")]
1000 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1001 _ => false,
1002 }
1003}
1004
1005#[allow(unused_variables, clippy::too_many_arguments)]
1009pub fn dit_attention(
1010 qh: &[f32],
1011 kh: &[f32],
1012 vh: &[f32],
1013 nh: usize,
1014 nkv: usize,
1015 n: usize,
1016 hd: usize,
1017 scale: f32,
1018 out: &mut [f32],
1019) -> bool {
1020 match backend() {
1021 #[cfg(target_os = "macos")]
1022 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1023 _ => false,
1024 }
1025}
1026
1027#[allow(unused_variables)]
1032pub fn q4t_matmat(
1033 model: &Arc<CmfModel>,
1034 idx: usize,
1035 xs: &[f32],
1036 b: usize,
1037 rows: usize,
1038 cols: usize,
1039 out: &mut [f32],
1040) -> bool {
1041 match backend() {
1042 #[cfg(target_os = "macos")]
1043 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1044 #[cfg(feature = "gpu")]
1045 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1046 #[allow(unreachable_patterns)]
1047 _ => false,
1048 }
1049}
1050
1051#[cfg(target_os = "macos")]
1053pub use crate::gpu_metal::{
1054 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1055 kv_mirror_read_last, kv_mirror_take_imp,
1056};
1057
1058#[cfg(target_os = "macos")]
1060pub fn gdn_block(
1061 model: &Arc<CmfModel>,
1062 layers: &[GdnGpuLayer],
1063 states: &mut [&mut [f32]],
1064 cfg: &GdnGpuCfg,
1065 h: &mut [f32],
1066) -> bool {
1067 match backend() {
1068 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1069 _ => false,
1070 }
1071}
1072
1073#[allow(unused_variables)]
1075pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1076 match backend() {
1077 #[cfg(target_os = "macos")]
1078 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1079 #[cfg(feature = "gpu")]
1080 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1081 Backend::None => false,
1082 }
1083}
1084
1085#[allow(unused_variables)]
1087pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1088 match backend() {
1089 #[cfg(target_os = "macos")]
1090 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1091 #[cfg(feature = "gpu")]
1092 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1093 Backend::None => false,
1094 }
1095}
1096
1097static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1113static 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)];
1117
1118const GRAPH_RACE_SAMPLES: u32 = 4;
1120
1121pub fn graph_race_begin_generation() {
1124 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1125 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1126 return;
1127 }
1128 let (gn, cn) = (
1129 GRAPH_N[1].load(Ordering::Relaxed),
1130 GRAPH_N[0].load(Ordering::Relaxed),
1131 );
1132 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1133 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1134 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1135 let verdict = if g_avg < c_avg { 1 } else { 2 };
1136 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1137 tracing::info!(
1138 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1139 g_avg as f64 / 1e6,
1140 c_avg as f64 / 1e6,
1141 if verdict == 1 { "graph" } else { "normal path" }
1142 );
1143 return;
1144 }
1145 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1146 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1147}
1148
1149pub fn graph_race_use_graph(trusted: bool) -> bool {
1153 if trusted {
1154 return true;
1155 }
1156 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1157 1 => true,
1158 2 => false,
1159 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1160 }
1161}
1162
1163pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1168 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1169 return false;
1170 }
1171 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1172 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1173 if !first || cn == 0 {
1174 return false;
1175 }
1176 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1177 let ns = dur.as_nanos() as u64;
1178 if ns > 1_000_000_000 && ns > 4 * c_avg {
1179 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1180 tracing::info!(
1181 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1182 ns as f64 / 1e6,
1183 c_avg as f64 / 1e6
1184 );
1185 return true;
1186 }
1187 false
1188}
1189
1190pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1194 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1195 return;
1196 }
1197 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1198 if tok == 0 {
1199 return;
1200 }
1201 let i = used_graph as usize;
1202 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1203 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1204}