qualia_core_db/platform/compute_bridge/
execute.rs1use std::sync::OnceLock;
15
16use super::gpu_gemm;
17use super::kernel_class::KernelClass;
18use super::policy::ComputePolicy;
19use crate::platform::hetero_dispatch::{HostCapabilities, PowerThermalBudget};
20
21const GPU_MIN_FLOPS: u64 = 1 << 20; #[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum RanOn {
28 Gpu,
29 Cpu,
30}
31
32static POLICY: OnceLock<ComputePolicy> = OnceLock::new();
33
34pub fn shared_policy() -> &'static ComputePolicy {
37 POLICY.get_or_init(|| {
38 let registry = super::default_registry();
39 let panel = super::backend::KernelPanel::default();
40 let budget = PowerThermalBudget {
41 vram_budget_bytes: 4u64 << 30,
42 power_budget_mw: 45_000,
43 thermal_headroom_c: 20.0,
44 };
45 let host = HostCapabilities {
46 gpu_available: gpu_gemm::shared().is_some(),
47 vram_available: 2u64 << 30,
48 npu_available: false,
49 cpu_threads: num_cpus::get() as u32,
50 };
51 ComputePolicy::probe(®istry, &panel, budget, host)
52 })
53}
54
55fn cpu_gemm_f32(_m: usize, k: usize, n: usize, a: &[f32], b: &[f32], c: &mut [f32]) {
57 use rayon::prelude::*;
58 c.par_chunks_mut(n).enumerate().for_each(|(i, row)| {
59 let a_row = &a[i * k..(i + 1) * k];
60 for (j, cell) in row.iter_mut().enumerate() {
61 let mut s = 0.0f32;
62 for l in 0..k {
63 s += a_row[l] * b[l * n + j];
64 }
65 *cell = s;
66 }
67 });
68}
69
70pub fn accelerated_gemm_f32(
75 m: usize,
76 k: usize,
77 n: usize,
78 a: &[f32],
79 b: &[f32],
80 c: &mut [f32],
81) -> RanOn {
82 debug_assert_eq!(a.len(), m * k);
83 debug_assert_eq!(b.len(), k * n);
84 debug_assert_eq!(c.len(), m * n);
85
86 let flops = (m as u64) * (k as u64) * (n as u64);
87 if flops >= GPU_MIN_FLOPS {
88 if let Some(ctx) = gpu_gemm::shared() {
89 if ctx.fits(m, k, n) {
90 let plan = shared_policy().select(KernelClass::DenseLinear, (m * n * 4) as u64);
91 if !plan.is_cpu() {
92 if let Some(result) = ctx.gemm(m, k, n, a, b) {
93 c.copy_from_slice(&result);
94 return RanOn::Gpu;
95 }
96 }
97 }
98 }
99 }
100 cpu_gemm_f32(m, k, n, a, b, c);
101 RanOn::Cpu
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107
108 fn ref_gemm(m: usize, k: usize, n: usize, a: &[f32], b: &[f32]) -> Vec<f32> {
109 let mut c = vec![0.0f32; m * n];
110 for i in 0..m {
111 for j in 0..n {
112 let mut s = 0.0f32;
113 for l in 0..k {
114 s += a[i * k + l] * b[l * n + j];
115 }
116 c[i * n + j] = s;
117 }
118 }
119 c
120 }
121
122 #[test]
123 fn small_job_runs_on_cpu_and_is_correct() {
124 let (m, k, n) = (4, 5, 3);
125 let a: Vec<f32> = (0..m * k).map(|i| i as f32 * 0.1).collect();
126 let b: Vec<f32> = (0..k * n).map(|i| i as f32 * 0.2 - 0.5).collect();
127 let mut c = vec![0.0f32; m * n];
128 let ran = accelerated_gemm_f32(m, k, n, &a, &b, &mut c);
129 assert_eq!(ran, RanOn::Cpu, "a tiny job must stay on CPU");
130 assert_eq!(c, ref_gemm(m, k, n, &a, &b));
131 }
132
133 #[test]
134 fn large_job_is_correct_on_whichever_backend_the_machine_chose() {
135 let (m, k, n) = (128, 128, 128);
138 let a: Vec<f32> = (0..m * k).map(|i| ((i % 17) as f32) * 0.05 - 0.3).collect();
139 let b: Vec<f32> = (0..k * n).map(|i| ((i % 11) as f32) * 0.07 - 0.2).collect();
140 let mut c = vec![0.0f32; m * n];
141 let ran = accelerated_gemm_f32(m, k, n, &a, &b, &mut c);
142 eprintln!("[accelerated_gemm_f32] 128³ ran on {ran:?}");
143 let reference = ref_gemm(m, k, n, &a, &b);
144 let max_err = c
145 .iter()
146 .zip(&reference)
147 .map(|(x, y)| (x - y).abs())
148 .fold(0.0f32, f32::max);
149 assert!(max_err < 1e-2, "max abs err {max_err} on {ran:?}");
150 }
151}