1use alloc::{
2 boxed::Box,
3 string::{String, ToString},
4 vec::Vec,
5};
6use core::{
7 fmt::Display,
8 hash::Hash,
9 marker::PhantomData,
10 sync::atomic::{AtomicI8, Ordering},
11};
12
13use cubecl_common::{
14 format::format_str,
15 hash::{StableHash, StableHasher},
16};
17use cubecl_ir::{Id, Scope, StorageType, Value};
18use serde::{Deserialize, Serialize};
19
20use crate::{
21 compiler::{CompilationError, Compiler, CubeTask},
22 config::{CubeClRuntimeConfig, RuntimeConfig, compilation::CompilationLogLevel},
23 id::KernelId,
24 server::{CubeDim, ExecutionMode},
25};
26
27pub trait KernelMetadata: Send + Sync + 'static {
29 fn name(&self) -> &'static str {
31 core::any::type_name::<Self>()
32 }
33
34 fn id(&self) -> KernelId;
36
37 fn address_type(&self) -> StorageType;
39}
40
41#[derive(Debug, Clone)]
42#[allow(missing_docs)]
43pub struct KernelDefinition {
44 pub buffers: Vec<KernelArg>,
45 pub tensor_maps: Vec<KernelArg>,
46 pub scalars: Vec<ScalarKernelArg>,
47 pub cube_dim: CubeDim,
48 pub body: Scope,
49 pub options: KernelOptions,
50}
51
52impl KernelDefinition {
53 pub fn num_global_buffers(&self) -> usize {
55 self.buffers.len() + self.tensor_maps.len()
56 }
57
58 pub fn stable_hash(&self) -> StableHash {
69 let mut hasher = StableHasher::new();
70
71 self.buffers.hash(&mut hasher);
72 self.tensor_maps.hash(&mut hasher);
73 self.scalars.hash(&mut hasher);
74 self.cube_dim.hash(&mut hasher);
75 self.options.hash(&mut hasher);
76 self.body.hash(&mut hasher);
77
78 let state = self.body.state();
83 for (id, function) in state.functions.iter() {
84 id.hash(&mut hasher);
85 function.hash(&mut hasher);
86 }
87
88 hasher.finalize()
89 }
90}
91
92#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
93pub struct KernelOptions {
95 pub kernel_name: String,
97 pub debug_symbols: bool,
99 pub cluster_dim: Option<CubeDim>,
101}
102
103#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
104pub struct KernelArg {
106 pub id: Id,
108 pub value: Value,
110 pub has_extended_meta: bool,
112}
113
114#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
115#[allow(missing_docs)]
116pub struct ScalarKernelArg {
117 pub ty: StorageType,
118 pub count: usize,
119}
120
121#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
122#[allow(missing_docs)]
123pub enum Visibility {
124 Uniform,
125 Read,
126 ReadWrite,
127}
128
129pub struct CompiledKernel<C: Compiler> {
131 pub entrypoint_name: String,
141
142 pub debug_name: Option<&'static str>,
160
161 pub source: String,
163 pub repr: Option<C::Representation>,
165 pub cube_dim: CubeDim,
167 pub debug_info: Option<DebugInformation>,
169}
170
171#[derive(new)]
173pub struct DebugInformation {
174 pub lang_tag: &'static str,
176 pub id: KernelId,
178}
179
180pub trait CubeKernel: KernelMetadata {
182 fn define(&self) -> KernelDefinition;
184}
185
186pub struct KernelTask<C: Compiler, K: CubeKernel> {
188 kernel_definition: K,
189 _compiler: PhantomData<C>,
190}
191
192pub struct CubeTaskKernel<C: Compiler> {
194 pub task: Box<dyn CubeTask<C>>,
196}
197
198impl<C: Compiler, K: CubeKernel> KernelTask<C, K> {
199 pub fn new(kernel_definition: K) -> Self {
201 Self {
202 kernel_definition,
203 _compiler: PhantomData,
204 }
205 }
206}
207
208impl<C: Compiler, K: CubeKernel> CubeTask<C> for KernelTask<C, K> {
209 fn define(&self) -> KernelDefinition {
210 self.kernel_definition.define()
211 }
212
213 fn compile(
214 &self,
215 gpu_ir: KernelDefinition,
216 compiler: &mut C,
217 compilation_options: &C::CompilationOptions,
218 mode: ExecutionMode,
219 addr_type: StorageType,
220 ) -> Result<CompiledKernel<C>, CompilationError> {
221 let entrypoint_name = gpu_ir.options.kernel_name.clone();
222 let cube_dim = gpu_ir.cube_dim;
223 let lower_level_ir = compiler.compile(gpu_ir, compilation_options, mode, addr_type)?;
224
225 Ok(CompiledKernel {
226 entrypoint_name,
227 debug_name: Some(core::any::type_name::<K>()),
228 source: lower_level_ir.to_string(),
229 repr: Some(lower_level_ir),
230 cube_dim,
231 debug_info: None,
232 })
233 }
234}
235
236impl<C: Compiler, K: CubeKernel> KernelMetadata for KernelTask<C, K> {
237 fn id(&self) -> KernelId {
239 self.kernel_definition.id()
240 }
241
242 fn name(&self) -> &'static str {
244 self.kernel_definition.name()
245 }
246
247 fn address_type(&self) -> StorageType {
248 self.kernel_definition.address_type()
249 }
250}
251
252impl<C: Compiler> KernelMetadata for Box<dyn CubeTask<C>> {
253 fn id(&self) -> KernelId {
255 self.as_ref().id()
256 }
257
258 fn name(&self) -> &'static str {
260 self.as_ref().name()
261 }
262
263 fn address_type(&self) -> StorageType {
264 self.as_ref().address_type()
265 }
266}
267
268static COMPILATION_LEVEL: AtomicI8 = AtomicI8::new(-1);
269
270fn compilation_level() -> u8 {
271 let compilation_level = COMPILATION_LEVEL.load(Ordering::Relaxed);
272 if compilation_level == -1 {
273 let val = match CubeClRuntimeConfig::get().compilation.logger.level {
274 CompilationLogLevel::Full => 2,
275 CompilationLogLevel::Disabled => 0,
276 CompilationLogLevel::Basic => 1,
277 };
278
279 COMPILATION_LEVEL.store(val, Ordering::Relaxed);
280 val as u8
281 } else {
282 compilation_level as u8
283 }
284}
285
286impl<C: Compiler> Display for CompiledKernel<C> {
287 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
288 match compilation_level() {
289 2 => self.format_full(f),
290 _ => self.format_basic(f),
291 }
292 }
293}
294
295impl<C: Compiler> CompiledKernel<C> {
296 fn format_basic(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
297 f.write_str("[Compiling kernel]")?;
298 if let Some(name) = self.debug_name {
299 if name.len() <= 32 {
300 f.write_fmt(format_args!(" {name}"))?;
301 } else {
302 f.write_fmt(format_args!(" {}", name.split('<').next().unwrap_or("")))?;
303 }
304 }
305
306 Ok(())
307 }
308
309 fn format_full(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
310 f.write_str("[START_KERNEL_COMPILATION]")?;
311
312 if let Some(name) = self.debug_name {
313 if name.len() <= 32 {
314 f.write_fmt(format_args!("\nname: {name}"))?;
315 } else {
316 let name = format_str(name, &[('<', '>')], false);
317 f.write_fmt(format_args!("\nname: {name}"))?;
318 }
319 }
320
321 if let Some(info) = &self.debug_info {
322 f.write_fmt(format_args!("\nid: {:#?}", info.id))?;
323 }
324
325 f.write_fmt(format_args!(
326 "
327source:
328```{}
329{}
330```
331[END_KERNEL_COMPILATION]
332",
333 self.debug_info
334 .as_ref()
335 .map(|info| info.lang_tag)
336 .unwrap_or(""),
337 self.source
338 ))
339 }
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use cubecl_ir::{ElemType, Instruction, Operation, Type};
346
347 fn definition(body: Scope) -> KernelDefinition {
348 KernelDefinition {
349 buffers: Vec::new(),
350 tensor_maps: Vec::new(),
351 scalars: Vec::new(),
352 cube_dim: CubeDim::new_single(),
353 body,
354 options: KernelOptions::default(),
355 }
356 }
357
358 fn scope_with_copy() -> Scope {
360 let scope = Scope::root(false);
361 let local = scope.create_local_mut(Type::scalar(ElemType::Bool));
362 scope.register(Instruction::new(Operation::Copy(local), local));
363 scope
364 }
365
366 #[test]
367 fn hash_is_stable_across_calls() {
368 let definition = definition(scope_with_copy());
369
370 assert_eq!(definition.stable_hash(), definition.stable_hash());
371 }
372
373 #[test]
374 fn equivalent_definitions_hash_equal() {
375 let lhs = definition(scope_with_copy());
376 let rhs = definition(scope_with_copy());
377
378 assert_eq!(lhs.stable_hash(), rhs.stable_hash());
379 }
380
381 #[test]
382 fn body_change_changes_hash() {
383 let lhs = definition(scope_with_copy());
384
385 let scope = Scope::root(false);
386 let local = scope.create_local_mut(Type::scalar(ElemType::Bool));
387 scope.register(Instruction::new(
389 Operation::ConstructAggregate(alloc::vec![local]),
390 local,
391 ));
392 let rhs = definition(scope);
393
394 assert_ne!(lhs.stable_hash(), rhs.stable_hash());
395 }
396
397 #[test]
398 fn cube_dim_change_changes_hash() {
399 let lhs = definition(scope_with_copy());
400 let mut rhs = definition(scope_with_copy());
401 rhs.cube_dim = CubeDim::new_2d(2, 2);
402
403 assert_ne!(lhs.stable_hash(), rhs.stable_hash());
404 }
405
406 #[test]
410 fn outlined_function_change_changes_hash() {
411 fn with_function(extra_instruction: bool) -> KernelDefinition {
413 let outlined = Scope::root(false);
414 let local = outlined.create_local_mut(Type::scalar(ElemType::Bool));
415 outlined.register(Instruction::new(Operation::Copy(local), local));
416 if extra_instruction {
417 outlined.register(Instruction::new(
418 Operation::ConstructAggregate(alloc::vec![local]),
419 local,
420 ));
421 }
422
423 let definition = definition(Scope::root(false));
424 definition.body.create_function(Vec::new(), outlined);
425 definition
426 }
427
428 let lhs = with_function(false);
429 let rhs = with_function(true);
430
431 assert_eq!(
434 StableHasher::hash_one(&lhs.body),
435 StableHasher::hash_one(&rhs.body)
436 );
437 assert_ne!(lhs.stable_hash(), rhs.stable_hash());
438 }
439}