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 fn probe_set_device(label: &str) {
52 let _ = DEVICE_LABEL.set(label.to_string());
53}
54
55fn device_label() -> &'static str {
56 DEVICE_LABEL.get().map(String::as_str).unwrap_or("unknown")
57}
58
59static DEVICE_LABEL: std::sync::OnceLock<String> = std::sync::OnceLock::new();
60
61static CACHE_DIR: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
70
71pub fn set_cache_dir(dir: std::path::PathBuf) {
73 let _ = CACHE_DIR.set(dir);
74}
75
76pub fn cache_dir_pub() -> std::path::PathBuf {
78 cache_dir()
79}
80
81fn cache_dir() -> std::path::PathBuf {
82 if let Some(d) = CACHE_DIR.get() {
83 return d.clone();
84 }
85 match std::env::var_os("TMPDIR") {
86 Some(t) => std::path::PathBuf::from(t),
87 None => std::env::temp_dir(),
88 }
89}
90
91fn probe_cache_path() -> Option<std::path::PathBuf> {
94 match std::env::var("CMF_PROBE_CACHE") {
95 Ok(v) if v == "0" => None,
96 Ok(v) => Some(std::path::PathBuf::from(v)),
97 Err(_) => Some(cache_dir().join("cortiq-gpu-probe.tsv")),
98 }
99}
100
101fn probe_cache_key_named(class: &str) -> String {
105 format!(
106 "{}\t{}\t{}",
107 env!("CARGO_PKG_VERSION"),
108 device_label(),
109 class
110 )
111}
112
113const CLASS_NAMES: [&str; 7] = [
114 "ffn",
115 "matvec",
116 "matmat",
117 "qkv-batch",
118 "matmat-wide",
119 "lm-head",
120 "gemm-nt",
121];
122
123fn probe_cache_load() {
132 static ONCE: std::sync::Once = std::sync::Once::new();
133 ONCE.call_once(|| {
134 let Some(path) = probe_cache_path() else {
135 return;
136 };
137 if cfg!(test) && std::env::var("CMF_PROBE_CACHE").is_err() {
142 return;
143 }
144 let Ok(text) = std::fs::read_to_string(&path) else {
145 return;
146 };
147 probe_cache_adopt(&text);
148 });
149}
150
151fn probe_cache_adopt(text: &str) {
155 for line in text.lines() {
156 let Some((key, verdict)) = line.rsplit_once('\t') else {
157 continue;
158 };
159 let winner = match verdict.trim() {
160 "gpu" => 1u8,
161 "cpu" => 2u8,
162 _ => continue,
163 };
164 for (i, name) in CLASS_NAMES.iter().enumerate() {
165 if probe_cache_key_named(name) == key {
166 let _ = PROBES[i].state.compare_exchange(
167 0,
168 winner,
169 Ordering::Relaxed,
170 Ordering::Relaxed,
171 );
172 tracing::debug!("gpu probe [{name}]: remembered → {verdict}");
173 }
174 }
175 }
176}
177
178fn probe_cache_store(c: OpClass, winner: u8) {
181 let Some(path) = probe_cache_path() else {
182 return;
183 };
184 let line = format!(
185 "{}\t{}\n",
186 probe_cache_key_named(CLASS_NAMES[c as usize]),
187 if winner == 1 { "gpu" } else { "cpu" }
188 );
189 use std::io::Write;
190 if let Ok(mut f) = std::fs::OpenOptions::new()
191 .create(true)
192 .append(true)
193 .open(&path)
194 {
195 let _ = f.write_all(line.as_bytes());
196 }
197}
198
199pub fn cold_epoch() -> u64 {
205 COLD_EPOCH.load(std::sync::atomic::Ordering::Relaxed)
206}
207static COLD_EPOCH: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
208
209pub(crate) fn probe_note_cold() {
210 COLD_EPOCH.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
211 PROBE_COLD.with(|c| c.set(true));
212}
213
214pub(crate) fn probe_was_cold() -> bool {
218 PROBE_COLD.with(|c| c.get())
219}
220
221pub fn set_layer(l: i64) {
223 CUR_LAYER.with(|c| c.set(l));
224}
225
226pub fn cur_layer() -> i64 {
228 CUR_LAYER.with(|c| c.get())
229}
230
231fn layer_ranges() -> &'static Option<Vec<(i64, i64)>> {
234 static R: OnceLock<Option<Vec<(i64, i64)>>> = OnceLock::new();
235 R.get_or_init(|| {
236 let s = std::env::var("CMF_GPU_LAYERS").ok()?;
237 let mut v = Vec::new();
238 for part in s.split(',') {
239 let part = part.trim();
240 match part.split_once('-') {
241 Some((a, b)) => v.push((a.trim().parse().ok()?, b.trim().parse().ok()?)),
242 None => {
243 let x: i64 = part.parse().ok()?;
244 v.push((x, x));
245 }
246 }
247 }
248 Some(v)
249 })
250}
251
252fn layer_allowed() -> bool {
253 match layer_ranges() {
254 None => true,
255 Some(ranges) => {
256 let cur = CUR_LAYER.with(|c| c.get());
257 cur < 0 || ranges.iter().any(|(a, b)| cur >= *a && cur <= *b)
258 }
259 }
260}
261
262pub fn enabled_here() -> bool {
266 !CPU_ONLY.with(|c| c.get()) && enabled() && layer_allowed()
267}
268
269#[derive(Clone, Copy)]
281pub enum OpClass {
282 Ffn = 0,
284 Matvec = 1,
286 Matmat = 2,
288 Batch = 3,
290 MatmatWide = 4,
296 MatvecHead = 5,
303 GemmNt = 6,
310}
311
312pub fn matvec_class(rows: usize, cols: usize) -> OpClass {
316 if rows * cols >= 67_108_864 {
317 OpClass::MatvecHead
318 } else {
319 OpClass::Matvec
320 }
321}
322
323pub enum ProbeArm {
325 Gpu,
327 CpuTimed,
329 Cpu,
331}
332
333const PROBE_SAMPLES: u32 = 6;
335
336struct Probe {
337 state: AtomicU8,
339 flip: AtomicU32,
340 gpu_ns: AtomicU64,
341 gpu_n: AtomicU32,
342 cpu_ns: AtomicU64,
343 cpu_n: AtomicU32,
344 gpu_min: AtomicU64,
351 cpu_min: AtomicU64,
352}
353
354impl Probe {
355 const fn new() -> Self {
356 Self {
357 state: AtomicU8::new(0),
358 flip: AtomicU32::new(0),
359 gpu_ns: AtomicU64::new(0),
360 gpu_n: AtomicU32::new(0),
361 cpu_ns: AtomicU64::new(0),
362 cpu_n: AtomicU32::new(0),
363 gpu_min: AtomicU64::new(u64::MAX),
364 cpu_min: AtomicU64::new(u64::MAX),
365 }
366 }
367}
368
369static PROBES: [Probe; 7] = [
370 Probe::new(),
371 Probe::new(),
372 Probe::new(),
373 Probe::new(),
374 Probe::new(),
375 Probe::new(),
376 Probe::new(),
377];
378
379static TRUST_GPU: AtomicBool = AtomicBool::new(false);
386
387pub fn trust_gpu() -> GpuTrust {
389 let was = TRUST_GPU.swap(true, Ordering::Relaxed);
390 GpuTrust(was)
391}
392
393pub struct GpuTrust(bool);
394
395impl Drop for GpuTrust {
396 fn drop(&mut self) {
397 TRUST_GPU.store(self.0, Ordering::Relaxed);
398 }
399}
400
401fn probe_on_for(c: OpClass) -> bool {
402 if TRUST_GPU.load(Ordering::Relaxed) && matches!(c, OpClass::MatmatWide | OpClass::Ffn) {
408 return false;
409 }
410 probe_on()
411}
412
413fn probe_on() -> bool {
414 static ON: OnceLock<bool> = OnceLock::new();
415 *ON.get_or_init(|| {
416 std::env::var("CMF_GPU_PROBE")
417 .map(|v| v != "0" && v != "off")
418 .unwrap_or(true)
419 })
420}
421
422pub fn q1_force() -> bool {
427 #[cfg(target_os = "macos")]
428 {
429 backend() == Backend::Metal
430 }
431 #[cfg(not(target_os = "macos"))]
432 {
433 false
434 }
435}
436
437pub fn fused_block_trusted() -> bool {
456 #[cfg(target_os = "macos")]
457 if backend() == Backend::Metal {
458 return true;
459 }
460 wgpu_graph_default()
461}
462
463pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
475 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
476 {
477 return crate::gpu_wgpu::weight_is_resident(model, idx);
478 }
479 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
480 {
481 let _ = (model, idx);
482 true
483 }
484}
485
486pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
487 if !weights_resident && probe_deciding(c) {
488 return ProbeArm::Gpu;
489 }
490 probe_arm(c)
491}
492
493pub fn probe_arm(c: OpClass) -> ProbeArm {
494 PROBE_COLD.with(|f| f.set(false));
499 if !probe_on_for(c) {
500 return ProbeArm::Gpu;
501 }
502 probe_cache_load();
503 let p = &PROBES[c as usize];
504 match p.state.load(Ordering::Relaxed) {
505 1 => ProbeArm::Gpu,
506 2 => ProbeArm::Cpu,
507 _ => {
508 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
509 ProbeArm::Gpu
510 } else {
511 ProbeArm::CpuTimed
512 }
513 }
514 }
515}
516
517pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
520 let p = &PROBES[c as usize];
521 if p.state.load(Ordering::Relaxed) != 0 {
522 return;
523 }
524 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
525 return; }
527 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
528 if gpu {
529 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
530 p.gpu_n.fetch_add(1, Ordering::Relaxed);
531 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
532 } else {
533 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
534 p.cpu_n.fetch_add(1, Ordering::Relaxed);
535 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
536 }
537 let (gn, cn) = (
538 p.gpu_n.load(Ordering::Relaxed),
539 p.cpu_n.load(Ordering::Relaxed),
540 );
541 if gn >= 2 && cn >= 2 {
542 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
546 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
547 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
557 return;
558 }
559 let winner = if g <= cp { 1 } else { 2 };
560 if p.state
561 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
562 .is_ok()
563 {
564 tracing::info!(
565 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
566 CLASS_NAMES[c as usize],
567 g / 1e6,
568 cp / 1e6,
569 if winner == 1 { "gpu" } else { "cpu" },
570 );
571 probe_cache_store(c, winner);
572 }
573 }
574}
575
576pub fn probe_deciding(c: OpClass) -> bool {
579 probe_on_for(c) && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
580}
581
582#[allow(unused_variables)]
592pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
593 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
594 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
595 let resident = match backend() {
596 #[cfg(target_os = "macos")]
597 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
598 #[cfg(feature = "gpu")]
599 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
600 Backend::None => false,
601 };
602 if !resident && may_upload {
603 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
604 }
605 resident
606}
607
608#[cfg(test)]
610pub(crate) fn probe_reset() {
611 for p in &PROBES {
612 p.state.store(0, Ordering::Relaxed);
613 p.flip.store(0, Ordering::Relaxed);
614 p.gpu_ns.store(0, Ordering::Relaxed);
615 p.gpu_n.store(0, Ordering::Relaxed);
616 p.cpu_ns.store(0, Ordering::Relaxed);
617 p.cpu_n.store(0, Ordering::Relaxed);
618 }
619}
620
621#[cfg(test)]
622mod probe_tests {
623 use super::*;
624 use std::time::Duration;
625
626 #[test]
629 fn probe_alternates_discards_cold_and_decides() {
630 probe_reset();
631 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
633 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
634
635 probe_note_cold();
639 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
640 for _ in 0..PROBE_SAMPLES {
641 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
642 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
643 }
644 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
645
646 for _ in 0..PROBE_SAMPLES {
648 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
649 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
650 }
651 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
652
653 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
655 CPU_ONLY.with(|c| assert!(!c.get()));
656 cpu_scope(|| {
657 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
658 CPU_ONLY.with(|c| assert!(c.get()));
659 });
660 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
661 CPU_ONLY.with(|c| assert!(!c.get()));
662 probe_reset();
663 }
664
665 #[test]
666 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
667 let mine = probe_cache_key_named("gemm-nt");
679 let state = || {
680 PROBES[OpClass::GemmNt as usize]
681 .state
682 .load(Ordering::Relaxed)
683 };
684
685 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
687 assert_eq!(state(), 0);
688 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
690 assert_ne!(older, mine);
691 probe_cache_adopt(&format!("{older}\tgpu\n"));
692 assert_eq!(state(), 0);
693 probe_cache_adopt(&format!("{mine}\tcpu\n"));
695 assert_eq!(state(), 2);
696
697 PROBES[OpClass::GemmNt as usize]
698 .state
699 .store(0, Ordering::Relaxed);
700 }
701}
702
703pub const GPU_MIN_ROWS: usize = 65_536;
706
707pub fn min_rows() -> usize {
714 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
715 .ok()
716 .and_then(|v| v.parse().ok())
717 {
718 return v;
719 }
720 if discrete() { 4096 } else { GPU_MIN_ROWS }
721}
722
723pub fn discrete() -> bool {
725 match backend() {
726 #[cfg(feature = "gpu")]
727 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
728 #[cfg(target_os = "macos")]
729 Backend::Metal => false, Backend::None => false,
731 }
732}
733
734pub struct MoeJob<'a> {
738 pub gate: (usize, usize, usize, &'a [f32]),
739 pub up: (usize, usize, usize, &'a [f32]),
740 pub down: (usize, usize, usize, &'a [f32]),
741 pub xs_gate: Vec<f32>,
742 pub xs_up: Vec<f32>,
743 pub down_col: &'a [f32],
744 pub w: f32,
745 pub q1: bool,
748 pub q4t: bool,
751 pub q4tp: bool,
755 pub gu_q2: bool,
759 pub swiglu_limit: f32,
764}
765
766pub struct BatchJob<'a> {
768 pub idx: usize,
769 pub rows: usize,
770 pub cols: usize,
771 pub row_scale: &'a [f32],
772 pub xs: Vec<f32>,
773 pub layout: BatchLayout,
777}
778
779#[derive(Clone, Copy, PartialEq, Eq, Debug)]
782pub enum BatchLayout {
783 Q8,
784 Q1,
785 Q4t,
786 Q4tp,
787}
788
789#[derive(Clone, Copy, PartialEq, Eq)]
790enum Backend {
791 None,
792 #[cfg(target_os = "macos")]
793 Metal,
794 #[cfg(feature = "gpu")]
795 Wgpu,
796}
797
798fn backend() -> Backend {
799 #[cfg(feature = "gpu")]
800 if crate::gpu_wgpu::selected() {
801 return if crate::gpu_wgpu::enabled() {
802 Backend::Wgpu
803 } else {
804 Backend::None
805 };
806 }
807 #[cfg(target_os = "macos")]
808 if crate::gpu_metal::enabled() {
809 return Backend::Metal;
810 }
811 Backend::None
812}
813
814pub fn backend_available() -> bool {
820 #[cfg(target_os = "macos")]
821 {
822 true
824 }
825 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
826 {
827 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
828 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
829 }
830 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
831 {
832 false
833 }
834}
835
836static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
842
843pub fn pause_gpu() -> GpuPause {
845 GPU_PAUSED.store(true, Ordering::Relaxed);
846 GpuPause(())
847}
848
849pub struct GpuPause(());
850
851impl Drop for GpuPause {
852 fn drop(&mut self) {
853 GPU_PAUSED.store(false, Ordering::Relaxed);
854 }
855}
856
857pub fn enabled() -> bool {
858 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
859}
860
861pub fn wgpu_active() -> bool {
875 #[cfg(feature = "gpu")]
876 {
877 matches!(backend(), Backend::Wgpu)
878 }
879 #[cfg(not(feature = "gpu"))]
880 {
881 false
882 }
883}
884
885pub fn default_device() -> usize {
892 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
893 *D.get_or_init(|| {
894 std::env::var("CMF_GPU_ADAPTER")
895 .ok()
896 .and_then(|v| v.trim().parse::<usize>().ok())
897 .unwrap_or(0)
898 })
899}
900
901thread_local! {
902 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
903}
904
905pub fn current_device() -> usize {
907 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
908}
909
910pub fn set_current_device(i: usize) {
914 CUR_DEV.with(|c| c.set(Some(i)));
915}
916
917pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
919 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
920 let r = f();
921 CUR_DEV.with(|c| c.set(prev));
922 r
923}
924
925pub fn device_count() -> usize {
928 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
929 {
930 return crate::gpu_wgpu::adapter_count();
931 }
932 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
933 {
934 usize::from(backend_available())
935 }
936}
937
938pub fn vram_budget() -> u64 {
942 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
943 {
944 return crate::gpu_wgpu::device_vram_budget();
945 }
946 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
947 {
948 if backend_available() { u64::MAX } else { 0 }
949 }
950}
951
952pub fn upload_bytes() -> u64 {
956 #[cfg(feature = "gpu")]
957 {
958 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
959 }
960 #[cfg(not(feature = "gpu"))]
961 0
962}
963
964#[derive(Clone, Copy, PartialEq, Eq, Debug)]
976pub enum GraphPhase {
977 Prefill,
978 Decode,
979}
980
981pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
989 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
990 Some("0") => false,
991 Some("prefill") => phase == GraphPhase::Prefill,
992 Some(_) => true,
993 None => {
994 if wgpu_graph_default() {
995 return true;
996 }
997 let _ = phase;
1002 false
1003 }
1004 }
1005}
1006
1007pub fn wgpu_graph_default() -> bool {
1008 #[cfg(feature = "gpu")]
1009 {
1010 matches!(backend(), Backend::Wgpu)
1016 && (crate::gpu_wgpu::discrete_active()
1017 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
1018 }
1019 #[cfg(not(feature = "gpu"))]
1020 {
1021 false
1022 }
1023}
1024
1025#[allow(clippy::too_many_arguments, unused_variables)]
1027pub fn q8_matvec_range(
1028 model: &Arc<CmfModel>,
1029 idx: usize,
1030 row0: usize,
1031 row_scale: &[f32],
1032 xs: &[f32],
1033 rows: usize,
1034 cols: usize,
1035 out: &mut [f32],
1036) -> bool {
1037 match backend() {
1038 #[cfg(target_os = "macos")]
1039 Backend::Metal => {
1040 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1041 }
1042 #[cfg(feature = "gpu")]
1043 Backend::Wgpu => {
1044 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1045 }
1046 Backend::None => false,
1047 }
1048}
1049
1050#[allow(clippy::too_many_arguments, unused_variables)]
1053pub fn q8_matmat(
1054 model: &Arc<CmfModel>,
1055 idx: usize,
1056 row_scale: &[f32],
1057 pre: &[f32],
1058 b: usize,
1059 rows: usize,
1060 cols: usize,
1061 out: &mut [f32],
1062) -> bool {
1063 match backend() {
1064 #[cfg(target_os = "macos")]
1065 Backend::Metal => {
1066 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1067 }
1068 #[cfg(feature = "gpu")]
1069 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1070 Backend::None => false,
1071 }
1072}
1073
1074#[allow(unused_variables)]
1077pub fn q1_matvec(
1078 model: &Arc<CmfModel>,
1079 idx: usize,
1080 xs: &[f32],
1081 rows: usize,
1082 cols: usize,
1083 out: &mut [f32],
1084) -> bool {
1085 match backend() {
1086 #[cfg(target_os = "macos")]
1087 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1088 #[cfg(feature = "gpu")]
1089 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1090 Backend::None => false,
1091 }
1092}
1093
1094#[allow(clippy::too_many_arguments)]
1098pub fn attn_dropin(
1099 model: &Arc<CmfModel>,
1100 kv_id: u64,
1101 layer: usize,
1102 normed: &[f32],
1103 wq_idx: usize,
1104 wk_idx: usize,
1105 wv_idx: usize,
1106 wo_idx: usize,
1107 q_norm: Option<&[f32]>,
1108 k_norm: Option<&[f32]>,
1109 invf: &[f32],
1110 nh: usize,
1111 nkv: usize,
1112 hd: usize,
1113 rd: usize,
1114 hidden: usize,
1115 pos: usize,
1116 cap: usize,
1117 gemma: bool,
1118 eps: f32,
1119 cpu_k: &[Vec<f32>],
1120 cpu_v: &[Vec<f32>],
1121 out: &mut [f32],
1122) -> bool {
1123 match backend() {
1124 #[cfg(feature = "gpu")]
1125 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1126 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1127 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1128 ),
1129 #[allow(unused_variables)]
1130 _ => false,
1131 }
1132}
1133
1134pub struct GraphW<'a> {
1138 pub idx: usize,
1139 pub kind: u8,
1140 pub row_scale: &'a [f32],
1141 pub data: &'a [f32],
1142}
1143
1144pub enum GraphAttn<'a> {
1147 Full {
1148 wq: GraphW<'a>,
1149 wk: GraphW<'a>,
1150 wv: GraphW<'a>,
1151 wo: GraphW<'a>,
1152 q_norm: Option<&'a [f32]>,
1153 k_norm: Option<&'a [f32]>,
1154 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1156 output_gate: bool,
1159 cpu_k: &'a [Vec<f32>],
1160 cpu_v: &'a [Vec<f32>],
1161 },
1162 Gdn {
1163 qkv: GraphW<'a>,
1164 z: GraphW<'a>,
1165 a: GraphW<'a>,
1166 b: GraphW<'a>,
1167 out: GraphW<'a>,
1168 conv1d: &'a [f32],
1169 a_log: &'a [f32],
1170 dt_bias: &'a [f32],
1171 norm: &'a [f32],
1172 nv: usize,
1173 nk: usize,
1174 dk: usize,
1175 dv: usize,
1176 kk: usize,
1177 cpu_state: &'a [f32],
1182 },
1183}
1184
1185pub struct GraphLayer<'a> {
1187 pub input_norm: &'a [f32],
1188 pub attn: GraphAttn<'a>,
1189 pub post_norm: &'a [f32],
1190 pub ffn: GraphFfn<'a>,
1191}
1192
1193pub enum GraphFfn<'a> {
1198 Dense {
1199 gate: GraphW<'a>,
1200 up: GraphW<'a>,
1201 down: GraphW<'a>,
1202 },
1203 Moe {
1204 router: GraphW<'a>,
1206 shared_gate: GraphW<'a>,
1208 experts: Vec<(usize, usize, usize)>,
1212 n_exp: usize,
1214 top_k: usize,
1215 inter: usize,
1216 norm_topk: bool,
1217 q4tp: bool,
1223 gu_q2: bool,
1227 },
1228}
1229
1230#[allow(clippy::too_many_arguments)]
1235pub fn forward_token_graph(
1236 model: &Arc<CmfModel>,
1237 kv_id: u64,
1238 layers: &[GraphLayer],
1239 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1242 o1_epoch: u64,
1243 invf: &[f32],
1244 h: &mut [f32],
1245 nh: usize,
1246 nkv: usize,
1247 hd: usize,
1248 rd: usize,
1249 hidden: usize,
1250 inter: usize,
1251 position: usize,
1252 cap: usize,
1253 gemma: bool,
1254 eps: f32,
1255 lm_head: Option<(&GraphW, usize)>,
1256 final_norm: &[f32],
1257 logits: &mut Vec<f32>,
1258 loop_norm_at: &[usize],
1259 steps: usize,
1260 embed: Option<(&GraphW, usize, f32)>,
1261 ids_out: Option<&mut Vec<u32>>,
1262 layers_run: Option<&mut usize>,
1265 layer_base: usize,
1269 hidden_too: bool,
1271) -> bool {
1272 match backend() {
1273 #[cfg(feature = "gpu")]
1274 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1275 model,
1276 kv_id,
1277 layers,
1278 o1,
1279 o1_epoch,
1280 invf,
1281 h,
1282 nh,
1283 nkv,
1284 hd,
1285 rd,
1286 hidden,
1287 inter,
1288 position,
1289 cap,
1290 gemma,
1291 eps,
1292 lm_head,
1293 final_norm,
1294 logits,
1295 loop_norm_at,
1296 steps,
1297 embed,
1298 ids_out,
1299 layers_run,
1300 layer_base,
1301 hidden_too,
1302 ),
1303 #[allow(unused_variables)]
1304 _ => {
1305 let _ = (
1306 lm_head,
1307 final_norm,
1308 logits,
1309 loop_norm_at,
1310 layers_run,
1311 layer_base,
1312 hidden_too,
1313 );
1314 false
1315 }
1316 }
1317}
1318
1319pub struct SpecTail<'a> {
1323 pub lm: GraphW<'a>,
1324 pub lm_rows: usize,
1325 pub final_norm: &'a [f32],
1326 pub logits_out: &'a mut Vec<f32>,
1327}
1328
1329#[allow(clippy::too_many_arguments)]
1333pub fn forward_batch_graph(
1334 model: &Arc<CmfModel>,
1335 kv_id: u64,
1336 layers: &[GraphLayer],
1337 invf: &[f32],
1338 h: &mut [f32],
1339 nh: usize,
1340 nkv: usize,
1341 hd: usize,
1342 rd: usize,
1343 hidden: usize,
1344 inter: usize,
1345 positions: &[usize],
1346 cap: usize,
1347 gemma: bool,
1348 eps: f32,
1349 k: usize,
1350 spec: Option<SpecTail<'_>>,
1351) -> bool {
1352 match backend() {
1353 #[cfg(feature = "gpu")]
1354 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1355 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1356 eps, k, spec,
1357 ),
1358 #[allow(unreachable_patterns)]
1359 _ => {
1360 let _ = spec;
1361 false
1362 }
1363 }
1364}
1365
1366pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1369 #[cfg(feature = "gpu")]
1370 if backend() == Backend::Wgpu {
1371 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1372 }
1373 #[allow(unreachable_code)]
1374 {
1375 let _ = (kv_id, slot);
1376 false
1377 }
1378}
1379
1380pub fn graph_kv_reset(_kv_id: u64) {
1382 #[cfg(feature = "gpu")]
1383 if backend() == Backend::Wgpu {
1384 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1385 }
1386}
1387
1388pub fn q1t_matvec(
1392 model: &Arc<CmfModel>,
1393 idx: usize,
1394 xs: &[f32],
1395 rows: usize,
1396 cols: usize,
1397 out: &mut [f32],
1398) -> bool {
1399 match backend() {
1400 #[cfg(target_os = "macos")]
1401 Backend::Metal => {
1402 if metal_q1t_enabled() {
1403 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1404 } else {
1405 false
1406 }
1407 }
1408 #[cfg(feature = "gpu")]
1409 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1410 Backend::None => false,
1411 }
1412}
1413
1414#[allow(unused_variables)]
1417pub fn q4b_matvec(
1418 model: &Arc<CmfModel>,
1419 idx: usize,
1420 xs: &[f32],
1421 rows: usize,
1422 cols: usize,
1423 out: &mut [f32],
1424) -> bool {
1425 match backend() {
1426 #[cfg(target_os = "macos")]
1427 Backend::Metal => false,
1428 #[cfg(feature = "gpu")]
1429 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1430 Backend::None => false,
1431 }
1432}
1433
1434pub fn q1t_matmat(
1437 model: &Arc<CmfModel>,
1438 idx: usize,
1439 xs: &[f32],
1440 b: usize,
1441 rows: usize,
1442 cols: usize,
1443 out: &mut [f32],
1444) -> bool {
1445 match backend() {
1446 #[cfg(target_os = "macos")]
1447 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1451 #[cfg(feature = "gpu")]
1452 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1453 Backend::None => false,
1454 }
1455}
1456
1457#[cfg(target_os = "macos")]
1461pub(crate) fn metal_q1t_enabled() -> bool {
1462 std::env::var("CMF_METAL_Q1T")
1463 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1464 .unwrap_or(true)
1465}
1466
1467pub fn q1_matmat(
1469 model: &Arc<CmfModel>,
1470 idx: usize,
1471 xs: &[f32],
1472 b: usize,
1473 rows: usize,
1474 cols: usize,
1475 out: &mut [f32],
1476) -> bool {
1477 match backend() {
1478 #[cfg(feature = "gpu")]
1479 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1480 #[allow(unused_variables)]
1481 _ => false,
1482 }
1483}
1484
1485static MM_KILL: AtomicBool = AtomicBool::new(false);
1490pub(crate) fn mm_killed() -> bool {
1491 MM_KILL.load(Ordering::Relaxed)
1492}
1493pub(crate) fn mm_kill() {
1494 MM_KILL.store(true, Ordering::Relaxed);
1495}
1496
1497static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1504const MM_STRIKES_TO_KILL: u32 = 3;
1505static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1512
1513pub fn mm_kill_arm(on: bool) {
1516 MM_ARMED.store(on, Ordering::Relaxed);
1517 if on {
1518 MM_STRIKES.store(0, Ordering::Relaxed);
1519 }
1520}
1521
1522pub(crate) fn mm_budget_check(
1529 what: &str,
1530 el: std::time::Duration,
1531 budget: std::time::Duration,
1532 exempt: bool,
1533) {
1534 if el <= budget {
1535 MM_STRIKES.store(0, Ordering::Relaxed);
1536 return;
1537 }
1538 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1539 return;
1540 }
1541 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1542 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1543 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1544 if !on {
1545 tracing::info!(
1546 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1547 );
1548 return;
1549 }
1550 if n >= MM_STRIKES_TO_KILL {
1551 tracing::warn!(
1552 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1553 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1554 );
1555 mm_kill();
1556 } else {
1557 tracing::info!(
1558 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1559 );
1560 }
1561}
1562
1563#[allow(unused_variables, clippy::too_many_arguments)]
1568pub fn chunk_attend(
1569 q: &[f32],
1570 k: &[&[f32]],
1571 v: &[&[f32]],
1572 b: usize,
1573 s0: usize,
1574 nh: usize,
1575 nkv: usize,
1576 hd: usize,
1577 scale: f32,
1578 out: &mut [f32],
1579) -> bool {
1580 match backend() {
1581 #[cfg(feature = "gpu")]
1582 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1583 #[allow(unreachable_patterns)]
1584 _ => false,
1585 }
1586}
1587
1588#[allow(unused_variables, clippy::too_many_arguments)]
1592pub fn q4t_qkv(
1593 model: &Arc<CmfModel>,
1594 wq: usize,
1595 wk: usize,
1596 wv: usize,
1597 xs: &[f32],
1598 b: usize,
1599 cols: usize,
1600 rq: usize,
1601 rk: usize,
1602 rv: usize,
1603 out: &mut [f32],
1604) -> bool {
1605 match backend() {
1606 #[cfg(feature = "gpu")]
1607 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1608 #[allow(unreachable_patterns)]
1609 _ => false,
1610 }
1611}
1612
1613#[allow(unused_variables, clippy::too_many_arguments)]
1615#[allow(clippy::too_many_arguments, unused_variables)]
1619pub fn q4tp_ffn_packed(
1620 model: &Arc<CmfModel>,
1621 w1: usize,
1622 w2: usize,
1623 xs: &[f32],
1624 b: usize,
1625 hidden: usize,
1626 inter: usize,
1627 bias: Option<&[f32]>,
1628 out: &mut [f32],
1629) -> bool {
1630 match backend() {
1631 #[cfg(feature = "gpu")]
1632 Backend::Wgpu => {
1633 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1634 }
1635 #[allow(unreachable_patterns)]
1636 _ => false,
1637 }
1638}
1639
1640pub fn q4tp_ffn(
1641 model: &Arc<CmfModel>,
1642 w1: usize,
1643 w3: usize,
1644 w2: usize,
1645 xs: &[f32],
1646 b: usize,
1647 hidden: usize,
1648 inter: usize,
1649 out: &mut [f32],
1650) -> bool {
1651 match backend() {
1652 #[cfg(target_os = "macos")]
1653 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1654 #[cfg(feature = "gpu")]
1655 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1656 #[allow(unreachable_patterns)]
1657 _ => false,
1658 }
1659}
1660
1661pub fn q4t_ffn(
1662 model: &Arc<CmfModel>,
1663 w1: usize,
1664 w3: usize,
1665 w2: usize,
1666 xs: &[f32],
1667 b: usize,
1668 hidden: usize,
1669 inter: usize,
1670 out: &mut [f32],
1671) -> bool {
1672 match backend() {
1673 #[cfg(target_os = "macos")]
1674 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1675 #[cfg(feature = "gpu")]
1676 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1677 #[allow(unreachable_patterns)]
1678 _ => false,
1679 }
1680}
1681
1682pub struct DitBlockArgs<'a> {
1687 pub n: usize,
1688 pub hidden: usize,
1689 pub inter: usize,
1690 pub nh: usize,
1691 pub nkv: usize,
1692 pub hd: usize,
1693 pub eps: f32,
1694 pub rope_cos: &'a [f32],
1695 pub rope_sin: &'a [f32],
1696 pub norm1: &'a [f32],
1697 pub norm2: &'a [f32],
1698 pub ffn_norm1: &'a [f32],
1699 pub ffn_norm2: &'a [f32],
1700 pub norm_q: &'a [f32],
1701 pub norm_k: &'a [f32],
1702 pub s_msa: &'a [f32],
1703 pub gate_msa: &'a [f32],
1704 pub s_mlp: &'a [f32],
1705 pub gate_mlp: &'a [f32],
1706 pub wq: usize,
1707 pub wk: usize,
1708 pub wv: usize,
1709 pub wo: usize,
1710 pub w1: usize,
1711 pub w3: usize,
1712 pub w2: usize,
1713 pub q4tp: bool,
1717 pub resident_in: bool,
1720 pub resident_out: bool,
1724}
1725
1726pub fn dit_chain_supported() -> bool {
1730 #[cfg(feature = "gpu")]
1731 {
1732 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1733 }
1734 #[allow(unreachable_code)]
1735 false
1736}
1737
1738pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1741 #[cfg(feature = "gpu")]
1742 {
1743 if matches!(backend(), Backend::Wgpu) {
1744 return crate::gpu_wgpu::dit_state_fetch(_x);
1745 }
1746 }
1747 false
1748}
1749
1750#[allow(unused_variables)]
1754#[allow(unused_variables, clippy::too_many_arguments)]
1758pub fn dit_qkv(
1759 model: &Arc<CmfModel>,
1760 wq: usize,
1761 wk: usize,
1762 wv: usize,
1763 xs: &[f32],
1764 b: usize,
1765 hidden: usize,
1766 qrows: usize,
1767 kvrows: usize,
1768 q_out: &mut [f32],
1769 k_out: &mut [f32],
1770 v_out: &mut [f32],
1771) -> bool {
1772 match backend() {
1773 #[cfg(feature = "gpu")]
1774 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1775 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1776 ),
1777 #[allow(unreachable_patterns)]
1778 _ => false,
1779 }
1780}
1781
1782pub fn fused_dit_block_available() -> bool {
1786 #[cfg(target_os = "macos")]
1787 {
1788 matches!(backend(), Backend::Metal) && fused_block_trusted()
1789 }
1790 #[cfg(not(target_os = "macos"))]
1791 {
1792 false
1793 }
1794}
1795
1796pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1797 dit_block_seg(model, a, &[a.n], x)
1798}
1799
1800pub fn dit_block_seg(
1804 model: &Arc<CmfModel>,
1805 a: &DitBlockArgs,
1806 segs: &[usize],
1807 x: &mut [f32],
1808) -> bool {
1809 match backend() {
1810 #[cfg(target_os = "macos")]
1811 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1812 #[cfg(feature = "gpu")]
1819 Backend::Wgpu
1820 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1821 Some("0") => false,
1822 Some(_) => true,
1823 None => crate::gpu_wgpu::discrete_active(),
1824 } =>
1825 {
1826 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1827 }
1828 #[allow(unreachable_patterns)]
1829 _ => false,
1830 }
1831}
1832
1833pub struct VaeResnetArgs<'a> {
1837 pub groups: usize,
1838 pub ic: usize,
1839 pub oc: usize,
1840 pub h: usize,
1841 pub w: usize,
1842 pub n1w: &'a [f32],
1843 pub n1b: &'a [f32],
1844 pub c1w: &'a [f32],
1845 pub c1b: &'a [f32],
1846 pub c1k: usize,
1847 pub n2w: &'a [f32],
1848 pub n2b: &'a [f32],
1849 pub c2w: &'a [f32],
1850 pub c2b: &'a [f32],
1851 pub c2k: usize,
1852 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1853}
1854
1855#[allow(unused_variables)]
1858pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1859 match backend() {
1860 #[cfg(target_os = "macos")]
1861 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1862 _ => false,
1863 }
1864}
1865
1866#[allow(unused_variables, clippy::too_many_arguments)]
1869pub fn vae_upsample_conv(
1870 w: &[f32],
1871 bias: &[f32],
1872 x: &[f32],
1873 ic: usize,
1874 oc: usize,
1875 h: usize,
1876 w_img: usize,
1877 k: usize,
1878 out: &mut [f32],
1879) -> bool {
1880 match backend() {
1881 #[cfg(target_os = "macos")]
1882 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1883 #[cfg(feature = "gpu")]
1884 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1885 #[allow(unreachable_patterns)]
1886 _ => false,
1887 }
1888}
1889
1890#[allow(unused_variables, clippy::too_many_arguments)]
1893pub fn vae_conv2d(
1894 w: &[f32],
1895 bias: &[f32],
1896 x: &[f32],
1897 ic: usize,
1898 oc: usize,
1899 h: usize,
1900 w_img: usize,
1901 k: usize,
1902 out: &mut [f32],
1903) -> bool {
1904 match backend() {
1905 #[cfg(target_os = "macos")]
1906 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1907 #[cfg(feature = "gpu")]
1908 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1909 #[allow(unreachable_patterns)]
1910 _ => false,
1911 }
1912}
1913
1914#[allow(unused_variables, clippy::too_many_arguments)]
1918#[allow(unused_variables)]
1922#[allow(clippy::too_many_arguments)]
1923#[allow(clippy::too_many_arguments, unused_variables)]
1926pub fn dit_qkv_attention(
1927 model: &Arc<CmfModel>,
1928 qkv_idx: usize,
1929 xn: &[f32],
1930 n: usize,
1931 hidden: usize,
1932 nh: usize,
1933 hd: usize,
1934 scale: f32,
1935 nr: (&[f32], &[f32], &[f32], f32),
1936 out: &mut [f32],
1937) -> bool {
1938 match backend() {
1939 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1940 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1941 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1942 ),
1943 #[allow(unreachable_patterns)]
1944 _ => false,
1945 }
1946}
1947
1948#[allow(clippy::too_many_arguments)]
1951pub fn dit_qkv_attn_out(
1952 model: &Arc<CmfModel>,
1953 qkv_idx: usize,
1954 out_idx: usize,
1955 xn: &[f32],
1956 n: usize,
1957 hidden: usize,
1958 nh: usize,
1959 hd: usize,
1960 scale: f32,
1961 nr: (&[f32], &[f32], &[f32], f32),
1962 proj: &mut [f32],
1963) -> bool {
1964 match backend() {
1965 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1966 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1967 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1968 ),
1969 #[allow(unreachable_patterns)]
1970 _ => false,
1971 }
1972}
1973
1974#[allow(clippy::too_many_arguments)]
1976pub fn vae_qkv_attn_out(
1977 model: &Arc<CmfModel>,
1978 qkv_idx: usize,
1979 out_idx: usize,
1980 xn: &[f32],
1981 n: usize,
1982 dim: usize,
1983 nh: usize,
1984 hd: usize,
1985 scale: f32,
1986 angles: &[f32],
1987 eps: f32,
1988 qkv_bias: &[f32],
1989 proj: &mut [f32],
1990) -> bool {
1991 match backend() {
1992 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1993 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1994 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1995 ),
1996 #[allow(unreachable_patterns)]
1997 _ => false,
1998 }
1999}
2000
2001#[allow(clippy::too_many_arguments)]
2002pub fn vae_attention_packed(
2003 qkv: &[f32],
2004 nh: usize,
2005 n: usize,
2006 hd: usize,
2007 scale: f32,
2008 angles: &[f32],
2009 eps: f32,
2010 out: &mut [f32],
2011) -> bool {
2012 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
2013}
2014
2015#[allow(clippy::too_many_arguments)]
2016pub fn vae_attention_packed_layout(
2017 qkv: &[f32],
2018 nh: usize,
2019 n: usize,
2020 hd: usize,
2021 scale: f32,
2022 angles: &[f32],
2023 eps: f32,
2024 out: &mut [f32],
2025 layout: u32,
2026) -> bool {
2027 match backend() {
2028 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2029 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
2030 qkv, nh, n, hd, scale, angles, eps, out, layout,
2031 ),
2032 #[allow(unreachable_patterns)]
2033 _ => false,
2034 }
2035}
2036
2037#[allow(clippy::too_many_arguments)]
2038pub fn dit_split_only(
2039 qkv: &[f32],
2040 nh: usize,
2041 n: usize,
2042 hd: usize,
2043 layout: u32,
2044 norm: Option<(&[f32], f32)>,
2045 out_q: &mut [f32],
2046) -> bool {
2047 match backend() {
2048 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2049 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2050 #[allow(unreachable_patterns)]
2051 _ => false,
2052 }
2053}
2054
2055pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2060 match backend() {
2061 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2062 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2063 #[allow(unreachable_patterns)]
2064 _ => false,
2065 }
2066}
2067
2068#[allow(clippy::too_many_arguments)]
2071pub fn music3_ffn(
2072 model: &std::sync::Arc<CmfModel>,
2073 idx_in: usize,
2074 idx_out: usize,
2075 h: &[f32],
2076 bias_in: &[f32],
2077 n: usize,
2078 hs: usize,
2079 inter: usize,
2080 out: &mut [f32],
2081) -> bool {
2082 match backend() {
2083 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2084 Backend::Wgpu => {
2085 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2086 }
2087 #[allow(unreachable_patterns)]
2088 _ => false,
2089 }
2090}
2091
2092#[allow(clippy::too_many_arguments)]
2096pub fn conv1d_gemm(
2097 x: &[f32],
2098 w: &[f32],
2099 ic: usize,
2100 oc: usize,
2101 n: usize,
2102 k: usize,
2103 pad: usize,
2104 dil: usize,
2105 out_n: usize,
2106 yt: &mut [f32],
2107) -> bool {
2108 match backend() {
2109 #[cfg(target_os = "macos")]
2110 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2111 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2112 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2113 #[allow(unreachable_patterns)]
2114 _ => false,
2115 }
2116}
2117
2118#[allow(clippy::too_many_arguments)]
2120pub fn vae_conv2d_coop(
2121 w: &[f32],
2122 bias: Option<&[f32]>,
2123 x: &[f32],
2124 ic: usize,
2125 oc: usize,
2126 h: usize,
2127 wi: usize,
2128 k: usize,
2129 out: &mut [f32],
2130) -> bool {
2131 match backend() {
2132 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2133 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2134 #[allow(unreachable_patterns)]
2135 _ => false,
2136 }
2137}
2138
2139pub fn dit_attention_packed(
2140 qkv: &[f32],
2141 nh: usize,
2142 n: usize,
2143 hd: usize,
2144 scale: f32,
2145 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2148 out: &mut [f32],
2149) -> bool {
2150 match backend() {
2151 #[cfg(feature = "gpu")]
2158 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2159 #[allow(unreachable_patterns)]
2160 _ => false,
2161 }
2162}
2163
2164pub fn dit_attention_packed_available() -> bool {
2172 #[allow(unreachable_patterns)]
2173 match backend() {
2174 #[cfg(feature = "gpu")]
2175 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2176 _ => false,
2177 }
2178}
2179
2180pub fn dit_attention(
2181 qh: &[f32],
2182 kh: &[f32],
2183 vh: &[f32],
2184 nh: usize,
2185 nkv: usize,
2186 n: usize,
2187 hd: usize,
2188 scale: f32,
2189 out: &mut [f32],
2190) -> bool {
2191 match backend() {
2192 #[cfg(target_os = "macos")]
2193 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2194 #[cfg(feature = "gpu")]
2195 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2196 #[allow(unreachable_patterns)]
2197 _ => false,
2198 }
2199}
2200
2201#[allow(unused_variables)]
2206pub fn q4tp_matmat(
2207 model: &Arc<CmfModel>,
2208 idx: usize,
2209 xs: &[f32],
2210 b: usize,
2211 rows: usize,
2212 cols: usize,
2213 out: &mut [f32],
2214) -> bool {
2215 match backend() {
2216 #[cfg(target_os = "macos")]
2217 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2218 #[cfg(feature = "gpu")]
2219 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2220 #[allow(unreachable_patterns)]
2221 _ => false,
2222 }
2223}
2224
2225pub fn q2tp_matmat(
2228 model: &Arc<CmfModel>,
2229 idx: usize,
2230 xs: &[f32],
2231 b: usize,
2232 rows: usize,
2233 cols: usize,
2234 out: &mut [f32],
2235) -> bool {
2236 match backend() {
2237 #[cfg(feature = "gpu")]
2238 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2239 #[allow(unreachable_patterns)]
2240 _ => false,
2241 }
2242}
2243
2244pub fn q4tp_matvec(
2249 model: &Arc<CmfModel>,
2250 idx: usize,
2251 xs: &[f32],
2252 rows: usize,
2253 cols: usize,
2254 out: &mut [f32],
2255) -> bool {
2256 match backend() {
2257 #[cfg(target_os = "macos")]
2258 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2259 #[cfg(feature = "gpu")]
2260 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2261 #[allow(unreachable_patterns)]
2262 _ => false,
2263 }
2264}
2265
2266pub fn q4t_matvec(
2272 model: &Arc<CmfModel>,
2273 idx: usize,
2274 xs: &[f32],
2275 rows: usize,
2276 cols: usize,
2277 out: &mut [f32],
2278) -> bool {
2279 match backend() {
2280 #[cfg(target_os = "macos")]
2281 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2282 #[allow(unreachable_patterns)]
2283 _ => false,
2284 }
2285}
2286
2287pub fn q4t_matmat(
2288 model: &Arc<CmfModel>,
2289 idx: usize,
2290 xs: &[f32],
2291 b: usize,
2292 rows: usize,
2293 cols: usize,
2294 out: &mut [f32],
2295) -> bool {
2296 match backend() {
2297 #[cfg(target_os = "macos")]
2298 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2299 #[cfg(feature = "gpu")]
2300 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2301 #[allow(unreachable_patterns)]
2302 _ => false,
2303 }
2304}
2305
2306#[cfg(target_os = "macos")]
2308pub use crate::gpu_metal::{
2309 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2310 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2311};
2312
2313#[cfg(target_os = "macos")]
2315pub fn gdn_block(
2316 model: &Arc<CmfModel>,
2317 layers: &[GdnGpuLayer],
2318 states: &mut [&mut [f32]],
2319 cfg: &GdnGpuCfg,
2320 h: &mut [f32],
2321) -> bool {
2322 match backend() {
2323 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2324 _ => false,
2325 }
2326}
2327
2328#[allow(unused_variables)]
2330pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2331 match backend() {
2332 #[cfg(target_os = "macos")]
2333 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2334 #[cfg(feature = "gpu")]
2335 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2336 Backend::None => false,
2337 }
2338}
2339
2340#[allow(unused_variables)]
2342pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2343 match backend() {
2344 #[cfg(target_os = "macos")]
2345 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2346 #[cfg(feature = "gpu")]
2347 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2348 Backend::None => false,
2349 }
2350}
2351
2352static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2368static 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)];
2372
2373const GRAPH_RACE_SAMPLES: u32 = 4;
2375
2376static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2386
2387pub fn graph_mark_unsupported() {
2392 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2393 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2394 }
2395}
2396
2397pub fn graph_unsupported() -> bool {
2398 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2399}
2400
2401pub fn graph_unsupported_reset() {
2403 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2404}
2405
2406pub fn graph_race_begin_generation() {
2407 #[cfg(feature = "gpu")]
2412 {
2413 static FLUSHED: std::sync::Once = std::sync::Once::new();
2425 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2426 if FIRST.swap(false, Ordering::Relaxed) {
2427 } else {
2429 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2430 }
2431 }
2432 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2433 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2434 return;
2435 }
2436 let (gn, cn) = (
2437 GRAPH_N[1].load(Ordering::Relaxed),
2438 GRAPH_N[0].load(Ordering::Relaxed),
2439 );
2440 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2441 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2442 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2443 let verdict = if g_avg < c_avg { 1 } else { 2 };
2444 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2445 tracing::info!(
2446 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2447 g_avg as f64 / 1e6,
2448 c_avg as f64 / 1e6,
2449 if verdict == 1 { "graph" } else { "normal path" }
2450 );
2451 return;
2452 }
2453 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2454 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2455}
2456
2457pub fn graph_race_use_graph(trusted: bool) -> bool {
2461 if trusted {
2462 return true;
2463 }
2464 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2465 1 => true,
2466 2 => false,
2467 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2468 }
2469}
2470
2471pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2476 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2477 return false;
2478 }
2479 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2480 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2481 if !first || cn == 0 {
2482 return false;
2483 }
2484 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2485 let ns = dur.as_nanos() as u64;
2486 if ns > 1_000_000_000 && ns > 4 * c_avg {
2487 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2488 tracing::info!(
2489 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2490 ns as f64 / 1e6,
2491 c_avg as f64 / 1e6
2492 );
2493 return true;
2494 }
2495 false
2496}
2497
2498pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2502 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2503 return;
2504 }
2505 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2506 if tok == 0 {
2507 return;
2508 }
2509 let i = used_graph as usize;
2510 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2511 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2512}
2513
2514pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2524 #[inline]
2525 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2526 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2527 for c in chunks.chunks_exact(8) {
2528 h ^= u64::from_le_bytes(c.try_into().unwrap());
2529 h = h.wrapping_mul(0x100_0000_01b3);
2530 }
2531 for &b in tail {
2532 h ^= b as u64;
2533 h = h.wrapping_mul(0x100_0000_01b3);
2534 }
2535 h
2536 }
2537 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2538 if data.len() <= 4096 {
2539 return fnv(h, data);
2540 }
2541 let step = (data.len() - 64) / 63;
2542 for i in 0..64 {
2543 h = fnv(h, &data[i * step..i * step + 64]);
2544 }
2545 h
2546}
2547
2548pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2551 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2552 fp_bytes(bytes)
2553}
2554
2555#[cfg(test)]
2556mod fp_tests {
2557 use super::fp_bytes;
2558
2559 #[test]
2564 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2565 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2567 let h0 = fp_bytes(&base);
2568 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2569 let mut dense = base.clone();
2572 for b in dense.iter_mut() {
2573 *b = b.wrapping_add(1);
2574 }
2575 assert_ne!(
2576 h0,
2577 fp_bytes(&dense),
2578 "a fully different tensor slipped through"
2579 );
2580 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2583 let mut small = vec![3u8; 4096];
2586 let hs = fp_bytes(&small);
2587 small[2048] ^= 1;
2588 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2589 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2591 let v = vec![9u8; n];
2592 let _ = fp_bytes(&v); }
2594 }
2595}
2596
2597pub fn bake_release() {
2601 #[cfg(feature = "gpu")]
2602 crate::gpu_wgpu::bake_release();
2603}
2604
2605pub fn bake_precision_strict(on: bool) {
2609 #[cfg(feature = "gpu")]
2610 crate::gpu_wgpu::bake_precision_strict(on);
2611 #[cfg(not(feature = "gpu"))]
2612 let _ = on;
2613}
2614
2615pub fn hostprof_encode_done(t0: std::time::Instant) {
2621 use std::sync::atomic::{AtomicU64, Ordering};
2622 static ENC: AtomicU64 = AtomicU64::new(0);
2623 static N: AtomicU64 = AtomicU64::new(0);
2624 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2625 return;
2626 }
2627 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2628 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2629 if n % 100 == 0 {
2630 eprintln!(
2631 "hostprof: encode {:.2} ms/token over {n} tokens",
2632 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2633 );
2634 }
2635}
2636
2637pub fn hostprof_total(t0: std::time::Instant) {
2638 use std::sync::atomic::{AtomicU64, Ordering};
2639 static TOT: AtomicU64 = AtomicU64::new(0);
2640 static N: AtomicU64 = AtomicU64::new(0);
2641 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2642 return;
2643 }
2644 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2645 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2646 if n % 100 == 0 {
2647 eprintln!(
2648 "hostprof: total {:.2} ms/token over {n} tokens",
2649 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2650 );
2651 }
2652}
2653
2654pub fn stageprof(stage: u32, dt: std::time::Duration) {
2658 use std::sync::atomic::{AtomicU64, Ordering};
2659 static NS: [AtomicU64; 4] = [
2660 AtomicU64::new(0),
2661 AtomicU64::new(0),
2662 AtomicU64::new(0),
2663 AtomicU64::new(0),
2664 ];
2665 static N: AtomicU64 = AtomicU64::new(0);
2666 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2667 return;
2668 }
2669 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2670 if stage == 1 {
2671 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2672 if n % 200 == 0 {
2673 eprintln!(
2674 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2675 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2676 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2677 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2678 );
2679 }
2680 }
2681}
2682
2683pub fn weight_bytes_dispatched() -> u64 {
2686 let mut total = 0u64;
2687 #[cfg(target_os = "macos")]
2688 {
2689 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2690 }
2691 #[cfg(feature = "gpu")]
2692 {
2693 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2694 }
2695 total
2696}
2697
2698pub fn weight_bytes_by() -> [u64; 6] {
2701 #[cfg(target_os = "macos")]
2702 {
2703 let mut o = [0u64; 6];
2704 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2705 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2706 }
2707 return o;
2708 }
2709 #[allow(unreachable_code)]
2710 [0; 6]
2711}