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
379fn probe_on() -> bool {
380 static ON: OnceLock<bool> = OnceLock::new();
381 *ON.get_or_init(|| {
382 std::env::var("CMF_GPU_PROBE")
383 .map(|v| v != "0" && v != "off")
384 .unwrap_or(true)
385 })
386}
387
388pub fn q1_force() -> bool {
393 #[cfg(target_os = "macos")]
394 {
395 backend() == Backend::Metal
396 }
397 #[cfg(not(target_os = "macos"))]
398 {
399 false
400 }
401}
402
403pub fn fused_block_trusted() -> bool {
422 #[cfg(target_os = "macos")]
423 if backend() == Backend::Metal {
424 return true;
425 }
426 wgpu_graph_default()
427}
428
429pub fn weight_is_resident(model: &Arc<CmfModel>, idx: usize) -> bool {
441 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
442 {
443 return crate::gpu_wgpu::weight_is_resident(model, idx);
444 }
445 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
446 {
447 let _ = (model, idx);
448 true
449 }
450}
451
452pub fn probe_arm_cold_prefers_gpu(c: OpClass, weights_resident: bool) -> ProbeArm {
453 if !weights_resident && probe_deciding(c) {
454 return ProbeArm::Gpu;
455 }
456 probe_arm(c)
457}
458
459pub fn probe_arm(c: OpClass) -> ProbeArm {
460 PROBE_COLD.with(|f| f.set(false));
465 if !probe_on() {
466 return ProbeArm::Gpu;
467 }
468 probe_cache_load();
469 let p = &PROBES[c as usize];
470 match p.state.load(Ordering::Relaxed) {
471 1 => ProbeArm::Gpu,
472 2 => ProbeArm::Cpu,
473 _ => {
474 if p.flip.fetch_add(1, Ordering::Relaxed) % 2 == 0 {
475 ProbeArm::Gpu
476 } else {
477 ProbeArm::CpuTimed
478 }
479 }
480 }
481}
482
483pub fn probe_record(c: OpClass, gpu: bool, dur: std::time::Duration) {
486 let p = &PROBES[c as usize];
487 if p.state.load(Ordering::Relaxed) != 0 {
488 return;
489 }
490 if gpu && PROBE_COLD.with(|f| f.replace(false)) {
491 return; }
493 let ns = dur.as_nanos().min(u64::MAX as u128) as u64;
494 if gpu {
495 p.gpu_ns.fetch_add(ns, Ordering::Relaxed);
496 p.gpu_n.fetch_add(1, Ordering::Relaxed);
497 p.gpu_min.fetch_min(ns, Ordering::Relaxed);
498 } else {
499 p.cpu_ns.fetch_add(ns, Ordering::Relaxed);
500 p.cpu_n.fetch_add(1, Ordering::Relaxed);
501 p.cpu_min.fetch_min(ns, Ordering::Relaxed);
502 }
503 let (gn, cn) = (
504 p.gpu_n.load(Ordering::Relaxed),
505 p.cpu_n.load(Ordering::Relaxed),
506 );
507 if gn >= 2 && cn >= 2 {
508 let g = p.gpu_min.load(Ordering::Relaxed) as f64;
512 let cp = p.cpu_min.load(Ordering::Relaxed) as f64;
513 if (gn < PROBE_SAMPLES || cn < PROBE_SAMPLES) && g < cp * 2.0 && cp < g * 2.0 {
523 return;
524 }
525 let winner = if g <= cp { 1 } else { 2 };
526 if p.state
527 .compare_exchange(0, winner, Ordering::Relaxed, Ordering::Relaxed)
528 .is_ok()
529 {
530 tracing::info!(
531 "gpu probe [{}]: gpu {:.2} ms vs cpu {:.2} ms per op → {}",
532 CLASS_NAMES[c as usize],
533 g / 1e6,
534 cp / 1e6,
535 if winner == 1 { "gpu" } else { "cpu" },
536 );
537 probe_cache_store(c, winner);
538 }
539 }
540}
541
542pub fn probe_deciding(c: OpClass) -> bool {
545 probe_on() && PROBES[c as usize].state.load(Ordering::Relaxed) == 0
546}
547
548#[allow(unused_variables)]
558pub fn q8_resident_or_upload(model: &Arc<CmfModel>, idx: usize) -> bool {
559 static PROBE_UPLOADS: AtomicU32 = AtomicU32::new(0);
560 let may_upload = PROBE_UPLOADS.load(Ordering::Relaxed) < 4;
561 let resident = match backend() {
562 #[cfg(target_os = "macos")]
563 Backend::Metal => crate::gpu_metal::q8_resident_or_upload(model, idx, may_upload),
564 #[cfg(feature = "gpu")]
565 Backend::Wgpu => crate::gpu_wgpu::q8_resident_or_upload(model, idx, may_upload),
566 Backend::None => false,
567 };
568 if !resident && may_upload {
569 PROBE_UPLOADS.fetch_add(1, Ordering::Relaxed);
570 }
571 resident
572}
573
574#[cfg(test)]
576pub(crate) fn probe_reset() {
577 for p in &PROBES {
578 p.state.store(0, Ordering::Relaxed);
579 p.flip.store(0, Ordering::Relaxed);
580 p.gpu_ns.store(0, Ordering::Relaxed);
581 p.gpu_n.store(0, Ordering::Relaxed);
582 p.cpu_ns.store(0, Ordering::Relaxed);
583 p.cpu_n.store(0, Ordering::Relaxed);
584 }
585}
586
587#[cfg(test)]
588mod probe_tests {
589 use super::*;
590 use std::time::Duration;
591
592 #[test]
595 fn probe_alternates_discards_cold_and_decides() {
596 probe_reset();
597 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
599 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::CpuTimed));
600
601 probe_note_cold();
605 probe_record(OpClass::Ffn, true, Duration::from_secs(1000));
606 for _ in 0..PROBE_SAMPLES {
607 probe_record(OpClass::Ffn, true, Duration::from_millis(1));
608 probe_record(OpClass::Ffn, false, Duration::from_millis(4));
609 }
610 assert!(matches!(probe_arm(OpClass::Ffn), ProbeArm::Gpu));
611
612 for _ in 0..PROBE_SAMPLES {
614 probe_record(OpClass::Matmat, true, Duration::from_millis(4));
615 probe_record(OpClass::Matmat, false, Duration::from_millis(1));
616 }
617 assert!(matches!(probe_arm(OpClass::Matmat), ProbeArm::Cpu));
618
619 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
621 CPU_ONLY.with(|c| assert!(!c.get()));
622 cpu_scope(|| {
623 cpu_scope(|| CPU_ONLY.with(|c| assert!(c.get())));
624 CPU_ONLY.with(|c| assert!(c.get()));
625 });
626 let _ = std::panic::catch_unwind(|| cpu_scope(|| panic!("scope test")));
627 CPU_ONLY.with(|c| assert!(!c.get()));
628 probe_reset();
629 }
630
631 #[test]
632 fn a_remembered_verdict_is_adopted_and_a_stranger_is_not() {
633 let mine = probe_cache_key_named("gemm-nt");
645 let state = || {
646 PROBES[OpClass::GemmNt as usize]
647 .state
648 .load(Ordering::Relaxed)
649 };
650
651 probe_cache_adopt("SomeOtherGPU/Vulkan\tgemm-nt\tgpu\n");
653 assert_eq!(state(), 0);
654 let older = mine.replacen(env!("CARGO_PKG_VERSION"), "0.0.0-old", 1);
656 assert_ne!(older, mine);
657 probe_cache_adopt(&format!("{older}\tgpu\n"));
658 assert_eq!(state(), 0);
659 probe_cache_adopt(&format!("{mine}\tcpu\n"));
661 assert_eq!(state(), 2);
662
663 PROBES[OpClass::GemmNt as usize]
664 .state
665 .store(0, Ordering::Relaxed);
666 }
667}
668
669pub const GPU_MIN_ROWS: usize = 65_536;
672
673pub fn min_rows() -> usize {
680 if let Some(v) = std::env::var("CMF_GPU_MIN_ROWS")
681 .ok()
682 .and_then(|v| v.parse().ok())
683 {
684 return v;
685 }
686 if discrete() { 4096 } else { GPU_MIN_ROWS }
687}
688
689pub fn discrete() -> bool {
691 match backend() {
692 #[cfg(feature = "gpu")]
693 Backend::Wgpu => crate::gpu_wgpu::is_discrete(),
694 #[cfg(target_os = "macos")]
695 Backend::Metal => false, Backend::None => false,
697 }
698}
699
700pub struct MoeJob<'a> {
704 pub gate: (usize, usize, usize, &'a [f32]),
705 pub up: (usize, usize, usize, &'a [f32]),
706 pub down: (usize, usize, usize, &'a [f32]),
707 pub xs_gate: Vec<f32>,
708 pub xs_up: Vec<f32>,
709 pub down_col: &'a [f32],
710 pub w: f32,
711 pub q1: bool,
714 pub q4t: bool,
717 pub q4tp: bool,
721 pub gu_q2: bool,
725 pub swiglu_limit: f32,
730}
731
732pub struct BatchJob<'a> {
734 pub idx: usize,
735 pub rows: usize,
736 pub cols: usize,
737 pub row_scale: &'a [f32],
738 pub xs: Vec<f32>,
739 pub layout: BatchLayout,
743}
744
745#[derive(Clone, Copy, PartialEq, Eq, Debug)]
748pub enum BatchLayout {
749 Q8,
750 Q1,
751 Q4t,
752 Q4tp,
753}
754
755#[derive(Clone, Copy, PartialEq, Eq)]
756enum Backend {
757 None,
758 #[cfg(target_os = "macos")]
759 Metal,
760 #[cfg(feature = "gpu")]
761 Wgpu,
762}
763
764fn backend() -> Backend {
765 #[cfg(feature = "gpu")]
766 if crate::gpu_wgpu::selected() {
767 return if crate::gpu_wgpu::enabled() {
768 Backend::Wgpu
769 } else {
770 Backend::None
771 };
772 }
773 #[cfg(target_os = "macos")]
774 if crate::gpu_metal::enabled() {
775 return Backend::Metal;
776 }
777 Backend::None
778}
779
780pub fn backend_available() -> bool {
786 #[cfg(target_os = "macos")]
787 {
788 true
790 }
791 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
792 {
793 static AVAIL: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
794 *AVAIL.get_or_init(crate::gpu_wgpu::adapter_probe)
795 }
796 #[cfg(all(not(feature = "gpu"), not(target_os = "macos")))]
797 {
798 false
799 }
800}
801
802static GPU_PAUSED: AtomicBool = AtomicBool::new(false);
808
809pub fn pause_gpu() -> GpuPause {
811 GPU_PAUSED.store(true, Ordering::Relaxed);
812 GpuPause(())
813}
814
815pub struct GpuPause(());
816
817impl Drop for GpuPause {
818 fn drop(&mut self) {
819 GPU_PAUSED.store(false, Ordering::Relaxed);
820 }
821}
822
823pub fn enabled() -> bool {
824 !GPU_PAUSED.load(Ordering::Relaxed) && backend() != Backend::None
825}
826
827pub fn wgpu_active() -> bool {
841 #[cfg(feature = "gpu")]
842 {
843 matches!(backend(), Backend::Wgpu)
844 }
845 #[cfg(not(feature = "gpu"))]
846 {
847 false
848 }
849}
850
851pub fn default_device() -> usize {
858 static D: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
859 *D.get_or_init(|| {
860 std::env::var("CMF_GPU_ADAPTER")
861 .ok()
862 .and_then(|v| v.trim().parse::<usize>().ok())
863 .unwrap_or(0)
864 })
865}
866
867thread_local! {
868 static CUR_DEV: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
869}
870
871pub fn current_device() -> usize {
873 CUR_DEV.with(|c| c.get()).unwrap_or_else(default_device)
874}
875
876pub fn set_current_device(i: usize) {
880 CUR_DEV.with(|c| c.set(Some(i)));
881}
882
883pub fn with_device<R>(dev: usize, f: impl FnOnce() -> R) -> R {
885 let prev = CUR_DEV.with(|c| c.replace(Some(dev)));
886 let r = f();
887 CUR_DEV.with(|c| c.set(prev));
888 r
889}
890
891pub fn device_count() -> usize {
894 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
895 {
896 return crate::gpu_wgpu::adapter_count();
897 }
898 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
899 {
900 usize::from(backend_available())
901 }
902}
903
904pub fn vram_budget() -> u64 {
908 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
909 {
910 return crate::gpu_wgpu::device_vram_budget();
911 }
912 #[cfg(not(all(feature = "gpu", not(target_os = "macos"))))]
913 {
914 if backend_available() { u64::MAX } else { 0 }
915 }
916}
917
918pub fn upload_bytes() -> u64 {
922 #[cfg(feature = "gpu")]
923 {
924 return crate::gpu_wgpu::UPLOAD_BYTES.load(std::sync::atomic::Ordering::Relaxed);
925 }
926 #[cfg(not(feature = "gpu"))]
927 0
928}
929
930#[derive(Clone, Copy, PartialEq, Eq, Debug)]
942pub enum GraphPhase {
943 Prefill,
944 Decode,
945}
946
947pub fn wgpu_graph_on(phase: GraphPhase) -> bool {
955 match std::env::var("CMF_GPU_WGPU_GRAPH").ok().as_deref() {
956 Some("0") => false,
957 Some("prefill") => phase == GraphPhase::Prefill,
958 Some(_) => true,
959 None => {
960 if wgpu_graph_default() {
961 return true;
962 }
963 let _ = phase;
968 false
969 }
970 }
971}
972
973pub fn wgpu_graph_default() -> bool {
974 #[cfg(feature = "gpu")]
975 {
976 matches!(backend(), Backend::Wgpu)
982 && (crate::gpu_wgpu::discrete_active()
983 || (cfg!(target_os = "macos") && crate::gpu_wgpu::adapter_up()))
984 }
985 #[cfg(not(feature = "gpu"))]
986 {
987 false
988 }
989}
990
991#[allow(clippy::too_many_arguments, unused_variables)]
993pub fn q8_matvec_range(
994 model: &Arc<CmfModel>,
995 idx: usize,
996 row0: usize,
997 row_scale: &[f32],
998 xs: &[f32],
999 rows: usize,
1000 cols: usize,
1001 out: &mut [f32],
1002) -> bool {
1003 match backend() {
1004 #[cfg(target_os = "macos")]
1005 Backend::Metal => {
1006 crate::gpu_metal::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1007 }
1008 #[cfg(feature = "gpu")]
1009 Backend::Wgpu => {
1010 crate::gpu_wgpu::q8_matvec_range(model, idx, row0, row_scale, xs, rows, cols, out)
1011 }
1012 Backend::None => false,
1013 }
1014}
1015
1016#[allow(clippy::too_many_arguments, unused_variables)]
1019pub fn q8_matmat(
1020 model: &Arc<CmfModel>,
1021 idx: usize,
1022 row_scale: &[f32],
1023 pre: &[f32],
1024 b: usize,
1025 rows: usize,
1026 cols: usize,
1027 out: &mut [f32],
1028) -> bool {
1029 match backend() {
1030 #[cfg(target_os = "macos")]
1031 Backend::Metal => {
1032 crate::gpu_metal::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out)
1033 }
1034 #[cfg(feature = "gpu")]
1035 Backend::Wgpu => crate::gpu_wgpu::q8_matmat(model, idx, row_scale, pre, b, rows, cols, out),
1036 Backend::None => false,
1037 }
1038}
1039
1040#[allow(unused_variables)]
1043pub fn q1_matvec(
1044 model: &Arc<CmfModel>,
1045 idx: usize,
1046 xs: &[f32],
1047 rows: usize,
1048 cols: usize,
1049 out: &mut [f32],
1050) -> bool {
1051 match backend() {
1052 #[cfg(target_os = "macos")]
1053 Backend::Metal => crate::gpu_metal::q1_matvec(model, idx, xs, rows, cols, out),
1054 #[cfg(feature = "gpu")]
1055 Backend::Wgpu => crate::gpu_wgpu::q1_matvec(model, idx, xs, rows, cols, out),
1056 Backend::None => false,
1057 }
1058}
1059
1060#[allow(clippy::too_many_arguments)]
1064pub fn attn_dropin(
1065 model: &Arc<CmfModel>,
1066 kv_id: u64,
1067 layer: usize,
1068 normed: &[f32],
1069 wq_idx: usize,
1070 wk_idx: usize,
1071 wv_idx: usize,
1072 wo_idx: usize,
1073 q_norm: Option<&[f32]>,
1074 k_norm: Option<&[f32]>,
1075 invf: &[f32],
1076 nh: usize,
1077 nkv: usize,
1078 hd: usize,
1079 rd: usize,
1080 hidden: usize,
1081 pos: usize,
1082 cap: usize,
1083 gemma: bool,
1084 eps: f32,
1085 cpu_k: &[Vec<f32>],
1086 cpu_v: &[Vec<f32>],
1087 out: &mut [f32],
1088) -> bool {
1089 match backend() {
1090 #[cfg(feature = "gpu")]
1091 Backend::Wgpu => crate::gpu_wgpu::attn_dropin_gpu(
1092 model, kv_id, layer, normed, wq_idx, wk_idx, wv_idx, wo_idx, q_norm, k_norm, invf, nh,
1093 nkv, hd, rd, hidden, pos, cap, gemma, eps, cpu_k, cpu_v, out,
1094 ),
1095 #[allow(unused_variables)]
1096 _ => false,
1097 }
1098}
1099
1100pub struct GraphW<'a> {
1104 pub idx: usize,
1105 pub kind: u8,
1106 pub row_scale: &'a [f32],
1107 pub data: &'a [f32],
1108}
1109
1110pub enum GraphAttn<'a> {
1113 Full {
1114 wq: GraphW<'a>,
1115 wk: GraphW<'a>,
1116 wv: GraphW<'a>,
1117 wo: GraphW<'a>,
1118 q_norm: Option<&'a [f32]>,
1119 k_norm: Option<&'a [f32]>,
1120 bias: Option<(&'a [f32], &'a [f32], &'a [f32])>,
1122 output_gate: bool,
1125 cpu_k: &'a [Vec<f32>],
1126 cpu_v: &'a [Vec<f32>],
1127 },
1128 Gdn {
1129 qkv: GraphW<'a>,
1130 z: GraphW<'a>,
1131 a: GraphW<'a>,
1132 b: GraphW<'a>,
1133 out: GraphW<'a>,
1134 conv1d: &'a [f32],
1135 a_log: &'a [f32],
1136 dt_bias: &'a [f32],
1137 norm: &'a [f32],
1138 nv: usize,
1139 nk: usize,
1140 dk: usize,
1141 dv: usize,
1142 kk: usize,
1143 cpu_state: &'a [f32],
1148 },
1149}
1150
1151pub struct GraphLayer<'a> {
1153 pub input_norm: &'a [f32],
1154 pub attn: GraphAttn<'a>,
1155 pub post_norm: &'a [f32],
1156 pub ffn: GraphFfn<'a>,
1157}
1158
1159pub enum GraphFfn<'a> {
1164 Dense {
1165 gate: GraphW<'a>,
1166 up: GraphW<'a>,
1167 down: GraphW<'a>,
1168 },
1169 Moe {
1170 router: GraphW<'a>,
1172 shared_gate: GraphW<'a>,
1174 experts: Vec<(usize, usize, usize)>,
1178 n_exp: usize,
1180 top_k: usize,
1181 inter: usize,
1182 norm_topk: bool,
1183 q4tp: bool,
1189 gu_q2: bool,
1193 },
1194}
1195
1196#[allow(clippy::too_many_arguments)]
1201pub fn forward_token_graph(
1202 model: &Arc<CmfModel>,
1203 kv_id: u64,
1204 layers: &[GraphLayer],
1205 o1: &[Option<Vec<crate::nystrom::O1DeviceView<'_>>>],
1208 o1_epoch: u64,
1209 invf: &[f32],
1210 h: &mut [f32],
1211 nh: usize,
1212 nkv: usize,
1213 hd: usize,
1214 rd: usize,
1215 hidden: usize,
1216 inter: usize,
1217 position: usize,
1218 cap: usize,
1219 gemma: bool,
1220 eps: f32,
1221 lm_head: Option<(&GraphW, usize)>,
1222 final_norm: &[f32],
1223 logits: &mut Vec<f32>,
1224 loop_norm_at: &[usize],
1225 steps: usize,
1226 embed: Option<(&GraphW, usize, f32)>,
1227 ids_out: Option<&mut Vec<u32>>,
1228 layers_run: Option<&mut usize>,
1231 layer_base: usize,
1235 hidden_too: bool,
1237) -> bool {
1238 match backend() {
1239 #[cfg(feature = "gpu")]
1240 Backend::Wgpu => crate::gpu_wgpu::forward_token_graph(
1241 model,
1242 kv_id,
1243 layers,
1244 o1,
1245 o1_epoch,
1246 invf,
1247 h,
1248 nh,
1249 nkv,
1250 hd,
1251 rd,
1252 hidden,
1253 inter,
1254 position,
1255 cap,
1256 gemma,
1257 eps,
1258 lm_head,
1259 final_norm,
1260 logits,
1261 loop_norm_at,
1262 steps,
1263 embed,
1264 ids_out,
1265 layers_run,
1266 layer_base,
1267 hidden_too,
1268 ),
1269 #[allow(unused_variables)]
1270 _ => {
1271 let _ = (
1272 lm_head,
1273 final_norm,
1274 logits,
1275 loop_norm_at,
1276 layers_run,
1277 layer_base,
1278 hidden_too,
1279 );
1280 false
1281 }
1282 }
1283}
1284
1285pub struct SpecTail<'a> {
1289 pub lm: GraphW<'a>,
1290 pub lm_rows: usize,
1291 pub final_norm: &'a [f32],
1292 pub logits_out: &'a mut Vec<f32>,
1293}
1294
1295#[allow(clippy::too_many_arguments)]
1299pub fn forward_batch_graph(
1300 model: &Arc<CmfModel>,
1301 kv_id: u64,
1302 layers: &[GraphLayer],
1303 invf: &[f32],
1304 h: &mut [f32],
1305 nh: usize,
1306 nkv: usize,
1307 hd: usize,
1308 rd: usize,
1309 hidden: usize,
1310 inter: usize,
1311 positions: &[usize],
1312 cap: usize,
1313 gemma: bool,
1314 eps: f32,
1315 k: usize,
1316 spec: Option<SpecTail<'_>>,
1317) -> bool {
1318 match backend() {
1319 #[cfg(feature = "gpu")]
1320 Backend::Wgpu => crate::gpu_wgpu::forward_batch_graph(
1321 model, kv_id, layers, invf, h, nh, nkv, hd, rd, hidden, inter, positions, cap, gemma,
1322 eps, k, spec,
1323 ),
1324 #[allow(unreachable_patterns)]
1325 _ => {
1326 let _ = spec;
1327 false
1328 }
1329 }
1330}
1331
1332pub fn gdn_spec_restore(kv_id: u64, slot: usize) -> bool {
1335 #[cfg(feature = "gpu")]
1336 if backend() == Backend::Wgpu {
1337 return crate::gpu_wgpu::gdn_spec_restore(kv_id, slot);
1338 }
1339 #[allow(unreachable_code)]
1340 {
1341 let _ = (kv_id, slot);
1342 false
1343 }
1344}
1345
1346pub fn graph_kv_reset(_kv_id: u64) {
1348 #[cfg(feature = "gpu")]
1349 if backend() == Backend::Wgpu {
1350 crate::gpu_wgpu::kv_mirror_reset(_kv_id);
1351 }
1352}
1353
1354pub fn q1t_matvec(
1358 model: &Arc<CmfModel>,
1359 idx: usize,
1360 xs: &[f32],
1361 rows: usize,
1362 cols: usize,
1363 out: &mut [f32],
1364) -> bool {
1365 match backend() {
1366 #[cfg(target_os = "macos")]
1367 Backend::Metal => {
1368 if metal_q1t_enabled() {
1369 crate::gpu_metal::q1t_matvec(model, idx, xs, rows, cols, out)
1370 } else {
1371 false
1372 }
1373 }
1374 #[cfg(feature = "gpu")]
1375 Backend::Wgpu => crate::gpu_wgpu::q1t_matvec(model, idx, xs, rows, cols, out),
1376 Backend::None => false,
1377 }
1378}
1379
1380#[allow(unused_variables)]
1383pub fn q4b_matvec(
1384 model: &Arc<CmfModel>,
1385 idx: usize,
1386 xs: &[f32],
1387 rows: usize,
1388 cols: usize,
1389 out: &mut [f32],
1390) -> bool {
1391 match backend() {
1392 #[cfg(target_os = "macos")]
1393 Backend::Metal => false,
1394 #[cfg(feature = "gpu")]
1395 Backend::Wgpu => crate::gpu_wgpu::q4b_matvec(model, idx, xs, rows, cols, out),
1396 Backend::None => false,
1397 }
1398}
1399
1400pub fn q1t_matmat(
1403 model: &Arc<CmfModel>,
1404 idx: usize,
1405 xs: &[f32],
1406 b: usize,
1407 rows: usize,
1408 cols: usize,
1409 out: &mut [f32],
1410) -> bool {
1411 match backend() {
1412 #[cfg(target_os = "macos")]
1413 Backend::Metal => crate::gpu_metal::q1t_matmat(model, idx, xs, b, rows, cols, out),
1417 #[cfg(feature = "gpu")]
1418 Backend::Wgpu => crate::gpu_wgpu::q1t_matmat(model, idx, xs, b, rows, cols, out),
1419 Backend::None => false,
1420 }
1421}
1422
1423#[cfg(target_os = "macos")]
1427pub(crate) fn metal_q1t_enabled() -> bool {
1428 std::env::var("CMF_METAL_Q1T")
1429 .map(|v| v != "0" && !v.eq_ignore_ascii_case("off"))
1430 .unwrap_or(true)
1431}
1432
1433pub fn q1_matmat(
1435 model: &Arc<CmfModel>,
1436 idx: usize,
1437 xs: &[f32],
1438 b: usize,
1439 rows: usize,
1440 cols: usize,
1441 out: &mut [f32],
1442) -> bool {
1443 match backend() {
1444 #[cfg(feature = "gpu")]
1445 Backend::Wgpu => crate::gpu_wgpu::q1_matmat(model, idx, xs, b, rows, cols, out),
1446 #[allow(unused_variables)]
1447 _ => false,
1448 }
1449}
1450
1451static MM_KILL: AtomicBool = AtomicBool::new(false);
1456pub(crate) fn mm_killed() -> bool {
1457 MM_KILL.load(Ordering::Relaxed)
1458}
1459pub(crate) fn mm_kill() {
1460 MM_KILL.store(true, Ordering::Relaxed);
1461}
1462
1463static MM_STRIKES: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
1470const MM_STRIKES_TO_KILL: u32 = 3;
1471static MM_ARMED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
1478
1479pub fn mm_kill_arm(on: bool) {
1482 MM_ARMED.store(on, Ordering::Relaxed);
1483 if on {
1484 MM_STRIKES.store(0, Ordering::Relaxed);
1485 }
1486}
1487
1488pub(crate) fn mm_budget_check(
1495 what: &str,
1496 el: std::time::Duration,
1497 budget: std::time::Duration,
1498 exempt: bool,
1499) {
1500 if el <= budget {
1501 MM_STRIKES.store(0, Ordering::Relaxed);
1502 return;
1503 }
1504 if exempt || !MM_ARMED.load(Ordering::Relaxed) {
1505 return;
1506 }
1507 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
1508 let on = *ON.get_or_init(|| std::env::var("CMF_MM_KILL").as_deref() != Ok("0"));
1509 let n = MM_STRIKES.fetch_add(1, Ordering::Relaxed) + 1;
1510 if !on {
1511 tracing::info!(
1512 "gpu {what} took {el:?} (budget {budget:?}) — over budget, CMF_MM_KILL=0 keeps the device"
1513 );
1514 return;
1515 }
1516 if n >= MM_STRIKES_TO_KILL {
1517 tracing::warn!(
1518 "gpu {what} took {el:?} (budget {budget:?}), {n} in a row — \
1519 device contended, CPU for the rest of the process (CMF_MM_KILL=0 to override)"
1520 );
1521 mm_kill();
1522 } else {
1523 tracing::info!(
1524 "gpu {what} took {el:?} (budget {budget:?}) — strike {n} of {MM_STRIKES_TO_KILL}"
1525 );
1526 }
1527}
1528
1529#[allow(unused_variables, clippy::too_many_arguments)]
1534pub fn chunk_attend(
1535 q: &[f32],
1536 k: &[&[f32]],
1537 v: &[&[f32]],
1538 b: usize,
1539 s0: usize,
1540 nh: usize,
1541 nkv: usize,
1542 hd: usize,
1543 scale: f32,
1544 out: &mut [f32],
1545) -> bool {
1546 match backend() {
1547 #[cfg(feature = "gpu")]
1548 Backend::Wgpu => crate::gpu_wgpu::chunk_attend(q, k, v, b, s0, nh, nkv, hd, scale, out),
1549 #[allow(unreachable_patterns)]
1550 _ => false,
1551 }
1552}
1553
1554#[allow(unused_variables, clippy::too_many_arguments)]
1558pub fn q4t_qkv(
1559 model: &Arc<CmfModel>,
1560 wq: usize,
1561 wk: usize,
1562 wv: usize,
1563 xs: &[f32],
1564 b: usize,
1565 cols: usize,
1566 rq: usize,
1567 rk: usize,
1568 rv: usize,
1569 out: &mut [f32],
1570) -> bool {
1571 match backend() {
1572 #[cfg(feature = "gpu")]
1573 Backend::Wgpu => crate::gpu_wgpu::q4t_qkv(model, wq, wk, wv, xs, b, cols, rq, rk, rv, out),
1574 #[allow(unreachable_patterns)]
1575 _ => false,
1576 }
1577}
1578
1579#[allow(unused_variables, clippy::too_many_arguments)]
1581#[allow(clippy::too_many_arguments, unused_variables)]
1585pub fn q4tp_ffn_packed(
1586 model: &Arc<CmfModel>,
1587 w1: usize,
1588 w2: usize,
1589 xs: &[f32],
1590 b: usize,
1591 hidden: usize,
1592 inter: usize,
1593 bias: Option<&[f32]>,
1594 out: &mut [f32],
1595) -> bool {
1596 match backend() {
1597 #[cfg(feature = "gpu")]
1598 Backend::Wgpu => {
1599 crate::gpu_wgpu::q4tp_ffn_packed(model, w1, w2, xs, b, hidden, inter, bias, out)
1600 }
1601 #[allow(unreachable_patterns)]
1602 _ => false,
1603 }
1604}
1605
1606pub fn q4tp_ffn(
1607 model: &Arc<CmfModel>,
1608 w1: usize,
1609 w3: usize,
1610 w2: usize,
1611 xs: &[f32],
1612 b: usize,
1613 hidden: usize,
1614 inter: usize,
1615 out: &mut [f32],
1616) -> bool {
1617 match backend() {
1618 #[cfg(target_os = "macos")]
1619 Backend::Metal => crate::gpu_metal::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1620 #[cfg(feature = "gpu")]
1621 Backend::Wgpu => crate::gpu_wgpu::q4tp_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1622 #[allow(unreachable_patterns)]
1623 _ => false,
1624 }
1625}
1626
1627pub fn q4t_ffn(
1628 model: &Arc<CmfModel>,
1629 w1: usize,
1630 w3: usize,
1631 w2: usize,
1632 xs: &[f32],
1633 b: usize,
1634 hidden: usize,
1635 inter: usize,
1636 out: &mut [f32],
1637) -> bool {
1638 match backend() {
1639 #[cfg(target_os = "macos")]
1640 Backend::Metal => crate::gpu_metal::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1641 #[cfg(feature = "gpu")]
1642 Backend::Wgpu => crate::gpu_wgpu::q4t_ffn(model, w1, w3, w2, xs, b, hidden, inter, out),
1643 #[allow(unreachable_patterns)]
1644 _ => false,
1645 }
1646}
1647
1648pub struct DitBlockArgs<'a> {
1653 pub n: usize,
1654 pub hidden: usize,
1655 pub inter: usize,
1656 pub nh: usize,
1657 pub nkv: usize,
1658 pub hd: usize,
1659 pub eps: f32,
1660 pub rope_cos: &'a [f32],
1661 pub rope_sin: &'a [f32],
1662 pub norm1: &'a [f32],
1663 pub norm2: &'a [f32],
1664 pub ffn_norm1: &'a [f32],
1665 pub ffn_norm2: &'a [f32],
1666 pub norm_q: &'a [f32],
1667 pub norm_k: &'a [f32],
1668 pub s_msa: &'a [f32],
1669 pub gate_msa: &'a [f32],
1670 pub s_mlp: &'a [f32],
1671 pub gate_mlp: &'a [f32],
1672 pub wq: usize,
1673 pub wk: usize,
1674 pub wv: usize,
1675 pub wo: usize,
1676 pub w1: usize,
1677 pub w3: usize,
1678 pub w2: usize,
1679 pub q4tp: bool,
1683 pub resident_in: bool,
1686 pub resident_out: bool,
1690}
1691
1692pub fn dit_chain_supported() -> bool {
1696 #[cfg(feature = "gpu")]
1697 {
1698 return matches!(backend(), Backend::Wgpu) && fused_dit_block_available();
1699 }
1700 #[allow(unreachable_code)]
1701 false
1702}
1703
1704pub fn dit_state_fetch(_x: &mut [f32]) -> bool {
1707 #[cfg(feature = "gpu")]
1708 {
1709 if matches!(backend(), Backend::Wgpu) {
1710 return crate::gpu_wgpu::dit_state_fetch(_x);
1711 }
1712 }
1713 false
1714}
1715
1716#[allow(unused_variables)]
1720#[allow(unused_variables, clippy::too_many_arguments)]
1724pub fn dit_qkv(
1725 model: &Arc<CmfModel>,
1726 wq: usize,
1727 wk: usize,
1728 wv: usize,
1729 xs: &[f32],
1730 b: usize,
1731 hidden: usize,
1732 qrows: usize,
1733 kvrows: usize,
1734 q_out: &mut [f32],
1735 k_out: &mut [f32],
1736 v_out: &mut [f32],
1737) -> bool {
1738 match backend() {
1739 #[cfg(feature = "gpu")]
1740 Backend::Wgpu => crate::gpu_wgpu::q4tp_qkv(
1741 model, wq, wk, wv, xs, b, hidden, qrows, kvrows, q_out, k_out, v_out,
1742 ),
1743 #[allow(unreachable_patterns)]
1744 _ => false,
1745 }
1746}
1747
1748pub fn fused_dit_block_available() -> bool {
1752 #[cfg(target_os = "macos")]
1753 {
1754 matches!(backend(), Backend::Metal) && fused_block_trusted()
1755 }
1756 #[cfg(not(target_os = "macos"))]
1757 {
1758 false
1759 }
1760}
1761
1762pub fn dit_block(model: &Arc<CmfModel>, a: &DitBlockArgs, x: &mut [f32]) -> bool {
1763 dit_block_seg(model, a, &[a.n], x)
1764}
1765
1766pub fn dit_block_seg(
1770 model: &Arc<CmfModel>,
1771 a: &DitBlockArgs,
1772 segs: &[usize],
1773 x: &mut [f32],
1774) -> bool {
1775 match backend() {
1776 #[cfg(target_os = "macos")]
1777 Backend::Metal if segs.len() <= 1 => crate::gpu_metal::dit_block(model, a, x),
1778 #[cfg(feature = "gpu")]
1785 Backend::Wgpu
1786 if match std::env::var("CMF_DIT_FUSED").ok().as_deref() {
1787 Some("0") => false,
1788 Some(_) => true,
1789 None => crate::gpu_wgpu::discrete_active(),
1790 } =>
1791 {
1792 crate::gpu_wgpu::dit_block_seg(model, a, segs, x)
1793 }
1794 #[allow(unreachable_patterns)]
1795 _ => false,
1796 }
1797}
1798
1799pub struct VaeResnetArgs<'a> {
1803 pub groups: usize,
1804 pub ic: usize,
1805 pub oc: usize,
1806 pub h: usize,
1807 pub w: usize,
1808 pub n1w: &'a [f32],
1809 pub n1b: &'a [f32],
1810 pub c1w: &'a [f32],
1811 pub c1b: &'a [f32],
1812 pub c1k: usize,
1813 pub n2w: &'a [f32],
1814 pub n2b: &'a [f32],
1815 pub c2w: &'a [f32],
1816 pub c2b: &'a [f32],
1817 pub c2k: usize,
1818 pub shortcut: Option<(&'a [f32], &'a [f32], usize)>,
1819}
1820
1821#[allow(unused_variables)]
1824pub fn vae_resnet(a: &VaeResnetArgs, x: &[f32], out: &mut [f32]) -> bool {
1825 match backend() {
1826 #[cfg(target_os = "macos")]
1827 Backend::Metal => crate::gpu_metal::vae_resnet(a, x, out),
1828 _ => false,
1829 }
1830}
1831
1832#[allow(unused_variables, clippy::too_many_arguments)]
1835pub fn vae_upsample_conv(
1836 w: &[f32],
1837 bias: &[f32],
1838 x: &[f32],
1839 ic: usize,
1840 oc: usize,
1841 h: usize,
1842 w_img: usize,
1843 k: usize,
1844 out: &mut [f32],
1845) -> bool {
1846 match backend() {
1847 #[cfg(target_os = "macos")]
1848 Backend::Metal => crate::gpu_metal::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1849 #[cfg(feature = "gpu")]
1850 Backend::Wgpu => crate::gpu_wgpu::vae_upsample_conv(w, bias, x, ic, oc, h, w_img, k, out),
1851 #[allow(unreachable_patterns)]
1852 _ => false,
1853 }
1854}
1855
1856#[allow(unused_variables, clippy::too_many_arguments)]
1859pub fn vae_conv2d(
1860 w: &[f32],
1861 bias: &[f32],
1862 x: &[f32],
1863 ic: usize,
1864 oc: usize,
1865 h: usize,
1866 w_img: usize,
1867 k: usize,
1868 out: &mut [f32],
1869) -> bool {
1870 match backend() {
1871 #[cfg(target_os = "macos")]
1872 Backend::Metal => crate::gpu_metal::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1873 #[cfg(feature = "gpu")]
1874 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d(w, bias, x, ic, oc, h, w_img, k, out),
1875 #[allow(unreachable_patterns)]
1876 _ => false,
1877 }
1878}
1879
1880#[allow(unused_variables, clippy::too_many_arguments)]
1884#[allow(unused_variables)]
1888#[allow(clippy::too_many_arguments)]
1889#[allow(clippy::too_many_arguments, unused_variables)]
1892pub fn dit_qkv_attention(
1893 model: &Arc<CmfModel>,
1894 qkv_idx: usize,
1895 xn: &[f32],
1896 n: usize,
1897 hidden: usize,
1898 nh: usize,
1899 hd: usize,
1900 scale: f32,
1901 nr: (&[f32], &[f32], &[f32], f32),
1902 out: &mut [f32],
1903) -> bool {
1904 match backend() {
1905 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1906 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attention(
1907 model, qkv_idx, xn, n, hidden, nh, hd, scale, nr, out,
1908 ),
1909 #[allow(unreachable_patterns)]
1910 _ => false,
1911 }
1912}
1913
1914#[allow(clippy::too_many_arguments)]
1917pub fn dit_qkv_attn_out(
1918 model: &Arc<CmfModel>,
1919 qkv_idx: usize,
1920 out_idx: usize,
1921 xn: &[f32],
1922 n: usize,
1923 hidden: usize,
1924 nh: usize,
1925 hd: usize,
1926 scale: f32,
1927 nr: (&[f32], &[f32], &[f32], f32),
1928 proj: &mut [f32],
1929) -> bool {
1930 match backend() {
1931 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1932 Backend::Wgpu => crate::gpu_wgpu::dit_qkv_attn_out(
1933 model, qkv_idx, out_idx, xn, n, hidden, nh, hd, scale, nr, proj,
1934 ),
1935 #[allow(unreachable_patterns)]
1936 _ => false,
1937 }
1938}
1939
1940#[allow(clippy::too_many_arguments)]
1942pub fn vae_qkv_attn_out(
1943 model: &Arc<CmfModel>,
1944 qkv_idx: usize,
1945 out_idx: usize,
1946 xn: &[f32],
1947 n: usize,
1948 dim: usize,
1949 nh: usize,
1950 hd: usize,
1951 scale: f32,
1952 angles: &[f32],
1953 eps: f32,
1954 qkv_bias: &[f32],
1955 proj: &mut [f32],
1956) -> bool {
1957 match backend() {
1958 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1959 Backend::Wgpu => crate::gpu_wgpu::vae_qkv_attn_out(
1960 model, qkv_idx, out_idx, xn, n, dim, nh, hd, scale, angles, eps, qkv_bias, proj,
1961 ),
1962 #[allow(unreachable_patterns)]
1963 _ => false,
1964 }
1965}
1966
1967#[allow(clippy::too_many_arguments)]
1968pub fn vae_attention_packed(
1969 qkv: &[f32],
1970 nh: usize,
1971 n: usize,
1972 hd: usize,
1973 scale: f32,
1974 angles: &[f32],
1975 eps: f32,
1976 out: &mut [f32],
1977) -> bool {
1978 vae_attention_packed_layout(qkv, nh, n, hd, scale, angles, eps, out, 1)
1979}
1980
1981#[allow(clippy::too_many_arguments)]
1982pub fn vae_attention_packed_layout(
1983 qkv: &[f32],
1984 nh: usize,
1985 n: usize,
1986 hd: usize,
1987 scale: f32,
1988 angles: &[f32],
1989 eps: f32,
1990 out: &mut [f32],
1991 layout: u32,
1992) -> bool {
1993 match backend() {
1994 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
1995 Backend::Wgpu => crate::gpu_wgpu::vae_attention_packed_layout(
1996 qkv, nh, n, hd, scale, angles, eps, out, layout,
1997 ),
1998 #[allow(unreachable_patterns)]
1999 _ => false,
2000 }
2001}
2002
2003#[allow(clippy::too_many_arguments)]
2004pub fn dit_split_only(
2005 qkv: &[f32],
2006 nh: usize,
2007 n: usize,
2008 hd: usize,
2009 layout: u32,
2010 norm: Option<(&[f32], f32)>,
2011 out_q: &mut [f32],
2012) -> bool {
2013 match backend() {
2014 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2015 Backend::Wgpu => crate::gpu_wgpu::dit_split_only(qkv, nh, n, hd, layout, norm, out_q),
2016 #[allow(unreachable_patterns)]
2017 _ => false,
2018 }
2019}
2020
2021pub fn gemm_nt_f32(x: &[f32], w: &[f32], y: &mut [f32], n: usize, k: usize, m: usize) -> bool {
2026 match backend() {
2027 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2028 Backend::Wgpu => crate::gpu_wgpu::gemm_nt_f32(x, w, y, n, k, m),
2029 #[allow(unreachable_patterns)]
2030 _ => false,
2031 }
2032}
2033
2034#[allow(clippy::too_many_arguments)]
2037pub fn music3_ffn(
2038 model: &std::sync::Arc<CmfModel>,
2039 idx_in: usize,
2040 idx_out: usize,
2041 h: &[f32],
2042 bias_in: &[f32],
2043 n: usize,
2044 hs: usize,
2045 inter: usize,
2046 out: &mut [f32],
2047) -> bool {
2048 match backend() {
2049 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2050 Backend::Wgpu => {
2051 crate::gpu_wgpu::music3_ffn(model, idx_in, idx_out, h, bias_in, n, hs, inter, out)
2052 }
2053 #[allow(unreachable_patterns)]
2054 _ => false,
2055 }
2056}
2057
2058#[allow(clippy::too_many_arguments)]
2062pub fn conv1d_gemm(
2063 x: &[f32],
2064 w: &[f32],
2065 ic: usize,
2066 oc: usize,
2067 n: usize,
2068 k: usize,
2069 pad: usize,
2070 dil: usize,
2071 out_n: usize,
2072 yt: &mut [f32],
2073) -> bool {
2074 match backend() {
2075 #[cfg(target_os = "macos")]
2076 Backend::Metal => crate::gpu_metal::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2077 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2078 Backend::Wgpu => crate::gpu_wgpu::conv1d_gemm(x, w, ic, oc, n, k, pad, dil, out_n, yt),
2079 #[allow(unreachable_patterns)]
2080 _ => false,
2081 }
2082}
2083
2084#[allow(clippy::too_many_arguments)]
2086pub fn vae_conv2d_coop(
2087 w: &[f32],
2088 bias: Option<&[f32]>,
2089 x: &[f32],
2090 ic: usize,
2091 oc: usize,
2092 h: usize,
2093 wi: usize,
2094 k: usize,
2095 out: &mut [f32],
2096) -> bool {
2097 match backend() {
2098 #[cfg(all(feature = "gpu", not(target_os = "macos")))]
2099 Backend::Wgpu => crate::gpu_wgpu::vae_conv2d_coop(w, bias, x, ic, oc, h, wi, k, out),
2100 #[allow(unreachable_patterns)]
2101 _ => false,
2102 }
2103}
2104
2105pub fn dit_attention_packed(
2106 qkv: &[f32],
2107 nh: usize,
2108 n: usize,
2109 hd: usize,
2110 scale: f32,
2111 nr: Option<(&[f32], &[f32], &[f32], f32)>,
2114 out: &mut [f32],
2115) -> bool {
2116 match backend() {
2117 #[cfg(feature = "gpu")]
2124 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed(qkv, nh, n, hd, scale, nr, out),
2125 #[allow(unreachable_patterns)]
2126 _ => false,
2127 }
2128}
2129
2130pub fn dit_attention_packed_available() -> bool {
2138 #[allow(unreachable_patterns)]
2139 match backend() {
2140 #[cfg(feature = "gpu")]
2141 Backend::Wgpu => crate::gpu_wgpu::dit_attention_packed_ready(),
2142 _ => false,
2143 }
2144}
2145
2146pub fn dit_attention(
2147 qh: &[f32],
2148 kh: &[f32],
2149 vh: &[f32],
2150 nh: usize,
2151 nkv: usize,
2152 n: usize,
2153 hd: usize,
2154 scale: f32,
2155 out: &mut [f32],
2156) -> bool {
2157 match backend() {
2158 #[cfg(target_os = "macos")]
2159 Backend::Metal => crate::gpu_metal::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2160 #[cfg(feature = "gpu")]
2161 Backend::Wgpu => crate::gpu_wgpu::dit_attention(qh, kh, vh, nh, nkv, n, hd, scale, out),
2162 #[allow(unreachable_patterns)]
2163 _ => false,
2164 }
2165}
2166
2167#[allow(unused_variables)]
2172pub fn q4tp_matmat(
2173 model: &Arc<CmfModel>,
2174 idx: usize,
2175 xs: &[f32],
2176 b: usize,
2177 rows: usize,
2178 cols: usize,
2179 out: &mut [f32],
2180) -> bool {
2181 match backend() {
2182 #[cfg(target_os = "macos")]
2183 Backend::Metal => crate::gpu_metal::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2184 #[cfg(feature = "gpu")]
2185 Backend::Wgpu => crate::gpu_wgpu::q4tp_matmat(model, idx, xs, b, rows, cols, out),
2186 #[allow(unreachable_patterns)]
2187 _ => false,
2188 }
2189}
2190
2191pub fn q2tp_matmat(
2194 model: &Arc<CmfModel>,
2195 idx: usize,
2196 xs: &[f32],
2197 b: usize,
2198 rows: usize,
2199 cols: usize,
2200 out: &mut [f32],
2201) -> bool {
2202 match backend() {
2203 #[cfg(feature = "gpu")]
2204 Backend::Wgpu => crate::gpu_wgpu::q2tp_matmat(model, idx, xs, b, rows, cols, out),
2205 #[allow(unreachable_patterns)]
2206 _ => false,
2207 }
2208}
2209
2210pub fn q4tp_matvec(
2215 model: &Arc<CmfModel>,
2216 idx: usize,
2217 xs: &[f32],
2218 rows: usize,
2219 cols: usize,
2220 out: &mut [f32],
2221) -> bool {
2222 match backend() {
2223 #[cfg(target_os = "macos")]
2224 Backend::Metal => crate::gpu_metal::q4tp_matvec_for_test(model, idx, xs, rows, cols, out),
2225 #[cfg(feature = "gpu")]
2226 Backend::Wgpu => crate::gpu_wgpu::q4tp_matvec(model, idx, xs, rows, cols, out),
2227 #[allow(unreachable_patterns)]
2228 _ => false,
2229 }
2230}
2231
2232pub fn q4t_matvec(
2238 model: &Arc<CmfModel>,
2239 idx: usize,
2240 xs: &[f32],
2241 rows: usize,
2242 cols: usize,
2243 out: &mut [f32],
2244) -> bool {
2245 match backend() {
2246 #[cfg(target_os = "macos")]
2247 Backend::Metal => crate::gpu_metal::q4t_matvec_for_test(model, idx, xs, rows, cols, out),
2248 #[allow(unreachable_patterns)]
2249 _ => false,
2250 }
2251}
2252
2253pub fn q4t_matmat(
2254 model: &Arc<CmfModel>,
2255 idx: usize,
2256 xs: &[f32],
2257 b: usize,
2258 rows: usize,
2259 cols: usize,
2260 out: &mut [f32],
2261) -> bool {
2262 match backend() {
2263 #[cfg(target_os = "macos")]
2264 Backend::Metal => crate::gpu_metal::q4t_matmat(model, idx, xs, b, rows, cols, out),
2265 #[cfg(feature = "gpu")]
2266 Backend::Wgpu => crate::gpu_wgpu::q4t_matmat(model, idx, xs, b, rows, cols, out),
2267 #[allow(unreachable_patterns)]
2268 _ => false,
2269 }
2270}
2271
2272#[cfg(target_os = "macos")]
2274pub use crate::gpu_metal::{
2275 AttnDeviceParams, AttnGpuLayer, GdnGpuCfg, GdnGpuLayer, GpuMoe, GraphDims, MetalFfn,
2276 O1AttnParams, TokenGraph, kv_mirror_drop, kv_mirror_read_last, kv_mirror_take_imp,
2277};
2278
2279#[cfg(target_os = "macos")]
2281pub fn gdn_block(
2282 model: &Arc<CmfModel>,
2283 layers: &[GdnGpuLayer],
2284 states: &mut [&mut [f32]],
2285 cfg: &GdnGpuCfg,
2286 h: &mut [f32],
2287) -> bool {
2288 match backend() {
2289 Backend::Metal => crate::gpu_metal::gdn_block(model, layers, states, cfg, h),
2290 _ => false,
2291 }
2292}
2293
2294#[allow(unused_variables)]
2296pub fn moe_block(model: &Arc<CmfModel>, jobs: &[MoeJob], out: &mut [f32]) -> bool {
2297 match backend() {
2298 #[cfg(target_os = "macos")]
2299 Backend::Metal => crate::gpu_metal::moe_block(model, jobs, out),
2300 #[cfg(feature = "gpu")]
2301 Backend::Wgpu => crate::gpu_wgpu::moe_block(model, jobs, out),
2302 Backend::None => false,
2303 }
2304}
2305
2306#[allow(unused_variables)]
2308pub fn matvec_batch(model: &Arc<CmfModel>, jobs: &[BatchJob], out: &mut [&mut [f32]]) -> bool {
2309 match backend() {
2310 #[cfg(target_os = "macos")]
2311 Backend::Metal => crate::gpu_metal::matvec_batch(model, jobs, out),
2312 #[cfg(feature = "gpu")]
2313 Backend::Wgpu => crate::gpu_wgpu::matvec_batch(model, jobs, out),
2314 Backend::None => false,
2315 }
2316}
2317
2318static GRAPH_RACE_STATE: AtomicU8 = AtomicU8::new(0); static GRAPH_RACE_FLIP: AtomicU32 = AtomicU32::new(0);
2334static 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)];
2338
2339const GRAPH_RACE_SAMPLES: u32 = 4;
2341
2342static GRAPH_UNSUPPORTED: AtomicBool = AtomicBool::new(false);
2352
2353pub fn graph_mark_unsupported() {
2358 if !GRAPH_UNSUPPORTED.swap(true, Ordering::Relaxed) {
2359 tracing::info!("wgpu token graph: unsupported for this model — not retrying");
2360 }
2361}
2362
2363pub fn graph_unsupported() -> bool {
2364 GRAPH_UNSUPPORTED.load(Ordering::Relaxed)
2365}
2366
2367pub fn graph_unsupported_reset() {
2369 GRAPH_UNSUPPORTED.store(false, Ordering::Relaxed);
2370}
2371
2372pub fn graph_race_begin_generation() {
2373 #[cfg(feature = "gpu")]
2378 {
2379 static FLUSHED: std::sync::Once = std::sync::Once::new();
2391 static FIRST: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
2392 if FIRST.swap(false, Ordering::Relaxed) {
2393 } else {
2395 FLUSHED.call_once(crate::gpu_wgpu::pipeline_cache_flush);
2396 }
2397 }
2398 GRAPH_RACE_TOK.store(0, Ordering::Relaxed);
2399 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2400 return;
2401 }
2402 let (gn, cn) = (
2403 GRAPH_N[1].load(Ordering::Relaxed),
2404 GRAPH_N[0].load(Ordering::Relaxed),
2405 );
2406 if gn >= GRAPH_RACE_SAMPLES && cn >= GRAPH_RACE_SAMPLES {
2407 let g_avg = GRAPH_NS[1].load(Ordering::Relaxed) / gn as u64;
2408 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2409 let verdict = if g_avg < c_avg { 1 } else { 2 };
2410 GRAPH_RACE_STATE.store(verdict, Ordering::Relaxed);
2411 tracing::info!(
2412 "wgpu graph race: graph {:.2} ms/tok vs normal {:.2} ms/tok -> {}",
2413 g_avg as f64 / 1e6,
2414 c_avg as f64 / 1e6,
2415 if verdict == 1 { "graph" } else { "normal path" }
2416 );
2417 return;
2418 }
2419 let flip = GRAPH_RACE_FLIP.fetch_add(1, Ordering::Relaxed);
2420 GRAPH_RACE_ARM_GRAPH.store((flip % 2 == 1) as u8, Ordering::Relaxed);
2421}
2422
2423pub fn graph_race_use_graph(trusted: bool) -> bool {
2427 if trusted {
2428 return true;
2429 }
2430 match GRAPH_RACE_STATE.load(Ordering::Relaxed) {
2431 1 => true,
2432 2 => false,
2433 _ => GRAPH_RACE_ARM_GRAPH.load(Ordering::Relaxed) == 1,
2434 }
2435}
2436
2437pub fn graph_race_first_token_hopeless(dur: std::time::Duration) -> bool {
2442 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2443 return false;
2444 }
2445 let first = GRAPH_RACE_TOK.load(Ordering::Relaxed) == 0;
2446 let cn = GRAPH_N[0].load(Ordering::Relaxed);
2447 if !first || cn == 0 {
2448 return false;
2449 }
2450 let c_avg = GRAPH_NS[0].load(Ordering::Relaxed) / cn as u64;
2451 let ns = dur.as_nanos() as u64;
2452 if ns > 1_000_000_000 && ns > 4 * c_avg {
2453 GRAPH_RACE_STATE.store(2, Ordering::Relaxed);
2454 tracing::info!(
2455 "wgpu graph race: first graph token {:.0} ms vs normal {:.2} ms/tok — hopeless, normal path wins",
2456 ns as f64 / 1e6,
2457 c_avg as f64 / 1e6
2458 );
2459 return true;
2460 }
2461 false
2462}
2463
2464pub fn graph_race_record(used_graph: bool, dur: std::time::Duration) {
2468 if GRAPH_RACE_STATE.load(Ordering::Relaxed) != 0 {
2469 return;
2470 }
2471 let tok = GRAPH_RACE_TOK.fetch_add(1, Ordering::Relaxed);
2472 if tok == 0 {
2473 return;
2474 }
2475 let i = used_graph as usize;
2476 GRAPH_NS[i].fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
2477 GRAPH_N[i].fetch_add(1, Ordering::Relaxed);
2478}
2479
2480pub(crate) fn fp_bytes(data: &[u8]) -> u64 {
2490 #[inline]
2491 fn fnv(mut h: u64, bytes: &[u8]) -> u64 {
2492 let (chunks, tail) = bytes.split_at(bytes.len() & !7);
2493 for c in chunks.chunks_exact(8) {
2494 h ^= u64::from_le_bytes(c.try_into().unwrap());
2495 h = h.wrapping_mul(0x100_0000_01b3);
2496 }
2497 for &b in tail {
2498 h ^= b as u64;
2499 h = h.wrapping_mul(0x100_0000_01b3);
2500 }
2501 h
2502 }
2503 let mut h = 0xcbf2_9ce4_8422_2325u64 ^ (data.len() as u64);
2504 if data.len() <= 4096 {
2505 return fnv(h, data);
2506 }
2507 let step = (data.len() - 64) / 63;
2508 for i in 0..64 {
2509 h = fnv(h, &data[i * step..i * step + 64]);
2510 }
2511 h
2512}
2513
2514pub(crate) fn fp_f32(data: &[f32]) -> u64 {
2517 let bytes = unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 4) };
2518 fp_bytes(bytes)
2519}
2520
2521#[cfg(test)]
2522mod fp_tests {
2523 use super::fp_bytes;
2524
2525 #[test]
2530 fn fp_bytes_sees_a_change_anywhere_in_a_sampled_slice() {
2531 let n = 1 << 20; let base: Vec<u8> = (0..n).map(|i| (i * 31 + 7) as u8).collect();
2533 let h0 = fp_bytes(&base);
2534 assert_eq!(h0, fp_bytes(&base), "fingerprint must be deterministic");
2535 let mut dense = base.clone();
2538 for b in dense.iter_mut() {
2539 *b = b.wrapping_add(1);
2540 }
2541 assert_ne!(
2542 h0,
2543 fp_bytes(&dense),
2544 "a fully different tensor slipped through"
2545 );
2546 assert_ne!(h0, fp_bytes(&base[..n - 64]));
2549 let mut small = vec![3u8; 4096];
2552 let hs = fp_bytes(&small);
2553 small[2048] ^= 1;
2554 assert_ne!(hs, fp_bytes(&small), "full hash missed a one-byte change");
2555 for n in [4097usize, 5000, 64 * 64, 1 << 16] {
2557 let v = vec![9u8; n];
2558 let _ = fp_bytes(&v); }
2560 }
2561}
2562
2563pub fn bake_release() {
2567 #[cfg(feature = "gpu")]
2568 crate::gpu_wgpu::bake_release();
2569}
2570
2571pub fn bake_precision_strict(on: bool) {
2575 #[cfg(feature = "gpu")]
2576 crate::gpu_wgpu::bake_precision_strict(on);
2577 #[cfg(not(feature = "gpu"))]
2578 let _ = on;
2579}
2580
2581pub fn hostprof_encode_done(t0: std::time::Instant) {
2587 use std::sync::atomic::{AtomicU64, Ordering};
2588 static ENC: AtomicU64 = AtomicU64::new(0);
2589 static N: AtomicU64 = AtomicU64::new(0);
2590 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2591 return;
2592 }
2593 ENC.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2594 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2595 if n % 100 == 0 {
2596 eprintln!(
2597 "hostprof: encode {:.2} ms/token over {n} tokens",
2598 ENC.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2599 );
2600 }
2601}
2602
2603pub fn hostprof_total(t0: std::time::Instant) {
2604 use std::sync::atomic::{AtomicU64, Ordering};
2605 static TOT: AtomicU64 = AtomicU64::new(0);
2606 static N: AtomicU64 = AtomicU64::new(0);
2607 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2608 return;
2609 }
2610 TOT.fetch_add(t0.elapsed().as_nanos() as u64, Ordering::Relaxed);
2611 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2612 if n % 100 == 0 {
2613 eprintln!(
2614 "hostprof: total {:.2} ms/token over {n} tokens",
2615 TOT.load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2616 );
2617 }
2618}
2619
2620pub fn stageprof(stage: u32, dt: std::time::Duration) {
2624 use std::sync::atomic::{AtomicU64, Ordering};
2625 static NS: [AtomicU64; 4] = [
2626 AtomicU64::new(0),
2627 AtomicU64::new(0),
2628 AtomicU64::new(0),
2629 AtomicU64::new(0),
2630 ];
2631 static N: AtomicU64 = AtomicU64::new(0);
2632 if std::env::var("CMF_GRAPH_HOSTPROF").as_deref() != Ok("1") {
2633 return;
2634 }
2635 NS[stage as usize % 4].fetch_add(dt.as_nanos() as u64, Ordering::Relaxed);
2636 if stage == 1 {
2637 let n = N.fetch_add(1, Ordering::Relaxed) + 1;
2638 if n % 200 == 0 {
2639 eprintln!(
2640 "stageprof: planning {:.2} ms/tok | gdn-item {:.2} ms/tok | attn-item {:.2} ms/tok ({n} tok)",
2641 NS[1].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2642 NS[2].load(Ordering::Relaxed) as f64 / n as f64 / 1e6,
2643 NS[3].load(Ordering::Relaxed) as f64 / n as f64 / 1e6
2644 );
2645 }
2646 }
2647}
2648
2649pub fn weight_bytes_dispatched() -> u64 {
2652 let mut total = 0u64;
2653 #[cfg(target_os = "macos")]
2654 {
2655 total += crate::gpu_metal::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2656 }
2657 #[cfg(feature = "gpu")]
2658 {
2659 total += crate::gpu_wgpu::WEIGHT_BYTES.load(std::sync::atomic::Ordering::Relaxed);
2660 }
2661 total
2662}
2663
2664pub fn weight_bytes_by() -> [u64; 6] {
2667 #[cfg(target_os = "macos")]
2668 {
2669 let mut o = [0u64; 6];
2670 for (i, a) in crate::gpu_metal::WEIGHT_BYTES_BY.iter().enumerate() {
2671 o[i] = a.load(std::sync::atomic::Ordering::Relaxed);
2672 }
2673 return o;
2674 }
2675 #[allow(unreachable_code)]
2676 [0; 6]
2677}