Skip to main content

cubecl_runtime/
kernel.rs

1use alloc::{
2    boxed::Box,
3    string::{String, ToString},
4    vec::Vec,
5};
6use core::{
7    fmt::Display,
8    marker::PhantomData,
9    sync::atomic::{AtomicI8, Ordering},
10};
11
12use cubecl_common::format::format_str;
13use cubecl_ir::{Id, Scope, StorageType, Value};
14use serde::{Deserialize, Serialize};
15
16use crate::{
17    compiler::{CompilationError, Compiler, CubeTask},
18    config::{CubeClRuntimeConfig, RuntimeConfig, compilation::CompilationLogLevel},
19    id::KernelId,
20    server::{CubeDim, ExecutionMode},
21};
22
23/// Implement this trait to create a [kernel definition](KernelDefinition).
24pub trait KernelMetadata: Send + Sync + 'static {
25    /// Name of the kernel for debugging.
26    fn name(&self) -> &'static str {
27        core::any::type_name::<Self>()
28    }
29
30    /// Identifier for the kernel, used for caching kernel compilation.
31    fn id(&self) -> KernelId;
32
33    /// Type of addresses in this kernel
34    fn address_type(&self) -> StorageType;
35}
36
37#[derive(Debug, Clone)]
38#[allow(missing_docs)]
39pub struct KernelDefinition {
40    pub buffers: Vec<KernelArg>,
41    pub tensor_maps: Vec<KernelArg>,
42    pub scalars: Vec<ScalarKernelArg>,
43    pub cube_dim: CubeDim,
44    pub body: Scope,
45    pub options: KernelOptions,
46}
47
48impl KernelDefinition {
49    /// Returns the total number of global buffers (including tensor maps)
50    pub fn num_global_buffers(&self) -> usize {
51        self.buffers.len() + self.tensor_maps.len()
52    }
53}
54
55#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
56/// Options for a specific kernel compilation
57pub struct KernelOptions {
58    /// The name of the kernel
59    pub kernel_name: String,
60    /// Whether to include debug symbols
61    pub debug_symbols: bool,
62    /// CUDA Cluster dim, if any
63    pub cluster_dim: Option<CubeDim>,
64}
65
66#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
67/// Global argument of a kernel.
68pub struct KernelArg {
69    /// The kernel id.
70    pub id: Id,
71    /// The value the argument is bound to.
72    pub value: Value,
73    /// Whether the argument has metadata.
74    pub has_extended_meta: bool,
75}
76
77#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
78#[allow(missing_docs)]
79pub struct ScalarKernelArg {
80    pub ty: StorageType,
81    pub count: usize,
82}
83
84#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
85#[allow(missing_docs)]
86pub enum Visibility {
87    Uniform,
88    Read,
89    ReadWrite,
90}
91
92/// A kernel, compiled in the target language
93pub struct CompiledKernel<C: Compiler> {
94    /// The name of the kernel entrypoint.
95    /// For example
96    ///
97    /// ```text
98    /// #[cube(launch)]
99    /// fn gelu_array<F: Float, R: Runtime>() {}
100    /// ```
101    ///
102    /// would have the entrypoint name "`gelu_array`".
103    pub entrypoint_name: String,
104
105    /// A fully qualified debug name of the kernel.
106    ///
107    /// For example
108    ///
109    /// ```text
110    /// #[cube(launch)]
111    /// fn gelu_array<F: Float, R: Runtime>() {}
112    /// ```
113    ///
114    /// would have a debug name such as
115    ///
116    /// ```text
117    /// gelu::gelu_array::GeluArray<
118    ///    cubecl_core::frontend::element::float::F32,
119    ///    cubecl_cuda::runtime::CudaRuntime,
120    /// >
121    /// ```
122    pub debug_name: Option<&'static str>,
123
124    /// Source code of the kernel
125    pub source: String,
126    /// In-memory representation of the kernel
127    pub repr: Option<C::Representation>,
128    /// Size of a cube for the compiled kernel
129    pub cube_dim: CubeDim,
130    /// Extra debugging information about the compiled kernel.
131    pub debug_info: Option<DebugInformation>,
132}
133
134/// Extra debugging information about the compiled kernel.
135#[derive(new)]
136pub struct DebugInformation {
137    /// The language tag of the source..
138    pub lang_tag: &'static str,
139    /// The compilation id.
140    pub id: KernelId,
141}
142
143/// Kernel that can be defined
144pub trait CubeKernel: KernelMetadata {
145    /// Define the kernel for compilation
146    fn define(&self) -> KernelDefinition;
147}
148
149/// Wraps a [`CubeKernel`] to allow it be compiled.
150pub struct KernelTask<C: Compiler, K: CubeKernel> {
151    kernel_definition: K,
152    _compiler: PhantomData<C>,
153}
154
155/// Generic [`CubeTask`] for compiling kernels
156pub struct CubeTaskKernel<C: Compiler> {
157    /// The inner compilation task being wrapped
158    pub task: Box<dyn CubeTask<C>>,
159}
160
161impl<C: Compiler, K: CubeKernel> KernelTask<C, K> {
162    /// Create a new kernel task
163    pub fn new(kernel_definition: K) -> Self {
164        Self {
165            kernel_definition,
166            _compiler: PhantomData,
167        }
168    }
169}
170
171impl<C: Compiler, K: CubeKernel> CubeTask<C> for KernelTask<C, K> {
172    fn compile(
173        &self,
174        compiler: &mut C,
175        compilation_options: &C::CompilationOptions,
176        mode: ExecutionMode,
177        addr_type: StorageType,
178    ) -> Result<CompiledKernel<C>, CompilationError> {
179        let gpu_ir = self.kernel_definition.define();
180        let entrypoint_name = gpu_ir.options.kernel_name.clone();
181        let cube_dim = gpu_ir.cube_dim;
182        let lower_level_ir = compiler.compile(gpu_ir, compilation_options, mode, addr_type)?;
183
184        Ok(CompiledKernel {
185            entrypoint_name,
186            debug_name: Some(core::any::type_name::<K>()),
187            source: lower_level_ir.to_string(),
188            repr: Some(lower_level_ir),
189            cube_dim,
190            debug_info: None,
191        })
192    }
193}
194
195impl<C: Compiler, K: CubeKernel> KernelMetadata for KernelTask<C, K> {
196    // Forward ID to underlying kernel definition.
197    fn id(&self) -> KernelId {
198        self.kernel_definition.id()
199    }
200
201    // Forward name to underlying kernel definition.
202    fn name(&self) -> &'static str {
203        self.kernel_definition.name()
204    }
205
206    fn address_type(&self) -> StorageType {
207        self.kernel_definition.address_type()
208    }
209}
210
211impl<C: Compiler> KernelMetadata for Box<dyn CubeTask<C>> {
212    // Deref and use existing ID.
213    fn id(&self) -> KernelId {
214        self.as_ref().id()
215    }
216
217    // Deref and use existing name.
218    fn name(&self) -> &'static str {
219        self.as_ref().name()
220    }
221
222    fn address_type(&self) -> StorageType {
223        self.as_ref().address_type()
224    }
225}
226
227static COMPILATION_LEVEL: AtomicI8 = AtomicI8::new(-1);
228
229fn compilation_level() -> u8 {
230    let compilation_level = COMPILATION_LEVEL.load(Ordering::Relaxed);
231    if compilation_level == -1 {
232        let val = match CubeClRuntimeConfig::get().compilation.logger.level {
233            CompilationLogLevel::Full => 2,
234            CompilationLogLevel::Disabled => 0,
235            CompilationLogLevel::Basic => 1,
236        };
237
238        COMPILATION_LEVEL.store(val, Ordering::Relaxed);
239        val as u8
240    } else {
241        compilation_level as u8
242    }
243}
244
245impl<C: Compiler> Display for CompiledKernel<C> {
246    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
247        match compilation_level() {
248            2 => self.format_full(f),
249            _ => self.format_basic(f),
250        }
251    }
252}
253
254impl<C: Compiler> CompiledKernel<C> {
255    fn format_basic(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
256        f.write_str("[Compiling kernel]")?;
257        if let Some(name) = self.debug_name {
258            if name.len() <= 32 {
259                f.write_fmt(format_args!(" {name}"))?;
260            } else {
261                f.write_fmt(format_args!(" {}", name.split('<').next().unwrap_or("")))?;
262            }
263        }
264
265        Ok(())
266    }
267
268    fn format_full(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
269        f.write_str("[START_KERNEL_COMPILATION]")?;
270
271        if let Some(name) = self.debug_name {
272            if name.len() <= 32 {
273                f.write_fmt(format_args!("\nname: {name}"))?;
274            } else {
275                let name = format_str(name, &[('<', '>')], false);
276                f.write_fmt(format_args!("\nname: {name}"))?;
277            }
278        }
279
280        if let Some(info) = &self.debug_info {
281            f.write_fmt(format_args!("\nid: {:#?}", info.id))?;
282        }
283
284        f.write_fmt(format_args!(
285            "
286source:
287```{}
288{}
289```
290[END_KERNEL_COMPILATION]
291",
292            self.debug_info
293                .as_ref()
294                .map(|info| info.lang_tag)
295                .unwrap_or(""),
296            self.source
297        ))
298    }
299}