trueno/simulation/scheduler/
mod.rs1use crate::Backend;
7use std::collections::VecDeque;
8use std::marker::PhantomData;
9
10#[derive(Debug, Clone, Copy, PartialEq)]
15pub struct BackendTolerance {
16 pub scalar_vs_simd: f32,
18 pub simd_vs_gpu: f32,
20 pub gpu_vs_gpu: f32,
22}
23
24impl Default for BackendTolerance {
25 fn default() -> Self {
26 Self { scalar_vs_simd: 0.0, simd_vs_gpu: 1e-5, gpu_vs_gpu: 1e-6 }
27 }
28}
29
30impl BackendTolerance {
31 #[must_use]
33 pub const fn strict() -> Self {
34 Self { scalar_vs_simd: 0.0, simd_vs_gpu: 0.0, gpu_vs_gpu: 0.0 }
35 }
36
37 #[must_use]
39 pub const fn relaxed() -> Self {
40 Self { scalar_vs_simd: 1e-6, simd_vs_gpu: 1e-4, gpu_vs_gpu: 1e-5 }
41 }
42
43 #[must_use]
45 pub fn for_backends(&self, a: Backend, b: Backend) -> f32 {
46 match (a, b) {
47 (Backend::Scalar, Backend::Scalar) => 0.0,
48 (
49 Backend::Scalar,
50 Backend::SSE2
51 | Backend::AVX
52 | Backend::AVX2
53 | Backend::AVX512
54 | Backend::NEON
55 | Backend::WasmSIMD
56 | Backend::Auto,
57 )
58 | (
59 Backend::SSE2
60 | Backend::AVX
61 | Backend::AVX2
62 | Backend::AVX512
63 | Backend::NEON
64 | Backend::WasmSIMD
65 | Backend::Auto,
66 Backend::Scalar,
67 ) => self.scalar_vs_simd,
68 (Backend::GPU, Backend::GPU) => self.gpu_vs_gpu,
69 (
70 Backend::GPU,
71 Backend::Scalar
72 | Backend::SSE2
73 | Backend::AVX
74 | Backend::AVX2
75 | Backend::AVX512
76 | Backend::NEON
77 | Backend::WasmSIMD
78 | Backend::Auto,
79 )
80 | (
81 Backend::Scalar
82 | Backend::SSE2
83 | Backend::AVX
84 | Backend::AVX2
85 | Backend::AVX512
86 | Backend::NEON
87 | Backend::WasmSIMD
88 | Backend::Auto,
89 Backend::GPU,
90 ) => self.simd_vs_gpu,
91 (
93 Backend::SSE2
94 | Backend::AVX
95 | Backend::AVX2
96 | Backend::AVX512
97 | Backend::NEON
98 | Backend::WasmSIMD
99 | Backend::Auto,
100 Backend::SSE2
101 | Backend::AVX
102 | Backend::AVX2
103 | Backend::AVX512
104 | Backend::NEON
105 | Backend::WasmSIMD
106 | Backend::Auto,
107 ) => self.scalar_vs_simd,
108 }
109 }
110}
111
112#[derive(Debug, Clone)]
117pub struct BackendSelector {
118 gpu_threshold: usize,
120 parallel_threshold: usize,
122}
123
124impl Default for BackendSelector {
125 fn default() -> Self {
126 Self { gpu_threshold: 100_000, parallel_threshold: 1_000 }
127 }
128}
129
130impl BackendSelector {
131 #[must_use]
133 pub const fn new(gpu_threshold: usize, parallel_threshold: usize) -> Self {
134 Self { gpu_threshold, parallel_threshold }
135 }
136
137 #[must_use]
139 pub const fn gpu_threshold(&self) -> usize {
140 self.gpu_threshold
141 }
142
143 #[must_use]
145 pub const fn parallel_threshold(&self) -> usize {
146 self.parallel_threshold
147 }
148
149 #[must_use]
157 pub fn select_for_size(&self, size: usize, gpu_available: bool) -> BackendCategory {
158 if size < self.parallel_threshold {
159 BackendCategory::SimdOnly
160 } else if size < self.gpu_threshold {
161 BackendCategory::SimdParallel
162 } else if gpu_available {
163 BackendCategory::Gpu
164 } else {
165 BackendCategory::SimdParallel }
167 }
168
169 #[must_use]
171 pub fn is_at_gpu_boundary(&self, size: usize) -> bool {
172 size == self.gpu_threshold || size == self.gpu_threshold - 1
173 }
174
175 #[must_use]
177 pub fn is_at_parallel_boundary(&self, size: usize) -> bool {
178 size == self.parallel_threshold || size == self.parallel_threshold - 1
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
184pub enum BackendCategory {
185 SimdOnly,
187 SimdParallel,
189 Gpu,
191}
192
193#[derive(Debug, Clone)]
195pub struct SimulationTest {
196 pub backend: Backend,
198 pub input_size: usize,
200 pub cycle: u32,
202 pub seed: u64,
204}
205
206#[derive(Debug)]
211pub struct HeijunkaScheduler {
212 queue: VecDeque<SimulationTest>,
214 backends: Vec<Backend>,
216}
217
218impl HeijunkaScheduler {
219 #[must_use]
221 pub fn new(
222 backends: Vec<Backend>,
223 input_sizes: Vec<usize>,
224 cycles_per_backend: u32,
225 master_seed: u64,
226 ) -> Self {
227 let mut queue = VecDeque::new();
228
229 for size in &input_sizes {
231 for backend in &backends {
232 for cycle in 0..cycles_per_backend {
233 let seed = compute_seed(*backend, *size, cycle, master_seed);
234 queue.push_back(SimulationTest {
235 backend: *backend,
236 input_size: *size,
237 cycle,
238 seed,
239 });
240 }
241 }
242 }
243
244 Self { queue, backends: backends.clone() }
245 }
246
247 pub fn next_test(&mut self) -> Option<SimulationTest> {
249 self.queue.pop_front()
250 }
251
252 #[must_use]
254 pub fn remaining(&self) -> usize {
255 self.queue.len()
256 }
257
258 #[must_use]
260 pub fn backends(&self) -> &[Backend] {
261 &self.backends
262 }
263
264 #[must_use]
266 pub fn is_empty(&self) -> bool {
267 self.queue.is_empty()
268 }
269}
270
271pub(crate) fn compute_seed(backend: Backend, size: usize, cycle: u32, master_seed: u64) -> u64 {
273 let backend_bits = backend as u64;
274 let size_bits = size as u64;
275 let cycle_bits = u64::from(cycle);
276
277 master_seed
278 .wrapping_add(backend_bits.wrapping_mul(0x9E37_79B9_7F4A_7C15))
279 .wrapping_add(size_bits.wrapping_mul(0x6A09_E667_BB67_AE85))
280 .wrapping_add(cycle_bits.wrapping_mul(0x3C6E_F372_FE94_F82B))
281}
282
283#[derive(Debug, Clone)]
285pub struct SimTestConfigBuilder<S> {
286 seed: u64,
287 tolerance: BackendTolerance,
288 backends: Vec<Backend>,
289 input_sizes: Vec<usize>,
290 cycles: u32,
291 _state: PhantomData<S>,
292}
293
294pub struct NeedsSeed;
296pub struct Ready;
298
299impl Default for SimTestConfigBuilder<NeedsSeed> {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305impl SimTestConfigBuilder<NeedsSeed> {
306 #[must_use]
308 pub fn new() -> Self {
309 Self {
310 seed: 0,
311 tolerance: BackendTolerance::default(),
312 backends: vec![Backend::Scalar, Backend::AVX2],
313 input_sizes: vec![100, 1_000, 10_000, 100_000],
314 cycles: 10,
315 _state: PhantomData,
316 }
317 }
318
319 #[must_use]
321 pub fn seed(self, seed: u64) -> SimTestConfigBuilder<Ready> {
322 SimTestConfigBuilder {
323 seed,
324 tolerance: self.tolerance,
325 backends: self.backends,
326 input_sizes: self.input_sizes,
327 cycles: self.cycles,
328 _state: PhantomData,
329 }
330 }
331}
332
333impl SimTestConfigBuilder<Ready> {
334 #[must_use]
336 pub fn tolerance(mut self, tolerance: BackendTolerance) -> Self {
337 self.tolerance = tolerance;
338 self
339 }
340
341 #[must_use]
343 pub fn backends(mut self, backends: Vec<Backend>) -> Self {
344 self.backends = backends;
345 self
346 }
347
348 #[must_use]
350 pub fn input_sizes(mut self, sizes: Vec<usize>) -> Self {
351 self.input_sizes = sizes;
352 self
353 }
354
355 #[must_use]
357 pub fn cycles(mut self, cycles: u32) -> Self {
358 self.cycles = cycles;
359 self
360 }
361
362 #[must_use]
364 pub fn build(self) -> SimTestConfig {
365 SimTestConfig {
366 seed: self.seed,
367 tolerance: self.tolerance,
368 backends: self.backends,
369 input_sizes: self.input_sizes,
370 cycles: self.cycles,
371 }
372 }
373}
374
375#[derive(Debug, Clone)]
377pub struct SimTestConfig {
378 pub seed: u64,
380 pub tolerance: BackendTolerance,
382 pub backends: Vec<Backend>,
384 pub input_sizes: Vec<usize>,
386 pub cycles: u32,
388}
389
390impl SimTestConfig {
391 #[must_use]
393 pub fn builder() -> SimTestConfigBuilder<NeedsSeed> {
394 SimTestConfigBuilder::new()
395 }
396
397 #[must_use]
399 pub fn create_scheduler(&self) -> HeijunkaScheduler {
400 HeijunkaScheduler::new(
401 self.backends.clone(),
402 self.input_sizes.clone(),
403 self.cycles,
404 self.seed,
405 )
406 }
407}
408
409#[cfg(test)]
410mod tests;
411
412#[cfg(test)]
413mod proptests;