qualia_core_db/platform/compute_bridge/
policy.rs1use crate::device_benchmark::CircuitKind;
15use crate::platform::hetero_dispatch::{
16 select_precision, HeterogeneousDispatcher, HostCapabilities, PowerThermalBudget, Precision,
17 ZeroCopyStrategy,
18};
19
20use super::backend::{BackendId, KernelPanel};
21use super::kernel_class::KernelClass;
22use super::matrix::{probe_class_matrix, ClassMatrix};
23
24#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct Plan {
29 pub backend: BackendId,
31 pub circuit_kind: CircuitKind,
33 pub precision: Precision,
35 pub tiles: u32,
38 pub zero_copy: ZeroCopyStrategy,
40}
41
42impl Plan {
43 pub fn is_cpu(self) -> bool {
45 self.backend == BackendId::CPU || self.circuit_kind == CircuitKind::Cpu
46 }
47}
48
49pub struct ComputePolicy {
52 matrix: ClassMatrix,
53 budget: PowerThermalBudget,
54 host: HostCapabilities,
55}
56
57impl ComputePolicy {
58 pub fn from_class_matrix(
61 matrix: ClassMatrix,
62 budget: PowerThermalBudget,
63 host: HostCapabilities,
64 ) -> Self {
65 Self {
66 matrix,
67 budget,
68 host,
69 }
70 }
71
72 pub fn probe(
75 registry: &super::backend::BackendRegistry,
76 panel: &KernelPanel,
77 budget: PowerThermalBudget,
78 host: HostCapabilities,
79 ) -> Self {
80 Self::from_class_matrix(probe_class_matrix(registry, panel), budget, host)
81 }
82
83 pub fn matrix(&self) -> &ClassMatrix {
85 &self.matrix
86 }
87
88 pub fn select(&self, class: KernelClass, problem_bytes: u64) -> Plan {
94 let param_count = (problem_bytes / 4).max(1);
97 let precision = select_precision(param_count, &self.budget);
98
99 let best = self.matrix.best_for(class);
100 let cpu_ms = self
101 .matrix
102 .rows(class)
103 .iter()
104 .find(|r| r.kind == CircuitKind::Cpu)
105 .map(|r| r.ms_per_gemv);
106
107 let choose_gpu = match best {
108 None => false, Some(b) if b.kind == CircuitKind::Cpu => false, Some(b) => {
111 if class.is_typically_gpu_amenable() {
115 true
116 } else if let Some(cms) = cpu_ms {
117 b.ms_per_gemv < cms * 0.95
118 } else {
119 true
120 }
121 }
122 };
123
124 if let (true, Some(b)) = (choose_gpu, best) {
125 let dispatcher = HeterogeneousDispatcher::new(self.host);
126 let tiles = dispatcher.gpu_tiles(problem_bytes);
127 let zero_copy = match b.kind {
128 CircuitKind::DiscreteGpu => ZeroCopyStrategy::StagingUpload,
129 _ => ZeroCopyStrategy::MmapDirect, };
131 Plan {
132 backend: BackendId(backend_id_for(&b.backend)),
133 circuit_kind: b.kind,
134 precision,
135 tiles,
136 zero_copy,
137 }
138 } else {
139 Plan {
141 backend: BackendId::CPU,
142 circuit_kind: CircuitKind::Cpu,
143 precision: Precision::F32, tiles: 1,
145 zero_copy: ZeroCopyStrategy::MmapDirect,
146 }
147 }
148 }
149}
150
151fn backend_id_for(backend: &str) -> &'static str {
155 match backend {
156 "native" => "cpu",
157 _ => "wgpu",
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::device_benchmark::CircuitBench;
165
166 const GIB: u64 = 1 << 30;
167
168 fn roomy_host() -> (PowerThermalBudget, HostCapabilities) {
169 (
170 PowerThermalBudget {
171 vram_budget_bytes: 16 * GIB,
172 power_budget_mw: 60_000,
173 thermal_headroom_c: 30.0,
174 },
175 HostCapabilities {
176 gpu_available: true,
177 vram_available: 8 * GIB,
178 npu_available: false,
179 cpu_threads: 16,
180 },
181 )
182 }
183
184 fn row(kind: CircuitKind, backend: &str, ms: f64) -> CircuitBench {
185 CircuitBench {
186 label: format!("{backend} circuit"),
187 kind,
188 backend: backend.to_string(),
189 ms_per_gemv: ms,
190 gflops: 0.0,
191 upload_gbps: if kind == CircuitKind::Cpu {
192 f64::INFINITY
193 } else {
194 5.0
195 },
196 rel_score: 1.0,
197 decode_proxy_tok_s: None,
198 }
199 }
200
201 fn synth_matrix() -> ClassMatrix {
203 let mut per_class = Vec::new();
204 for class in KernelClass::ALL {
205 let rows = match class {
206 KernelClass::DenseLinear => vec![
207 row(CircuitKind::DiscreteGpu, "Vulkan", 0.4),
208 row(CircuitKind::Cpu, "native", 20.0),
209 ],
210 KernelClass::Divergent => vec![row(CircuitKind::Cpu, "native", 3.0)],
211 _ => vec![row(CircuitKind::Cpu, "native", 5.0)],
212 };
213 per_class.push((class, rows));
214 }
215 ClassMatrix::from_per_class(per_class)
216 }
217
218 #[test]
219 fn selects_measured_gpu_winner_for_dense_linear() {
220 let (budget, host) = roomy_host();
221 let policy = ComputePolicy::from_class_matrix(synth_matrix(), budget, host);
222 let plan = policy.select(KernelClass::DenseLinear, 64 * 1024 * 1024);
223 assert_eq!(plan.backend, BackendId::WGPU);
224 assert_eq!(plan.circuit_kind, CircuitKind::DiscreteGpu);
225 assert_eq!(plan.zero_copy, ZeroCopyStrategy::StagingUpload); assert!(!plan.is_cpu());
227 }
228
229 #[test]
230 fn falls_back_to_cpu_when_only_cpu_probed() {
231 let (budget, host) = roomy_host();
232 let policy = ComputePolicy::from_class_matrix(synth_matrix(), budget, host);
233 let plan = policy.select(KernelClass::Divergent, 1 << 20);
234 assert!(plan.is_cpu());
235 assert_eq!(plan.backend, BackendId::CPU);
236 assert_eq!(plan.tiles, 1);
237 }
238
239 #[test]
240 fn unprobed_class_is_cpu_not_a_panic() {
241 let (budget, host) = roomy_host();
243 let empty = ClassMatrix::from_per_class(vec![(KernelClass::Fft, Vec::new())]);
244 let policy = ComputePolicy::from_class_matrix(empty, budget, host);
245 assert!(policy.select(KernelClass::Fft, 1 << 20).is_cpu());
246 assert!(policy.select(KernelClass::AllPairs, 1 << 20).is_cpu()); }
248
249 #[test]
250 fn vram_pressure_tiles_instead_of_failing() {
251 let budget = PowerThermalBudget {
252 vram_budget_bytes: 16 * GIB,
253 power_budget_mw: 60_000,
254 thermal_headroom_c: 30.0,
255 };
256 let host = HostCapabilities {
257 gpu_available: true,
258 vram_available: 2 * GIB,
259 npu_available: false,
260 cpu_threads: 8,
261 };
262 let policy = ComputePolicy::from_class_matrix(synth_matrix(), budget, host);
263 let plan = policy.select(KernelClass::DenseLinear, 5 * GIB);
265 assert!(!plan.is_cpu());
266 assert_eq!(plan.tiles, 3);
267 }
268}