Skip to main content

cubecl_server/
kernel.rs

1//! The compile step of a launch: what a [`CubeKernel`] becomes once a
2//! [`Compiler`] has seen it.
3
4pub use cubecl_runtime::kernel::*;
5
6use alloc::string::{String, ToString};
7use core::{
8    fmt::Display,
9    sync::atomic::{AtomicI8, Ordering},
10};
11
12use cubecl_common::format::format_str;
13use cubecl_environment::backtrace::BackTrace;
14
15use crate::{
16    compiler::{CompilationError, CompilationRecording, Compiler},
17    config::{CubeClRuntimeConfig, RuntimeConfig, compilation::CompilationLogLevel},
18    id::KernelId,
19    server::CubeDim,
20};
21
22/// A kernel, compiled in the target language
23pub struct CompiledKernel<C: Compiler> {
24    /// The name of the kernel entrypoint.
25    /// For example
26    ///
27    /// ```text
28    /// #[cube(launch)]
29    /// fn gelu_array<F: Float>() {}
30    /// ```
31    ///
32    /// would have the entrypoint name "`gelu_array`".
33    pub entrypoint_name: String,
34
35    /// A fully qualified debug name of the kernel.
36    ///
37    /// For example
38    ///
39    /// ```text
40    /// #[cube(launch)]
41    /// fn gelu_array<F: Float>() {}
42    /// ```
43    ///
44    /// would have a debug name such as
45    ///
46    /// ```text
47    /// gelu::gelu_array::GeluArray<
48    ///    cubecl_core::frontend::element::float::F32,
49    ///    cubecl_cuda::runtime::CudaRuntime,
50    /// >
51    /// ```
52    pub debug_name: Option<&'static str>,
53
54    /// Source code of the kernel
55    pub source: String,
56    /// In-memory representation of the kernel
57    pub repr: Option<C::Representation>,
58    /// Size of a cube for the compiled kernel
59    pub cube_dim: CubeDim,
60    /// What the kernel does with each buffer binding, by buffer position —
61    /// see [`BufferIOAttr`]. `None` when the compiler kept no answer, which the
62    /// launch path reads as every buffer both read and written: the
63    /// conservative direction, since over-claiming costs a spurious loud
64    /// failure and under-claiming costs a silent clean read of garbage.
65    pub io: Option<alloc::vec::Vec<BufferIOAttr>>,
66    /// Extra debugging information about the compiled kernel.
67    pub debug_info: Option<DebugInformation>,
68}
69
70/// Extra debugging information about the compiled kernel.
71#[derive(new)]
72pub struct DebugInformation {
73    /// The language tag of the source..
74    pub lang_tag: &'static str,
75    /// The compilation id.
76    pub id: KernelId,
77}
78
79impl<C: Compiler> CompiledKernel<C> {
80    /// Expand `kernel` and compile its definition with `compile`, telling
81    /// `recording` the IR and the source: the lowering every backend does,
82    /// whatever compiler and options it lowers with.
83    pub fn lower(
84        kernel: &dyn CubeKernel,
85        recording: &mut CompilationRecording,
86        compile: impl FnOnce(KernelDefinition) -> Result<Self, CompilationError>,
87    ) -> Result<Self, CompilationError> {
88        let definition = define_kernel(kernel)?;
89        recording.defined(&definition);
90        let compiled = compile(definition)?;
91        recording.source(&compiled.source);
92        Ok(compiled)
93    }
94
95    /// Compile `definition` with `compiler`, keeping `kernel`'s name as the
96    /// debug name of the result.
97    pub fn compile(
98        kernel: &dyn CubeKernel,
99        definition: KernelDefinition,
100        compiler: &mut C,
101        compilation_options: &C::CompilationOptions,
102    ) -> Result<Self, CompilationError> {
103        let entrypoint_name = definition.settings.kernel_name.clone();
104        let cube_dim = definition.settings.cube_dim.into();
105
106        // A hand-written kernel is already in the target language: there is no
107        // IR to hand the compiler, so neither analysis it produces exists.
108        // `io: None` reads as every buffer both read and written, which is the
109        // conservative direction.
110        if let Some(precompiled) = kernel.source() {
111            if precompiled.lang != compiler.lang_tag() {
112                return Err(CompilationError::Generic {
113                    reason: alloc::format!(
114                        "kernel `{}` carries {} source, but this compiler expects {}",
115                        kernel.name(),
116                        precompiled.lang,
117                        compiler.lang_tag()
118                    ),
119                    backtrace: BackTrace::capture(),
120                });
121            }
122            return Ok(CompiledKernel {
123                entrypoint_name: precompiled.entrypoint_name,
124                debug_name: Some(kernel.name()),
125                source: precompiled.source,
126                io: None,
127                repr: None,
128                cube_dim,
129                debug_info: None,
130            });
131        }
132
133        let lower_level_ir = compiler.compile(definition, compilation_options)?;
134
135        Ok(CompiledKernel {
136            entrypoint_name,
137            debug_name: Some(kernel.name()),
138            source: lower_level_ir.to_string(),
139            io: C::buffer_io(&lower_level_ir),
140            repr: Some(lower_level_ir),
141            cube_dim,
142            debug_info: None,
143        })
144    }
145}
146
147static COMPILATION_LEVEL: AtomicI8 = AtomicI8::new(-1);
148
149fn compilation_level() -> u8 {
150    let compilation_level = COMPILATION_LEVEL.load(Ordering::Relaxed);
151    if compilation_level == -1 {
152        let val = match CubeClRuntimeConfig::get().compilation.logger.level {
153            CompilationLogLevel::Full => 2,
154            CompilationLogLevel::Disabled => 0,
155            CompilationLogLevel::Basic => 1,
156        };
157
158        COMPILATION_LEVEL.store(val, Ordering::Relaxed);
159        val as u8
160    } else {
161        compilation_level as u8
162    }
163}
164
165impl<C: Compiler> Display for CompiledKernel<C> {
166    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
167        match compilation_level() {
168            2 => self.format_full(f),
169            _ => self.format_basic(f),
170        }
171    }
172}
173
174impl<C: Compiler> CompiledKernel<C> {
175    fn format_basic(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
176        f.write_str("[Compiling kernel]")?;
177        if let Some(name) = self.debug_name {
178            if name.len() <= 32 {
179                f.write_fmt(format_args!(" {name}"))?;
180            } else {
181                f.write_fmt(format_args!(" {}", name.split('<').next().unwrap_or("")))?;
182            }
183        }
184
185        Ok(())
186    }
187
188    fn format_full(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
189        f.write_str("[START_KERNEL_COMPILATION]")?;
190
191        if let Some(name) = self.debug_name {
192            if name.len() <= 32 {
193                f.write_fmt(format_args!("\nname: {name}"))?;
194            } else {
195                let name = format_str(name, &[('<', '>')], false);
196                f.write_fmt(format_args!("\nname: {name}"))?;
197            }
198        }
199
200        if let Some(info) = &self.debug_info {
201            f.write_fmt(format_args!("\nid: {:#?}", info.id))?;
202        }
203
204        f.write_fmt(format_args!(
205            "
206source:
207```{}
208{}
209```
210[END_KERNEL_COMPILATION]
211",
212            self.debug_info
213                .as_ref()
214                .map(|info| info.lang_tag)
215                .unwrap_or(""),
216            self.source
217        ))
218    }
219}