cubecl_runtime/
launched.rs1use crate::id::KernelId;
17use cubecl_common::hash::StableHash;
18use cubecl_environment::collections::{HashMap, HashSet};
19use cubecl_environment::sync::{AtomicUsize, LazyLock, Mutex, Ordering};
20
21static COLLECTING: AtomicUsize = AtomicUsize::new(0);
24
25static OPEN: LazyLock<Mutex<Collections>> = LazyLock::new(|| Mutex::new(Collections::default()));
27
28#[derive(Default)]
29struct Collections {
30 next: u64,
31 open: HashMap<u64, HashSet<StableHash>>,
32}
33
34#[must_use = "the kernels are collected until `finish`"]
38#[derive(Debug)]
39pub struct LaunchedKernels {
40 id: u64,
41}
42
43impl LaunchedKernels {
44 #[allow(
46 clippy::new_without_default,
47 reason = "starting a collection is not a value"
48 )]
49 pub fn new() -> Self {
50 let mut collections = OPEN.lock();
51 let id = collections.next;
52 collections.next += 1;
53 collections.open.insert(id, HashSet::default());
54 COLLECTING.fetch_add(1, Ordering::AcqRel);
56 Self { id }
57 }
58
59 pub fn finish(self) -> HashSet<StableHash> {
62 let launched = close(self.id);
63 core::mem::forget(self);
64 launched
65 }
66}
67
68impl Drop for LaunchedKernels {
69 fn drop(&mut self) {
70 close(self.id);
71 }
72}
73
74fn close(id: u64) -> HashSet<StableHash> {
76 let mut collections = OPEN.lock();
77 let launched = collections.open.remove(&id).unwrap_or_default();
78 COLLECTING.fetch_sub(1, Ordering::AcqRel);
79 launched
80}
81
82pub(crate) fn note(kernel: impl FnOnce() -> KernelId) {
85 if COLLECTING.load(Ordering::Relaxed) == 0 {
86 return;
87 }
88 let hash = kernel().stable_hash();
90 let mut collections = OPEN.lock();
91 for launched in collections.open.values_mut() {
92 launched.insert(hash);
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 struct Launched;
101
102 #[test]
103 fn a_collection_names_what_was_launched_while_it_was_open() {
104 let before = KernelId::new::<Launched>().info(0u32);
105 let during = KernelId::new::<Launched>().info(1u32);
106 note(|| before.clone());
107
108 let collection = LaunchedKernels::new();
109 note(|| during.clone());
110 note(|| during.clone());
111 let launched = collection.finish();
112
113 assert!(launched.contains(&during.stable_hash()));
115 assert!(!launched.contains(&before.stable_hash()));
116 }
117
118 #[test]
121 fn overlapping_collections_each_see_their_own_launches() {
122 let early = KernelId::new::<Launched>().info(10u32);
123 let late = KernelId::new::<Launched>().info(11u32);
124
125 let outer = LaunchedKernels::new();
126 note(|| early.clone());
127 let inner = LaunchedKernels::new();
128 note(|| late.clone());
129 let inner = inner.finish();
130 let outer = outer.finish();
131
132 assert!(inner.contains(&late.stable_hash()));
133 assert!(!inner.contains(&early.stable_hash()));
134 assert!(outer.contains(&early.stable_hash()) && outer.contains(&late.stable_hash()));
135 }
136}