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 enabled() -> bool {
470 backend() != Backend::None
471}
472
473pub fn wgpu_active() -> bool {
487 #[cfg(feature = "gpu")]
488 {
489 matches!(backend(), Backend::Wgpu)
490 }
491 #[cfg(not(feature = "gpu"))]
492 {
493 false
494 }
495}
496
497pub fn wgpu_graph_default() -> bool {
498 #[cfg(feature = "gpu")]
499 {
500 matches!(backend(), Backend::Wgpu) && crate::gpu_wgpu::discrete_active()
501 }
502 #[cfg(not(feature = "gpu"))]
503 {
504 false
505 }
506}
507
508#[allow(clippy::too_many_arguments, unused_variables)]
510pub fn q8_matvec_range(
511 model: &Arc<CmfModel>,
512 idx: usize,
513 row0: usize,
514 row_scale: &[f32],
515 xs: &[f32],
516 rows: usize,
517 cols: usize,
518 out: &mut [f32],
519) -> bool {
520 match backend() {
521 #[cfg(target_os = "macos")]
522 Backend::Metal => {
523 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
524 }
525 #[cfg(feature = "gpu")]
526 Backend::Wgpu => {
527 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
528 }
529 Backend::None => false,
530 }
531}
532
533#[allow(clippy::too_many_arguments, unused_variables)]
536pub fn q8_matmat(
537 model: &Arc<CmfModel>,
538 idx: usize,
539 row_scale: &[f32],
540 pre: &[f32],
541 b: usize,
542 rows: usize,
543 cols: usize,
544 out: &mut [f32],
545) -> bool {
546 match backend() {
547 #[cfg(target_os = "macos")]
548 Backend::Metal => {
549 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
550 }
551 #[cfg(feature = "gpu")]
552 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
553 Backend::None => false,
554 }
555}
556
557#[allow(unused_variables)]
560pub fn q1_matvec(
561 model: &Arc<CmfModel>,
562 idx: usize,
563 xs: &[f32],
564 rows: usize,
565 cols: usize,
566 out: &mut [f32],
567) -> bool {
568 match backend() {
569 #[cfg(target_os = "macos")]
570 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
571 #[cfg(feature = "gpu")]
572 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
573 Backend::None => false,
574 }
575}
576
577#[allow(clippy::too_many_arguments)]
581pub fn attn_dropin(
582 model: &Arc<CmfModel>,
583 kv_id: u64,
584 layer: usize,
585 normed: &[f32],
586 wq_idx: usize,
587 wk_idx: usize,
588 wv_idx: usize,
589 wo_idx: usize,
590 q_norm: Option<&[f32]>,
591 k_norm: Option<&[f32]>,
592 invf: &[f32],
593 nh: usize,
594 nkv: usize,
595 hd: usize,
596 rd: usize,
597 hidden: usize,
598 pos: usize,
599 cap: usize,
600 gemma: bool,
601 eps: f32,
602 cpu_k: &[Vec<f32>],
603 cpu_v: &[Vec<f32>],
604 out: &mut [f32],
605) -> bool {
606 match backend() {
607 #[cfg(feature = "gpu")]
608 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
609 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
610 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
611 ),
612 #[allow(unused_variables)]
613 _ => false,
614 }
615}
616
617pub struct GraphW<'a> {
621 pub idx: usize,
622 pub kind: u8,
623 pub row_scale: &'a [f32],
624 pub data: &'a [f32],
625}
626
627pub enum GraphAttn<'a> {
630 Full {
631 wq: GraphW<'a>,
632 wk: GraphW<'a>,
633 wv: GraphW<'a>,
634 wo: GraphW<'a>,
635 q_norm: Option<&'a [f32]>,
636 k_norm: Option<&'a [f32]>,
637 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
639 output_gate: bool,
642 cpu_k: &'a [Vec<f32>],
643 cpu_v: &'a [Vec<f32>],
644 },
645 Gdn {
646 qkv: GraphW<'a>,
647 z: GraphW<'a>,
648 a: GraphW<'a>,
649 b: GraphW<'a>,
650 out: GraphW<'a>,
651 conv1d: &'a [f32],
652 a_log: &'a [f32],
653 dt_bias: &'a [f32],
654 norm: &'a [f32],
655 nv: usize,
656 nk: usize,
657 dk: usize,
658 dv: usize,
659 kk: usize,
660 },
661}
662
663pub struct GraphLayer<'a> {
665 pub input_norm: &'a [f32],
666 pub attn: GraphAttn<'a>,
667 pub post_norm: &'a [f32],
668 pub ffn: GraphFfn<'a>,
669}
670
671pub enum GraphFfn<'a> {
676 Dense {
677 gate: GraphW<'a>,
678 up: GraphW<'a>,
679 down: GraphW<'a>,
680 },
681 Moe {
682 router: GraphW<'a>,
684 shared_gate: GraphW<'a>,
686 experts: Vec<(usize, usize, usize)>,
690 n_exp: usize,
692 top_k: usize,
693 inter: usize,
694 norm_topk: bool,
695 },
696}
697
698#[allow(clippy::too_many_arguments)]
703pub fn forward_token_graph(
704 model: &Arc<CmfModel>,
705 kv_id: u64,
706 layers: &[GraphLayer],
707 invf: &[f32],
708 h: &mut [f32],
709 nh: usize,
710 nkv: usize,
711 hd: usize,
712 rd: usize,
713 hidden: usize,
714 inter: usize,
715 position: usize,
716 cap: usize,
717 gemma: bool,
718 eps: f32,
719 lm_head: Option<(&GraphW, usize)>,
720 final_norm: &[f32],
721 logits: &mut Vec<f32>,
722 loop_norm_at: &[usize],
723) -> bool {
724 match backend() {
725 #[cfg(feature = "gpu")]
726 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
727 model,
728 kv_id,
729 layers,
730 invf,
731 h,
732 nh,
733 nkv,
734 hd,
735 rd,
736 hidden,
737 inter,
738 position,
739 cap,
740 gemma,
741 eps,
742 lm_head,
743 final_norm,
744 logits,
745 loop_norm_at,
746 ),
747 #[allow(unused_variables)]
748 _ => {
749 let _ = (lm_head, final_norm, logits, loop_norm_at);
750 false
751 }
752 }
753}
754
755#[allow(clippy::too_many_arguments)]
759pub fn forward_batch_graph(
760 model: &Arc<CmfModel>,
761 kv_id: u64,
762 layers: &[GraphLayer],
763 invf: &[f32],
764 h: &mut [f32],
765 nh: usize,
766 nkv: usize,
767 hd: usize,
768 rd: usize,
769 hidden: usize,
770 inter: usize,
771 positions: &[usize],
772 cap: usize,
773 gemma: bool,
774 eps: f32,
775 k: usize,
776) -> bool {
777 match backend() {
778 #[cfg(feature = "gpu")]
779 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
780 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
781 eps, k,
782 ),
783 _ => false,
784 }
785}
786
787pub fn graph_kv_reset(_kv_id: u64) {
789 #[cfg(feature = "gpu")]
790 if backend() == Backend::Wgpu {
791 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
792 }
793}
794
795pub fn q1t_matvec(
799 model: &Arc<CmfModel>,
800 idx: usize,
801 xs: &[f32],
802 rows: usize,
803 cols: usize,
804 out: &mut [f32],
805) -> bool {
806 match backend() {
807 #[cfg(target_os = "macos")]
808 Backend::Metal => {
809 if metal_q1t_enabled() {
810 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
811 } else {
812 false
813 }
814 }
815 #[cfg(feature = "gpu")]
816 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
817 Backend::None => false,
818 }
819}
820
821#[allow(unused_variables)]
824pub fn q4b_matvec(
825 model: &Arc<CmfModel>,
826 idx: usize,
827 xs: &[f32],
828 rows: usize,
829 cols: usize,
830 out: &mut [f32],
831) -> bool {
832 match backend() {
833 #[cfg(target_os = "macos")]
834 Backend::Metal => false,
835 #[cfg(feature = "gpu")]
836 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
837 Backend::None => false,
838 }
839}
840
841pub fn q1t_matmat(
844 model: &Arc<CmfModel>,
845 idx: usize,
846 xs: &[f32],
847 b: usize,
848 rows: usize,
849 cols: usize,
850 out: &mut [f32],
851) -> bool {
852 match backend() {
853 #[cfg(target_os = "macos")]
854 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
858 #[cfg(feature = "gpu")]
859 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
860 Backend::None => false,
861 }
862}
863
864#[cfg(target_os = "macos")]
868pub(crate) fn metal_q1t_enabled() -> bool {
869 std::env::var("CMF_METAL_Q1T")
870 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
871 .unwrap_or(true)
872}
873
874pub fn q1_matmat(
876 model: &Arc<CmfModel>,
877 idx: usize,
878 xs: &[f32],
879 b: usize,
880 rows: usize,
881 cols: usize,
882 out: &mut [f32],
883) -> bool {
884 match backend() {
885 #[cfg(feature = "gpu")]
886 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
887 #[allow(unused_variables)]
888 _ => false,
889 }
890}
891
892static MM_KILL: AtomicBool = AtomicBool::new(false);
897pub(crate) fn mm_killed() -> bool {
898 MM_KILL.load(Ordering::Relaxed)
899}
900pub(crate) fn mm_kill() {
901 MM_KILL.store(true, Ordering::Relaxed);
902}
903
904#[allow(unused_variables, clippy::too_many_arguments)]
907pub fn q4t_ffn(
908 model: &Arc<CmfModel>,
909 w1: usize,
910 w3: usize,
911 w2: usize,
912 xs: &[f32],
913 b: usize,
914 hidden: usize,
915 inter: usize,
916 out: &mut [f32],
917) -> bool {
918 match backend() {
919 #[cfg(target_os = "macos")]
920 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
921 #[cfg(feature = "gpu")]
922 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
923 #[allow(unreachable_patterns)]
924 _ => false,
925 }
926}
927
928pub struct DitBlockArgs<'a> {
933 pub n: usize,
934 pub hidden: usize,
935 pub inter: usize,
936 pub nh: usize,
937 pub nkv: usize,
938 pub hd: usize,
939 pub eps: f32,
940 pub rope_cos: &'a [f32],
941 pub rope_sin: &'a [f32],
942 pub norm1: &'a [f32],
943 pub norm2: &'a [f32],
944 pub ffn_norm1: &'a [f32],
945 pub ffn_norm2: &'a [f32],
946 pub norm_q: &'a [f32],
947 pub norm_k: &'a [f32],
948 pub s_msa: &'a [f32],
949 pub gate_msa: &'a [f32],
950 pub s_mlp: &'a [f32],
951 pub gate_mlp: &'a [f32],
952 pub wq: usize,
953 pub wk: usize,
954 pub wv: usize,
955 pub wo: usize,
956 pub w1: usize,
957 pub w3: usize,
958 pub w2: usize,
959}
960
961#[allow(unused_variables)]
965pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
966 match backend() {
967 #[cfg(target_os = "macos")]
968 Backend::Metal => crate::gpu_metal::dit_block(model, a, x),
969 _ => false,
970 }
971}
972
973pub struct VaeResnetArgs<'a> {
977 pub groups: usize,
978 pub ic: usize,
979 pub oc: usize,
980 pub h: usize,
981 pub w: usize,
982 pub n1w: &'a [f32],
983 pub n1b: &'a [f32],
984 pub c1w: &'a [f32],
985 pub c1b: &'a [f32],
986 pub c1k: usize,
987 pub n2w: &'a [f32],
988 pub n2b: &'a [f32],
989 pub c2w: &'a [f32],
990 pub c2b: &'a [f32],
991 pub c2k: usize,
992 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
993}
994
995#[allow(unused_variables)]
998pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
999 match backend() {
1000 #[cfg(target_os = "macos")]
1001 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1002 _ => false,
1003 }
1004}
1005
1006#[allow(unused_variables, clippy::too_many_arguments)]
1009pub fn vae_upsample_conv(
1010 w: &[f32],
1011 bias: &[f32],
1012 x: &[f32],
1013 ic: usize,
1014 oc: usize,
1015 h: usize,
1016 w_img: usize,
1017 k: usize,
1018 out: &mut [f32],
1019) -> bool {
1020 match backend() {
1021 #[cfg(target_os = "macos")]
1022 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1023 _ => false,
1024 }
1025}
1026
1027#[allow(unused_variables, clippy::too_many_arguments)]
1030pub fn vae_conv2d(
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_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1044 _ => false,
1045 }
1046}
1047
1048#[allow(unused_variables, clippy::too_many_arguments)]
1052pub fn dit_attention(
1053 qh: &[f32],
1054 kh: &[f32],
1055 vh: &[f32],
1056 nh: usize,
1057 nkv: usize,
1058 n: usize,
1059 hd: usize,
1060 scale: f32,
1061 out: &mut [f32],
1062) -> bool {
1063 match backend() {
1064 #[cfg(target_os = "macos")]
1065 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
1066 _ => false,
1067 }
1068}
1069
1070#[allow(unused_variables)]
1075pub fn q4t_matmat(
1076 model: &Arc<CmfModel>,
1077 idx: usize,
1078 xs: &[f32],
1079 b: usize,
1080 rows: usize,
1081 cols: usize,
1082 out: &mut [f32],
1083) -> bool {
1084 match backend() {
1085 #[cfg(target_os = "macos")]
1086 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
1087 #[cfg(feature = "gpu")]
1088 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
1089 #[allow(unreachable_patterns)]
1090 _ => false,
1091 }
1092}
1093
1094#[cfg(target_os = "macos")]
1096pub use crate::gpu_metal::{
1097 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GraphDims, TokenGraph, kv_mirror_drop,
1098 kv_mirror_read_last, kv_mirror_take_imp,
1099};
1100
1101#[cfg(target_os = "macos")]
1103pub fn gdn_block(
1104 model: &Arc<CmfModel>,
1105 layers: &[GdnGpuLayer],
1106 states: &mut [&mut [f32]],
1107 cfg: &GdnGpuCfg,
1108 h: &mut [f32],
1109) -> bool {
1110 match backend() {
1111 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
1112 _ => false,
1113 }
1114}
1115
1116#[allow(unused_variables)]
1118pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
1119 match backend() {
1120 #[cfg(target_os = "macos")]
1121 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
1122 #[cfg(feature = "gpu")]
1123 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
1124 Backend::None => false,
1125 }
1126}
1127
1128#[allow(unused_variables)]
1130pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
1131 match backend() {
1132 #[cfg(target_os = "macos")]
1133 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
1134 #[cfg(feature = "gpu")]
1135 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
1136 Backend::None => false,
1137 }
1138}
1139
1140static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
1156static 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)];
1160
1161const GRAPH_RACE_SAMPLES: u32 = 4;
1163
1164pub fn graph_race_begin_generation() {
1167 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
1168 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1169 return;
1170 }
1171 let (gn, cn) = (
1172 GRAPH_N[1].load(Ordering::Relaxed),
1173 GRAPH_N[0].load(Ordering::Relaxed),
1174 );
1175 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
1176 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
1177 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1178 let verdict = if g_avg < c_avg { 1 } else { 2 };
1179 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
1180 tracing::info!(
1181 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
1182 g_avg as f64 / 1e6,
1183 c_avg as f64 / 1e6,
1184 if verdict == 1 { "graph" } else { "normal path" }
1185 );
1186 return;
1187 }
1188 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
1189 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
1190}
1191
1192pub fn graph_race_use_graph(trusted: bool) -> bool {
1196 if trusted {
1197 return true;
1198 }
1199 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
1200 1 => true,
1201 2 => false,
1202 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
1203 }
1204}
1205
1206pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
1211 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1212 return false;
1213 }
1214 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
1215 let cn = GRAPH_N[0].load(Ordering::Relaxed);
1216 if !first || cn == 0 {
1217 return false;
1218 }
1219 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
1220 let ns = dur.as_nanos() as u64;
1221 if ns > 1_000_000_000 && ns > 4 * c_avg {
1222 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
1223 tracing::info!(
1224 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
1225 ns as f64 / 1e6,
1226 c_avg as f64 / 1e6
1227 );
1228 return true;
1229 }
1230 false
1231}
1232
1233pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
1237 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
1238 return;
1239 }
1240 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
1241 if tok == 0 {
1242 return;
1243 }
1244 let i = used_graph as usize;
1245 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1246 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
1247}