cubecl_runtime/execution/
process_mode.rs1use super::{StatisticsCollector, StatisticsRecorder, StreamMode};
2use cubecl_environment::sync::{AtomicUsize, Mutex, Ordering};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum ProcessMode {
7 Execute,
9 CompileOnly,
13 CompileAndAutotune,
16}
17
18impl ProcessMode {
19 pub fn current() -> Self {
21 OpenState::load().mode()
22 }
23
24 pub fn stream_mode(self) -> StreamMode {
27 match self {
28 Self::Execute => StreamMode::Execute,
29 Self::CompileOnly | Self::CompileAndAutotune => StreamMode::Discard,
30 }
31 }
32}
33
34#[derive(Debug)]
75pub struct ProcessModeOverride {
76 mode: ProcessMode,
77}
78
79impl ProcessModeOverride {
80 pub fn new(mode: ProcessMode, collector: &StatisticsCollector) -> Self {
84 let mut active = ACTIVE.lock();
85 let open = OpenState::load();
86 match Opening::new(open, active.as_ref(), mode, collector) {
87 Opening::First => {
88 *active = Some(collector.recorder());
89 OpenState::first(mode).store();
90 }
91 Opening::Joins => open.joined().store(),
92 Opening::OtherCollector => {
93 drop(active);
94 panic!("an override cannot open while one of another collector is")
95 }
96 Opening::OtherMode => {
97 drop(active);
98 panic!(
99 "an override of {mode:?} cannot open while one of {:?} is",
100 open.mode()
101 )
102 }
103 }
104 Self { mode }
105 }
106
107 pub fn mode(&self) -> ProcessMode {
109 self.mode
110 }
111
112 pub(crate) fn active_recorder() -> Option<StatisticsRecorder> {
121 if !OpenState::load().is_open() {
122 return None;
123 }
124 ACTIVE.lock().clone()
125 }
126}
127
128impl Drop for ProcessModeOverride {
129 fn drop(&mut self) {
130 let mut active = ACTIVE.lock();
133 let left = OpenState::load().left();
134 if !left.is_open() {
135 *active = None;
136 }
137 left.store();
138 }
139}
140
141enum Opening {
143 First,
145 Joins,
147 OtherCollector,
149 OtherMode,
151}
152
153impl Opening {
154 fn new(
155 open: OpenState,
156 active: Option<&StatisticsRecorder>,
157 mode: ProcessMode,
158 collector: &StatisticsCollector,
159 ) -> Self {
160 let Some(active) = active.filter(|_| open.is_open()) else {
161 return Self::First;
162 };
163 if !active.tallies_into(collector) {
164 Self::OtherCollector
165 } else if open.mode() != mode {
166 Self::OtherMode
167 } else {
168 Self::Joins
169 }
170 }
171}
172
173#[derive(Debug, Clone, Copy)]
178struct OpenState(usize);
179
180static OPEN: AtomicUsize = AtomicUsize::new(0);
182static ACTIVE: Mutex<Option<StatisticsRecorder>> = Mutex::new(None);
184
185impl OpenState {
186 const LEVEL_BITS: u32 = 2;
188 const LEVEL_MASK: usize = (1 << Self::LEVEL_BITS) - 1;
190 const GUARD: usize = 1 << Self::LEVEL_BITS;
192
193 fn load() -> Self {
194 Self(OPEN.load(Ordering::Relaxed))
195 }
196
197 fn store(self) {
198 OPEN.store(self.0, Ordering::Relaxed);
199 }
200
201 fn first(mode: ProcessMode) -> Self {
206 let level = match mode {
207 ProcessMode::CompileOnly => 1,
208 ProcessMode::CompileAndAutotune => 2,
209 ProcessMode::Execute => 3,
210 };
211 Self(Self::GUARD | level)
212 }
213
214 fn is_open(self) -> bool {
215 self.0 != 0
216 }
217
218 fn mode(self) -> ProcessMode {
220 match self.0 & Self::LEVEL_MASK {
221 1 => ProcessMode::CompileOnly,
222 2 => ProcessMode::CompileAndAutotune,
223 _ => ProcessMode::Execute,
224 }
225 }
226
227 fn joined(self) -> Self {
229 Self(self.0 + Self::GUARD)
230 }
231
232 fn left(self) -> Self {
234 match self.0 - Self::GUARD {
235 rest if rest < Self::GUARD => Self(0),
236 rest => Self(rest),
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::super::statistics::{KernelHandling, KernelOutcome, TuneOutcome, TunePick};
244 use super::super::{CompilationStatistics, KernelRegistration, TuneRegistration};
245 use super::*;
246 use alloc::vec;
249 use alloc::vec::Vec;
250
251 #[test]
254 #[serial_test::serial]
255 #[should_panic(
256 expected = "an override of CompileOnly cannot open while one of CompileAndAutotune is"
257 )]
258 fn process_modes_do_not_overlap() {
259 let collector = StatisticsCollector::new();
260 let opened = ProcessModeOverride::new(ProcessMode::CompileAndAutotune, &collector);
261 let refused = ProcessModeOverride::new(ProcessMode::CompileOnly, &collector);
262 core::mem::drop(opened);
263 core::mem::drop(refused);
264 }
265
266 #[test]
269 #[serial_test::serial]
270 #[should_panic(expected = "cannot open while one of another collector is")]
271 fn two_collectors_do_not_overlap() {
272 let (first, second) = (StatisticsCollector::new(), StatisticsCollector::new());
273 let opened = ProcessModeOverride::new(ProcessMode::CompileOnly, &first);
274 let refused = ProcessModeOverride::new(ProcessMode::CompileOnly, &second);
275 core::mem::drop(opened);
276 core::mem::drop(refused);
277 }
278
279 #[test]
281 #[serial_test::serial]
282 fn a_refused_open_leaves_the_open_process_mode() {
283 let collector = StatisticsCollector::new();
284 let tune = ProcessModeOverride::new(ProcessMode::CompileAndAutotune, &collector);
285 let refused = std::panic::catch_unwind(|| {
286 ProcessModeOverride::new(ProcessMode::CompileOnly, &collector)
287 });
288 assert!(refused.is_err());
289 assert_eq!(ProcessMode::current(), ProcessMode::CompileAndAutotune);
290 core::mem::drop(tune);
291 assert_eq!(ProcessMode::current(), ProcessMode::Execute);
292 }
293
294 #[test]
297 #[serial_test::serial]
298 fn overrides_nest() {
299 let collector = StatisticsCollector::new();
300 {
301 let outer = ProcessModeOverride::new(ProcessMode::CompileOnly, &collector);
302 {
303 let inner = ProcessModeOverride::new(ProcessMode::CompileOnly, &collector);
304 core::mem::drop(inner);
305 }
306 assert_eq!(
307 ProcessMode::current(),
308 ProcessMode::CompileOnly,
309 "the outer is in force"
310 );
311 core::mem::drop(outer);
312 }
313 assert_eq!(ProcessMode::current(), ProcessMode::Execute);
314 assert!(ProcessModeOverride::active_recorder().is_none());
315 }
316
317 #[test]
322 #[serial_test::serial]
323 fn work_counts_to_the_collector_of_the_open_override() {
324 assert!(
325 KernelRegistration::register().is_none(),
326 "nothing counts outside an override"
327 );
328 let collector = StatisticsCollector::new();
329 let reader = collector.reader();
330
331 let compile = ProcessModeOverride::new(ProcessMode::CompileOnly, &collector);
332 let queued: Vec<KernelRegistration> = (0..4)
333 .filter_map(|_| KernelRegistration::register())
334 .collect();
335 let gathered = TuneRegistration::register().expect("an override is open");
336 core::mem::drop(compile);
337
338 let tune = ProcessModeOverride::new(ProcessMode::CompileAndAutotune, &collector);
340 let mut queued = queued.into_iter();
341 let mut compiled = queued.next().unwrap().settle(KernelOutcome::Compiled);
342 compiled.record(KernelHandling::Refused);
343 compiled.record(KernelHandling::Refused);
344 let mut loaded = queued.next().unwrap().settle(KernelOutcome::Loaded);
345 loaded.record(KernelHandling::Stored);
346 let _failed = queued.next().unwrap().settle(KernelOutcome::Failed);
347 let mut measured = gathered.settle(TuneOutcome::Measured);
348 measured.record(TunePick::Persisted);
349
350 let statistics = collector.statistics();
351 assert_eq!(
352 statistics.compilation,
353 CompilationStatistics {
354 registered: 4,
355 compiled: 1,
356 loaded: 1,
357 failed: 1,
358 abandoned: 0,
359 refused: 1,
360 stored: 1,
361 },
362 "a refusal told twice counts once"
363 );
364 assert_eq!(
365 statistics.autotune.settled(),
366 statistics.autotune.registered
367 );
368 assert_eq!(statistics.autotune.persisted, 1);
369 assert_eq!(reader.statistics(), statistics);
370 core::mem::drop(tune);
371
372 let execute = ProcessModeOverride::new(ProcessMode::Execute, &collector);
373 let _measured = TuneRegistration::register()
374 .expect("an execute override counts too")
375 .settle(TuneOutcome::Measured);
376 core::mem::drop(execute);
377 assert_eq!(collector.statistics().autotune.measured, 2);
378 drop(queued);
380 assert_eq!(collector.statistics().compilation.abandoned, 1);
381 }
382
383 #[test]
386 #[serial_test::serial]
387 fn a_dropped_registration_settles_as_abandoned() {
388 let collector = StatisticsCollector::new();
389 let compile = ProcessModeOverride::new(ProcessMode::CompileOnly, &collector);
390 drop(KernelRegistration::register());
391 drop(TuneRegistration::register());
392 let statistics = collector.statistics();
393 assert_eq!(
394 (
395 statistics.compilation.abandoned,
396 statistics.compilation.settled()
397 ),
398 (1, 1)
399 );
400 assert_eq!(
401 (statistics.autotune.abandoned, statistics.autotune.settled()),
402 (1, 1)
403 );
404 core::mem::drop(compile);
405 }
406}