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 probe_arm(c: OpClass) -> ProbeArm {
219 PROBE_COLD.with(|f| f.set(false));
224 if !probe_on() {
225 return ProbeArm::Gpu;
226 }
227 let p = &PROBES[c as usize];
228 match p.state.load(Ordering::Relaxed) {
229 1 => ProbeArm::Gpu,
230 2 => ProbeArm::Cpu,
231 _ => {
232 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
233 ProbeArm::Gpu
234 } else {
235 ProbeArm::CpuTimed
236 }
237 }
238 }
239}
240
241pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
244 let p = &PROBES[c as usize];
245 if p.state.load(Ordering::Relaxed) != 0 {
246 return;
247 }
248 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
249 return; }
251 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
252 if gpu {
253 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
254 p.gpu_n.fetch_add(1, Ordering::Relaxed);
255 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
256 } else {
257 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
258 p.cpu_n.fetch_add(1, Ordering::Relaxed);
259 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
260 }
261 let (gn, cn) = (
262 p.gpu_n.load(Ordering::Relaxed),
263 p.cpu_n.load(Ordering::Relaxed),
264 );
265 if gn >= 2 && cn >= 2 {
266 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
270 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
271 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 3.0 && cp < g * 3.0 {
274 return;
275 }
276 let winner = if g <= cp { 1 } else { 2 };
277 if p.state
278 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
279 .is_ok()
280 {
281 tracing::info!(
282 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
283 ["ffn", "matvec", "matmat", "qkv-batch", "matmat-wide"][c as usize],
284 g / 1e6,
285 cp / 1e6,
286 if winner == 1 { "gpu" } else { "cpu" },
287 );
288 }
289 }
290}
291
292pub fn probe_deciding(c: OpClass) -> bool {
295 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
296}
297
298#[allow(unused_variables)]
308pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
309 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
310 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
311 let resident = match backend() {
312 #[cfg(target_os = "macos")]
313 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
314 #[cfg(feature = "gpu")]
315 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
316 Backend::None => false,
317 };
318 if !resident && may_upload {
319 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
320 }
321 resident
322}
323
324#[cfg(test)]
326pub(crate) fn probe_reset() {
327 for p in &PROBES {
328 p.state.store(0, Ordering::Relaxed);
329 p.flip.store(0, Ordering::Relaxed);
330 p.gpu_ns.store(0, Ordering::Relaxed);
331 p.gpu_n.store(0, Ordering::Relaxed);
332 p.cpu_ns.store(0, Ordering::Relaxed);
333 p.cpu_n.store(0, Ordering::Relaxed);
334 }
335}
336
337#[cfg(test)]
338mod probe_tests {
339 use super::*;
340 use std::time::Duration;
341
342 #[test]
345 fn probe_alternates_discards_cold_and_decides() {
346 probe_reset();
347 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
349 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
350
351 probe_note_cold();
355 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
356 for _ in 0..PROBE_SAMPLES {
357 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
358 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
359 }
360 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
361
362 for _ in 0..PROBE_SAMPLES {
364 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
365 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
366 }
367 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
368
369 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
371 CPU_ONLY.with(|c| assert!(!c.get()));
372 cpu_scope(|| {
373 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
374 CPU_ONLY.with(|c| assert!(c.get()));
375 });
376 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
377 CPU_ONLY.with(|c| assert!(!c.get()));
378 probe_reset();
379 }
380}
381
382pub const GPU_MIN_ROWS: usize = 65_536;
385
386pub fn min_rows() -> usize {
393 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
394 .ok()
395 .and_then(|v| v.parse().ok())
396 {
397 return v;
398 }
399 if discrete() { 4096 } else { GPU_MIN_ROWS }
400}
401
402pub fn discrete() -> bool {
404 match backend() {
405 #[cfg(feature = "gpu")]
406 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
407 #[cfg(target_os = "macos")]
408 Backend::Metal => false, Backend::None => false,
410 }
411}
412
413pub struct MoeJob<'a> {
417 pub gate: (usize, usize, usize, &'a [f32]),
418 pub up: (usize, usize, usize, &'a [f32]),
419 pub down: (usize, usize, usize, &'a [f32]),
420 pub xs_gate: Vec<f32>,
421 pub xs_up: Vec<f32>,
422 pub down_col: &'a [f32],
423 pub w: f32,
424 pub q1: bool,
427 pub q4t: bool,
430}
431
432pub struct BatchJob<'a> {
434 pub idx: usize,
435 pub rows: usize,
436 pub cols: usize,
437 pub row_scale: &'a [f32],
438 pub xs: Vec<f32>,
439 pub q1: bool,
441}
442
443#[derive(Clone, Copy, PartialEq, Eq)]
444enum Backend {
445 None,
446 #[cfg(target_os = "macos")]
447 Metal,
448 #[cfg(feature = "gpu")]
449 Wgpu,
450}
451
452fn backend() -> Backend {
453 #[cfg(feature = "gpu")]
454 if crate::gpu_wgpu::selected() {
455 return if crate::gpu_wgpu::enabled() {
456 Backend::Wgpu
457 } else {
458 Backend::None
459 };
460 }
461 #[cfg(target_os = "macos")]
462 if crate::gpu_metal::enabled() {
463 return Backend::Metal;
464 }
465 Backend::None
466}
467
468pub fn backend_available() -> bool {
474 #[cfg(target_os = "macos")]
475 {
476 true
478 }
479 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
480 {
481 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
482 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
483 }
484 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
485 {
486 false
487 }
488}
489
490pub fn enabled() -> bool {
491 backend() != Backend::None
492}
493
494pub fn wgpu_active() -> bool {
508 #[cfg(feature = "gpu")]
509 {
510 matches!(backend(), Backend::Wgpu)
511 }
512 #[cfg(not(feature = "gpu"))]
513 {
514 false
515 }
516}
517
518pub fn wgpu_graph_default() -> bool {
519 #[cfg(feature = "gpu")]
520 {
521 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
522 }
523 #[cfg(not(feature = "gpu"))]
524 {
525 false
526 }
527}
528
529#[allow(clippy::too_many_arguments, unused_variables)]
531pub fn q8_matvec_range(
532 model: &Arc<CmfModel>,
533 idx: usize,
534 row0: usize,
535 row_scale: &[f32],
536 xs: &[f32],
537 rows: usize,
538 cols: usize,
539 out: &mut [f32],
540) -> bool {
541 match backend() {
542 #[cfg(target_os = "macos")]
543 Backend::Metal => {
544 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
545 }
546 #[cfg(feature = "gpu")]
547 Backend::Wgpu => {
548 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
549 }
550 Backend::None => false,
551 }
552}
553
554#[allow(clippy::too_many_arguments, unused_variables)]
557pub fn q8_matmat(
558 model: &Arc<CmfModel>,
559 idx: usize,
560 row_scale: &[f32],
561 pre: &[f32],
562 b: usize,
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_matmat(model, idx, row_scale, pre, b, rows, cols, out)
571 }
572 #[cfg(feature = "gpu")]
573 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
574 Backend::None => false,
575 }
576}
577
578#[allow(unused_variables)]
581pub fn q1_matvec(
582 model: &Arc<CmfModel>,
583 idx: usize,
584 xs: &[f32],
585 rows: usize,
586 cols: usize,
587 out: &mut [f32],
588) -> bool {
589 match backend() {
590 #[cfg(target_os = "macos")]
591 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
592 #[cfg(feature = "gpu")]
593 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
594 Backend::None => false,
595 }
596}
597
598#[allow(clippy::too_many_arguments)]
602pub fn attn_dropin(
603 model: &Arc<CmfModel>,
604 kv_id: u64,
605 layer: usize,
606 normed: &[f32],
607 wq_idx: usize,
608 wk_idx: usize,
609 wv_idx: usize,
610 wo_idx: usize,
611 q_norm: Option<&[f32]>,
612 k_norm: Option<&[f32]>,
613 invf: &[f32],
614 nh: usize,
615 nkv: usize,
616 hd: usize,
617 rd: usize,
618 hidden: usize,
619 pos: usize,
620 cap: usize,
621 gemma: bool,
622 eps: f32,
623 cpu_k: &[Vec<f32>],
624 cpu_v: &[Vec<f32>],
625 out: &mut [f32],
626) -> bool {
627 match backend() {
628 #[cfg(feature = "gpu")]
629 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
630 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
631 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
632 ),
633 #[allow(unused_variables)]
634 _ => false,
635 }
636}
637
638pub struct GraphW<'a> {
642 pub idx: usize,
643 pub kind: u8,
644 pub row_scale: &'a [f32],
645 pub data: &'a [f32],
646}
647
648pub enum GraphAttn<'a> {
651 Full {
652 wq: GraphW<'a>,
653 wk: GraphW<'a>,
654 wv: GraphW<'a>,
655 wo: GraphW<'a>,
656 q_norm: Option<&'a [f32]>,
657 k_norm: Option<&'a [f32]>,
658 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
660 output_gate: bool,
663 cpu_k: &'a [Vec<f32>],
664 cpu_v: &'a [Vec<f32>],
665 },
666 Gdn {
667 qkv: GraphW<'a>,
668 z: GraphW<'a>,
669 a: GraphW<'a>,
670 b: GraphW<'a>,
671 out: GraphW<'a>,
672 conv1d: &'a [f32],
673 a_log: &'a [f32],
674 dt_bias: &'a [f32],
675 norm: &'a [f32],
676 nv: usize,
677 nk: usize,
678 dk: usize,
679 dv: usize,
680 kk: usize,
681 },
682}
683
684pub struct GraphLayer<'a> {
686 pub input_norm: &'a [f32],
687 pub attn: GraphAttn<'a>,
688 pub post_norm: &'a [f32],
689 pub ffn: GraphFfn<'a>,
690}
691
692pub enum GraphFfn<'a> {
697 Dense {
698 gate: GraphW<'a>,
699 up: GraphW<'a>,
700 down: GraphW<'a>,
701 },
702 Moe {
703 router: GraphW<'a>,
705 shared_gate: GraphW<'a>,
707 experts: Vec<(usize, usize, usize)>,
711 n_exp: usize,
713 top_k: usize,
714 inter: usize,
715 norm_topk: bool,
716 },
717}
718
719#[allow(clippy::too_many_arguments)]
724pub fn forward_token_graph(
725 model: &Arc<CmfModel>,
726 kv_id: u64,
727 layers: &[GraphLayer],
728 invf: &[f32],
729 h: &mut [f32],
730 nh: usize,
731 nkv: usize,
732 hd: usize,
733 rd: usize,
734 hidden: usize,
735 inter: usize,
736 position: usize,
737 cap: usize,
738 gemma: bool,
739 eps: f32,
740 lm_head: Option<(&GraphW, usize)>,
741 final_norm: &[f32],
742 logits: &mut Vec<f32>,
743 loop_norm_at: &[usize],
744) -> bool {
745 match backend() {
746 #[cfg(feature = "gpu")]
747 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
748 model,
749 kv_id,
750 layers,
751 invf,
752 h,
753 nh,
754 nkv,
755 hd,
756 rd,
757 hidden,
758 inter,
759 position,
760 cap,
761 gemma,
762 eps,
763 lm_head,
764 final_norm,
765 logits,
766 loop_norm_at,
767 ),
768 #[allow(unused_variables)]
769 _ => {
770 let _ = (lm_head, final_norm, logits, loop_norm_at);
771 false
772 }
773 }
774}
775
776#[allow(clippy::too_many_arguments)]
780pub fn forward_batch_graph(
781 model: &Arc<CmfModel>,
782 kv_id: u64,
783 layers: &[GraphLayer],
784 invf: &[f32],
785 h: &mut [f32],
786 nh: usize,
787 nkv: usize,
788 hd: usize,
789 rd: usize,
790 hidden: usize,
791 inter: usize,
792 positions: &[usize],
793 cap: usize,
794 gemma: bool,
795 eps: f32,
796 k: usize,
797) -> bool {
798 match backend() {
799 #[cfg(feature = "gpu")]
800 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
801 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
802 eps, k,
803 ),
804 _ => false,
805 }
806}
807
808pub fn graph_kv_reset(_kv_id: u64) {
810 #[cfg(feature = "gpu")]
811 if backend() == Backend::Wgpu {
812 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
813 }
814}
815
816pub fn q1t_matvec(
820 model: &Arc<CmfModel>,
821 idx: usize,
822 xs: &[f32],
823 rows: usize,
824 cols: usize,
825 out: &mut [f32],
826) -> bool {
827 match backend() {
828 #[cfg(target_os = "macos")]
829 Backend::Metal => {
830 if metal_q1t_enabled() {
831 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
832 } else {
833 false
834 }
835 }
836 #[cfg(feature = "gpu")]
837 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
838 Backend::None => false,
839 }
840}
841
842#[allow(unused_variables)]
845pub fn q4b_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 => false,
856 #[cfg(feature = "gpu")]
857 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
858 Backend::None => false,
859 }
860}
861
862pub fn q1t_matmat(
865 model: &Arc<CmfModel>,
866 idx: usize,
867 xs: &[f32],
868 b: usize,
869 rows: usize,
870 cols: usize,
871 out: &mut [f32],
872) -> bool {
873 match backend() {
874 #[cfg(target_os = "macos")]
875 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
879 #[cfg(feature = "gpu")]
880 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
881 Backend::None => false,
882 }
883}
884
885#[cfg(target_os = "macos")]
889pub(crate) fn metal_q1t_enabled() -> bool {
890 std::env::var("CMF_METAL_Q1T")
891 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
892 .unwrap_or(true)
893}
894
895pub fn q1_matmat(
897 model: &Arc<CmfModel>,
898 idx: usize,
899 xs: &[f32],
900 b: usize,
901 rows: usize,
902 cols: usize,
903 out: &mut [f32],
904) -> bool {
905 match backend() {
906 #[cfg(feature = "gpu")]
907 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
908 #[allow(unused_variables)]
909 _ => false,
910 }
911}
912
913static MM_KILL: AtomicBool = AtomicBool::new(false);
918pub(crate) fn mm_killed() -> bool {
919 MM_KILL.load(Ordering::Relaxed)
920}
921pub(crate) fn mm_kill() {
922 MM_KILL.store(true, Ordering::Relaxed);
923}
924
925#[allow(unused_variables, clippy::too_many_arguments)]
928pub fn q4t_ffn(
929 model: &Arc<CmfModel>,
930 w1: usize,
931 w3: usize,
932 w2: usize,
933 xs: &[f32],
934 b: usize,
935 hidden: usize,
936 inter: usize,
937 out: &mut [f32],
938) -> bool {
939 match backend() {
940 #[cfg(target_os = "macos")]
941 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
942 #[cfg(feature = "gpu")]
943 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
944 #[allow(unreachable_patterns)]
945 _ => false,
946 }
947}
948
949pub struct DitBlockArgs<'a> {
954 pub n: usize,
955 pub hidden: usize,
956 pub inter: usize,
957 pub nh: usize,
958 pub nkv: usize,
959 pub hd: usize,
960 pub eps: f32,
961 pub rope_cos: &'a [f32],
962 pub rope_sin: &'a [f32],
963 pub norm1: &'a [f32],
964 pub norm2: &'a [f32],
965 pub ffn_norm1: &'a [f32],
966 pub ffn_norm2: &'a [f32],
967 pub norm_q: &'a [f32],
968 pub norm_k: &'a [f32],
969 pub s_msa: &'a [f32],
970 pub gate_msa: &'a [f32],
971 pub s_mlp: &'a [f32],
972 pub gate_mlp: &'a [f32],
973 pub wq: usize,
974 pub wk: usize,
975 pub wv: usize,
976 pub wo: usize,
977 pub w1: usize,
978 pub w3: usize,
979 pub w2: usize,
980}
981
982#[allow(unused_variables)]
986pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
987 match backend() {
988 #[cfg(target_os = "macos")]
989 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
990 _ => false,
991 }
992}
993
994pub struct VaeResnetArgs<'a> {
998 pub groups: usize,
999 pub ic: usize,
1000 pub oc: usize,
1001 pub h: usize,
1002 pub w: usize,
1003 pub n1w: &'a [f32],
1004 pub n1b: &'a [f32],
1005 pub c1w: &'a [f32],
1006 pub c1b: &'a [f32],
1007 pub c1k: usize,
1008 pub n2w: &'a [f32],
1009 pub n2b: &'a [f32],
1010 pub c2w: &'a [f32],
1011 pub c2b: &'a [f32],
1012 pub c2k: usize,
1013 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1014}
1015
1016#[allow(unused_variables)]
1019pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1020 match backend() {
1021 #[cfg(target_os = "macos")]
1022 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1023 _ => false,
1024 }
1025}
1026
1027#[allow(unused_variables, clippy::too_many_arguments)]
1030pub fn vae_upsample_conv(
1031 w: &[f32],
1032 bias: &[f32],
1033 x: &[f32],
1034 ic: usize,
1035 oc: usize,
1036 h: usize,
1037 w_img: usize,
1038 k: usize,
1039 out: &mut [f32],
1040) -> bool {
1041 match backend() {
1042 #[cfg(target_os = "macos")]
1043 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1044 _ => false,
1045 }
1046}
1047
1048#[allow(unused_variables, clippy::too_many_arguments)]
1051pub fn vae_conv2d(
1052 w: &[f32],
1053 bias: &[f32],
1054 x: &[f32],
1055 ic: usize,
1056 oc: usize,
1057 h: usize,
1058 w_img: usize,
1059 k: usize,
1060 out: &mut [f32],
1061) -> bool {
1062 match backend() {
1063 #[cfg(target_os = "macos")]
1064 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1065 _ => false,
1066 }
1067}
1068
1069#[allow(unused_variables, clippy::too_many_arguments)]
1073pub fn dit_attention(
1074 qh: &[f32],
1075 kh: &[f32],
1076 vh: &[f32],
1077 nh: usize,
1078 nkv: usize,
1079 n: usize,
1080 hd: usize,
1081 scale: f32,
1082 out: &mut [f32],
1083) -> bool {
1084 match backend() {
1085 #[cfg(target_os = "macos")]
1086 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1087 _ => false,
1088 }
1089}
1090
1091#[allow(unused_variables)]
1096pub fn q4t_matmat(
1097 model: &Arc<CmfModel>,
1098 idx: usize,
1099 xs: &[f32],
1100 b: usize,
1101 rows: usize,
1102 cols: usize,
1103 out: &mut [f32],
1104) -> bool {
1105 match backend() {
1106 #[cfg(target_os = "macos")]
1107 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1108 #[cfg(feature = "gpu")]
1109 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1110 #[allow(unreachable_patterns)]
1111 _ => false,
1112 }
1113}
1114
1115#[cfg(target_os = "macos")]
1117pub use crate::gpu_metal::{
1118 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1119 kv_mirror_read_last, kv_mirror_take_imp,
1120};
1121
1122#[cfg(target_os = "macos")]
1124pub fn gdn_block(
1125 model: &Arc<CmfModel>,
1126 layers: &[GdnGpuLayer],
1127 states: &mut [&mut [f32]],
1128 cfg: &GdnGpuCfg,
1129 h: &mut [f32],
1130) -> bool {
1131 match backend() {
1132 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1133 _ => false,
1134 }
1135}
1136
1137#[allow(unused_variables)]
1139pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1140 match backend() {
1141 #[cfg(target_os = "macos")]
1142 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1143 #[cfg(feature = "gpu")]
1144 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1145 Backend::None => false,
1146 }
1147}
1148
1149#[allow(unused_variables)]
1151pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1152 match backend() {
1153 #[cfg(target_os = "macos")]
1154 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1155 #[cfg(feature = "gpu")]
1156 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1157 Backend::None => false,
1158 }
1159}
1160
1161static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1177static 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)];
1181
1182const GRAPH_RACE_SAMPLES: u32 = 4;
1184
1185pub fn graph_race_begin_generation() {
1188 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1189 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1190 return;
1191 }
1192 let (gn, cn) = (
1193 GRAPH_N[1].load(Ordering::Relaxed),
1194 GRAPH_N[0].load(Ordering::Relaxed),
1195 );
1196 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1197 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1198 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1199 let verdict = if g_avg < c_avg { 1 } else { 2 };
1200 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1201 tracing::info!(
1202 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1203 g_avg as f64 / 1e6,
1204 c_avg as f64 / 1e6,
1205 if verdict == 1 { "graph" } else { "normal path" }
1206 );
1207 return;
1208 }
1209 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1210 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1211}
1212
1213pub fn graph_race_use_graph(trusted: bool) -> bool {
1217 if trusted {
1218 return true;
1219 }
1220 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1221 1 => true,
1222 2 => false,
1223 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1224 }
1225}
1226
1227pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1232 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1233 return false;
1234 }
1235 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1236 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1237 if !first || cn == 0 {
1238 return false;
1239 }
1240 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1241 let ns = dur.as_nanos() as u64;
1242 if ns > 1_000_000_000 && ns > 4 * c_avg {
1243 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1244 tracing::info!(
1245 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1246 ns as f64 / 1e6,
1247 c_avg as f64 / 1e6
1248 );
1249 return true;
1250 }
1251 false
1252}
1253
1254pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1258 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1259 return;
1260 }
1261 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1262 if tok == 0 {
1263 return;
1264 }
1265 let i = used_graph as usize;
1266 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1267 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1268}