Skip to main content

cubecl_runtime/
id.rs

1use alloc::format;
2use alloc::string::String;
3use alloc::sync::Arc;
4use core::{
5    any::{Any, TypeId},
6    fmt::Display,
7    hash::{Hash, Hasher},
8};
9use cubecl_common::{
10    format::{DebugRaw, format_str},
11    hash::{StableHash, StableHasher},
12};
13use cubecl_ir::AddressType;
14use derive_more::{Eq, PartialEq};
15
16use crate::server::{CubeDim, ExecutionMode};
17
18#[macro_export(local_inner_macros)]
19/// Create a new storage ID type.
20macro_rules! storage_id_type {
21    ($name:ident) => {
22        /// Storage ID.
23        #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
24        pub struct $name {
25            value: usize,
26        }
27
28        impl $name {
29            /// Create a new ID.
30            pub fn new() -> Self {
31                use core::sync::atomic::{AtomicUsize, Ordering};
32
33                static COUNTER: AtomicUsize = AtomicUsize::new(0);
34
35                let value = COUNTER.fetch_add(1, Ordering::Relaxed);
36                if value == usize::MAX {
37                    core::panic!("Memory ID overflowed");
38                }
39                Self { value }
40            }
41        }
42
43        impl Default for $name {
44            fn default() -> Self {
45                Self::new()
46            }
47        }
48    };
49}
50
51/// Identifies a backend-owned captured graph.
52///
53/// [`end_capture`](crate::server::ComputeServer::end_capture) records a graph,
54/// stores it in the backend's own registry, and returns this lightweight id;
55/// [`replay`](crate::server::ComputeServer::replay) and
56/// [`graph_destroy`](crate::server::ComputeServer::graph_destroy) take the id
57/// back to look the graph up. Referencing the graph by id keeps the raw
58/// executable inside the server — it never crosses the actor boundary in a box —
59/// exactly as memory is referenced by [`Handle`](crate::server::Handle) rather
60/// than by raw pointer.
61#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, PartialOrd, Ord)]
62pub struct GraphId {
63    value: u64,
64}
65
66impl GraphId {
67    /// Allocate a fresh, process-unique graph id.
68    pub fn new() -> Self {
69        use core::sync::atomic::{AtomicU64, Ordering};
70
71        static COUNTER: AtomicU64 = AtomicU64::new(0);
72
73        let value = COUNTER.fetch_add(1, Ordering::Relaxed);
74        if value == u64::MAX {
75            core::panic!("Graph ID overflowed");
76        }
77        Self { value }
78    }
79}
80
81impl Default for GraphId {
82    fn default() -> Self {
83        Self::new()
84    }
85}
86
87/// Kernel unique identifier.
88#[derive(Clone, PartialEq, Eq)]
89pub struct KernelId {
90    #[eq(skip)]
91    type_name: &'static str,
92    pub(crate) type_id: core::any::TypeId,
93    pub(crate) address_type: AddressType,
94    /// The [`CubeDim`] for this kernel
95    pub cube_dim: CubeDim,
96    pub(crate) mode: ExecutionMode,
97    pub(crate) info: Option<Info>,
98}
99
100impl Hash for KernelId {
101    fn hash<H: Hasher>(&self, state: &mut H) {
102        self.type_id.hash(state);
103        self.address_type.hash(state);
104        self.cube_dim.hash(state);
105        self.mode.hash(state);
106        self.info.hash(state);
107    }
108}
109
110impl core::fmt::Debug for KernelId {
111    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
112        let mut debug_str = f.debug_struct("KernelId");
113        debug_str
114            .field("type", &DebugRaw(self.type_name))
115            .field("address_type", &self.address_type);
116        debug_str.field("cube_dim", &self.cube_dim);
117        debug_str.field("mode", &self.mode);
118        match &self.info {
119            Some(info) => debug_str.field("info", info),
120            None => debug_str.field("info", &self.info),
121        };
122        debug_str.finish()
123    }
124}
125
126impl Display for KernelId {
127    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
128        match &self.info {
129            Some(info) => f.write_str(
130                format_str(
131                    format!("{info:?}").as_str(),
132                    &[('(', ')'), ('[', ']'), ('{', '}')],
133                    true,
134                )
135                .as_str(),
136            ),
137            None => f.write_str("No info"),
138        }
139    }
140}
141
142impl KernelId {
143    /// Create a new [kernel id](KernelId) for a type.
144    pub fn new<T: 'static>() -> Self {
145        Self {
146            type_id: core::any::TypeId::of::<T>(),
147            type_name: core::any::type_name::<T>(),
148            info: None,
149            cube_dim: CubeDim::new_single(),
150            mode: ExecutionMode::Checked,
151            address_type: Default::default(),
152        }
153    }
154
155    /// Render the key in a standard format that can be used between runs.
156    ///
157    /// Can be used as a persistent kernel cache key.
158    pub fn stable_format(&self) -> String {
159        format!(
160            "{}-{}-{:?}-{:?}-{:?}",
161            self.type_name, self.address_type, self.cube_dim, self.mode, self.info
162        )
163    }
164
165    /// Hash the key in a stable way that can be used between runs.
166    ///
167    /// Can be used as a persistent kernel cache key.
168    pub fn stable_hash(&self) -> StableHash {
169        let mut hasher = StableHasher::new();
170        self.type_name.hash(&mut hasher);
171        self.address_type.hash(&mut hasher);
172        self.cube_dim.hash(&mut hasher);
173        self.mode.hash(&mut hasher);
174        self.info.hash(&mut hasher);
175
176        hasher.finalize()
177    }
178
179    /// Add information to the [kernel id](KernelId).
180    ///
181    /// The information is used to differentiate kernels of the same kind but with different
182    /// configurations, which affect the generated code.
183    pub fn info<I: 'static + PartialEq + Eq + Hash + core::fmt::Debug + Send + Sync>(
184        mut self,
185        info: I,
186    ) -> Self {
187        self.info = Some(Info::new(info));
188        self
189    }
190
191    /// Set the [execution mode](ExecutionMode).
192    pub fn mode(&mut self, mode: ExecutionMode) {
193        self.mode = mode;
194    }
195
196    /// Set the [cube dim](CubeDim).
197    pub fn cube_dim(mut self, cube_dim: CubeDim) -> Self {
198        self.cube_dim = cube_dim;
199        self
200    }
201
202    /// Set the [`AddressType`].
203    pub fn address_type(mut self, addr_ty: AddressType) -> Self {
204        self.address_type = addr_ty;
205        self
206    }
207}
208
209impl core::fmt::Debug for Info {
210    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
211        self.value.fmt(f)
212    }
213}
214
215impl Info {
216    fn new<T: 'static + PartialEq + Eq + Hash + core::fmt::Debug + Send + Sync>(id: T) -> Self {
217        Self {
218            value: Arc::new(id),
219        }
220    }
221}
222
223/// This trait allows various types to be used as keys within a single data structure.
224///
225/// The downside is that the hashing method is hardcoded and cannot be configured using the
226/// [`core::hash::Hash`] function. The provided [Hasher] will be modified, but only based on the
227/// result of the hash from the [`DefaultHasher`].
228trait DynKey: core::fmt::Debug + Send + Sync {
229    fn dyn_type_id(&self) -> TypeId;
230    fn dyn_eq(&self, other: &dyn DynKey) -> bool;
231    fn dyn_hash(&self, state: &mut dyn Hasher);
232    fn dyn_hash_one(&self) -> StableHash;
233    fn as_any(&self) -> &dyn Any;
234}
235
236impl PartialEq for Info {
237    fn eq(&self, other: &Self) -> bool {
238        self.value.dyn_eq(other.value.as_ref())
239    }
240}
241
242/// Extra information
243#[derive(Clone)]
244pub(crate) struct Info {
245    value: Arc<dyn DynKey>,
246}
247impl Eq for Info {}
248
249impl Hash for Info {
250    fn hash<H: Hasher>(&self, state: &mut H) {
251        self.value.dyn_type_id().hash(state);
252        self.value.dyn_hash(state)
253    }
254}
255
256impl<T: 'static + PartialEq + Eq + Hash + core::fmt::Debug + Send + Sync> DynKey for T {
257    fn dyn_eq(&self, other: &dyn DynKey) -> bool {
258        if let Some(other) = other.as_any().downcast_ref::<T>() {
259            self == other
260        } else {
261            false
262        }
263    }
264
265    fn dyn_type_id(&self) -> TypeId {
266        TypeId::of::<T>()
267    }
268
269    fn dyn_hash(&self, state: &mut dyn Hasher) {
270        let hash = self.dyn_hash_one();
271        state.write_u128(hash);
272    }
273
274    fn dyn_hash_one(&self) -> StableHash {
275        let mut hasher = StableHasher::new();
276        self.hash(&mut hasher);
277        hasher.finalize()
278    }
279
280    fn as_any(&self) -> &dyn Any {
281        self
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288    use std::collections::HashSet;
289
290    #[test_log::test]
291    pub fn kernel_id_hash() {
292        let value_1 = KernelId::new::<()>().info("1");
293        let value_2 = KernelId::new::<()>().info("2");
294
295        let mut set = HashSet::new();
296
297        set.insert(value_1.clone());
298
299        assert!(set.contains(&value_1));
300        assert!(!set.contains(&value_2));
301    }
302}