Repository: tracel-ai/cubecl
Pull request #665: Feat/cubecl config
Author: login: nathanielsimard
Created: 2025-05-05T13:45:40Z | Merged: 2025-05-05T16:08:48Z

## Validate your PR with burn.

It is important that you make sure that you don't introduce any bugs in burn. 

### Instructions

- [x] Create a new branch or fork of the [burn repo](https://github.com/Tracel-AI/burn)
- [x] Update the main [Cargo.toml](https://github.com/tracel-ai/burn/blob/40aa993fb5969351a006b560ec89da5119dc9721/Cargo.toml#L159=L161) with this PR hash.
- [x] Fix any broken tests or compilation errors in burn.
- [x] Submit a PR in burn with your fixes and link it here.


Burn PR: https://github.com/tracel-ai/burn/pull/3150

Example:

```toml
[profiling]
logger = { file = "path/to/file", log = "debug", append = true, level = "full" }

[autotune]
level = "balanced"
cache = "target"
logger = { log = "info", level = "minimal" }

[compilation]
logger = { level = "full" }
cache = "target"
```


Diff:
diff --git a/Cargo.toml b/Cargo.toml
index 1a5d392898..04ec163209 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,6 +26,7 @@ serde = { version = "1.0.204", default-features = false, features = [
     "alloc",
 ] } # alloc is for no_std, derive is needed
 serde_json = { version = "1.0.119", default-features = false }
+toml = "0.8.22"
 variadics_please = "1"
 
 dashmap = "5.5.3"
diff --git a/crates/cubecl-common/src/cache.rs b/crates/cubecl-common/src/cache.rs
index 3b8e7b9026..c70649c3a0 100644
--- a/crates/cubecl-common/src/cache.rs
+++ b/crates/cubecl-common/src/cache.rs
@@ -42,7 +42,8 @@ pub struct Cache<K, V> {
 pub struct CacheOption {
     separator: Option<Vec<u8>>,
     version: Option<String>,
-    root: Option<String>,
+    name: Option<String>,
+    root: Option<PathBuf>,
     lock_max_duration: Option<Duration>,
 }
 
@@ -81,25 +82,39 @@ impl CacheOption {
         self
     }
 
-    /// The root directory for the cache.
+    /// The name for the cache.
     ///
-    /// It will appear in the directory "$HOME/.cache/{root}/"
-    pub fn root<R: Into<String>>(mut self, root: R) -> Self {
-        self.root = Some(root.into());
+    /// It will appear in the directory "$HOME/.cache/{name}/"
+    pub fn name<R: Into<String>>(mut self, name: R) -> Self {
+        self.name = Some(name.into());
         self
     }
 
-    fn resolve(self) -> (Vec<u8>, String, String, Duration) {
+    /// The root path for the cache.
+    ///
+    /// It will appear in the directory "{path}/{name}/"
+    pub fn root<R: Into<PathBuf>>(mut self, path: R) -> Self {
+        self.root = Some(path.into());
+        self
+    }
+
+    fn resolve(self) -> (Vec<u8>, String, String, PathBuf, Duration) {
         let separator = self.separator.unwrap_or_else(|| b"\n".to_vec());
         let version = self
             .version
             .unwrap_or_else(|| std::env!("CARGO_PKG_VERSION").to_string());
-        let root = self.root.unwrap_or_else(|| "cubecl".to_string());
+        let name = self.name.unwrap_or_else(|| "cubecl".to_string());
         let duration = self
             .lock_max_duration
             .unwrap_or_else(|| Duration::from_secs(30));
+        let root = match self.root {
+            Some(root) => root,
+            None => dirs::home_dir()
+                .expect("An home directory should exist")
+                .join(".cache"),
+        };
 
-        (separator, root, version, duration)
+        (separator, name, version, root, duration)
     }
 }
 
@@ -114,8 +129,8 @@ impl<T: Serialize + DeserializeOwned + PartialEq + Eq + Clone> CacheValue for T
 impl<K: CacheKey, V: CacheValue> Cache<K, V> {
     /// Create a new cache and load the data from the provided path if it exists.
     pub fn new<P: AsRef<Path>>(path: P, option: CacheOption) -> Self {
-        let (separator, root, version, lock_max_duration) = option.resolve();
-        let path = get_persistent_cache_file_path(path, root, version);
+        let (separator, name, version, root, lock_max_duration) = option.resolve();
+        let path = get_persistent_cache_file_path(path, root, name, version);
 
         let mut this = Self {
             in_memory_cache: HashMap::new(),
@@ -271,22 +286,19 @@ impl<K: CacheKey, V: CacheValue> Display for Cache<K, V> {
 
 fn get_persistent_cache_file_path<P: AsRef<Path>>(
     path_partial: P,
-    root: String,
+    root: PathBuf,
+    name: String,
     version: String,
 ) -> PathBuf {
     let path_partial: &Path = path_partial.as_ref();
-    let home_dir = dirs::home_dir().expect("An home directory should exist");
     let add_extension = !path_partial.ends_with("json.log");
 
-    let mut path = home_dir
-        .join(".cache")
-        .join(sanitize_path_segment(&root))
+    let mut path = root
+        .join(sanitize_path_segment(&name))
         .join(sanitize_path_segment(&version));
 
     for segment in path_partial.iter() {
-        // Skip the root directory since it resets the previous path segments.
-        //
-        // "/path/file" == "path/file" => "$HOME/.cache/tracel-ai/path/file"
+        // Skip the name directory since it resets the previous path segments.
         if segment == "/" {
             continue;
         }
diff --git a/crates/cubecl-core/src/compute/builder.rs b/crates/cubecl-core/src/compute/builder.rs
index 25dd866f5f..ecb40c33dd 100644
--- a/crates/cubecl-core/src/compute/builder.rs
+++ b/crates/cubecl-core/src/compute/builder.rs
@@ -1,7 +1,9 @@
+use std::sync::atomic::{AtomicI8, Ordering};
+
 use alloc::collections::BTreeMap;
 
 use cubecl_ir::{ExpandElement, Scope, Variable, VariableKind};
-use cubecl_runtime::debug::DebugLogger;
+use cubecl_runtime::config::{GlobalConfig, compilation::CompilationLogLevel};
 
 use crate::ir::{Elem, Id, Item};
 use crate::prelude::KernelDefinition;
@@ -19,6 +21,8 @@ pub struct KernelBuilder {
     tensor_maps: Vec<Id>,
 }
 
+static DEBUG: AtomicI8 = AtomicI8::new(-1);
+
 impl KernelBuilder {
     /// Register a scalar and return the [element](ExpandElement) to be used for kernel expansion.
     pub fn scalar(&mut self, elem: Elem) -> ExpandElement {
@@ -118,8 +122,20 @@ impl KernelBuilder {
     }
 
     pub fn new() -> Self {
+        let debug = DEBUG.load(Ordering::Relaxed);
+        let debug = if debug == -1 {
+            let val = match GlobalConfig::get().compilation.logger.level {
+                CompilationLogLevel::Disabled => 0,
+                CompilationLogLevel::Full => 1,
+            };
+
+            DEBUG.store(val, Ordering::Relaxed);
+            val == 1
+        } else {
+            debug == 1
+        };
         Self {
-            scope: Scope::root(DebugLogger::default().is_activated()),
+            scope: Scope::root(debug),
             buffers: Default::default(),
             scalars: Default::default(),
             tensor_maps: Default::default(),
diff --git a/crates/cubecl-cuda/Cargo.toml b/crates/cubecl-cuda/Cargo.toml
index 7b094a7dfa..0ec50f845a 100644
--- a/crates/cubecl-cuda/Cargo.toml
+++ b/crates/cubecl-cuda/Cargo.toml
@@ -33,7 +33,7 @@ cubecl-runtime = { path = "../cubecl-runtime", version = "0.6.0", default-featur
 ] }
 
 bytemuck = { workspace = true }
-cudarc = { version = "0.16.2", features = [
+cudarc = { version = "0.16.3", features = [
     "std",
     "driver",
     "cuda-version-from-build-system",
diff --git a/crates/cubecl-cuda/src/compute/server.rs b/crates/cubecl-cuda/src/compute/server.rs
index aa5b3a4bd6..9def9b8b75 100644
--- a/crates/cubecl-cuda/src/compute/server.rs
+++ b/crates/cubecl-cuda/src/compute/server.rs
@@ -23,7 +23,7 @@ use cubecl_core::{
 use cubecl_runtime::memory_management::MemoryUsage;
 use cubecl_runtime::storage::BindingResource;
 use cubecl_runtime::{
-    debug::{DebugLogger, ProfileLevel},
+    logging::{ProfileLevel, ServerLogger},
     storage::ComputeStorage,
 };
 use cubecl_runtime::{
@@ -38,6 +38,7 @@ use cudarc::driver::sys::{
 use cudarc::driver::sys::{CUfunc_st, CUtensorMapInterleave};
 use std::collections::HashMap;
 use std::path::PathBuf;
+use std::str::FromStr;
 use std::{ffi::CStr, os::raw::c_void};
 use std::{ffi::CString, mem::MaybeUninit};
 
@@ -47,7 +48,7 @@ use cubecl_common::cache::{Cache, CacheOption};
 #[derive(Debug)]
 pub struct CudaServer {
     ctx: CudaContext,
-    logger: DebugLogger,
+    logger: ServerLogger,
 }
 
 #[derive(Debug)]
@@ -57,7 +58,7 @@ pub(crate) struct CudaContext {
     memory_management: MemoryManagement<CudaStorage>,
     module_names: HashMap<KernelId, CompiledKernel>,
     #[cfg(feature = "compilation-cache")]
-    ptx_cache: Cache<String, PtxCacheEntry>,
+    ptx_cache: Option<Cache<String, PtxCacheEntry>>,
     timestamps: KernelTimestamps,
     pub(crate) arch: CudaArchitecture,
     compilation_options: CompilationOptions,
@@ -563,7 +564,18 @@ impl CudaContext {
             memory_management,
             module_names: HashMap::new(),
             #[cfg(feature = "compilation-cache")]
-            ptx_cache: Cache::new("cuda/ptx", CacheOption::default()),
+            ptx_cache: {
+                let config = cubecl_runtime::config::GlobalConfig::get();
+                if let Some(cache) = &config.compilation.cache {
+                    let root = cache.root();
+                    Some(Cache::new(
+                        "ptx",
+                        CacheOption::default().name("cuda").root(root),
+                    ))
+                } else {
+                    None
+                }
+            },
             stream,
             arch,
             timestamps: KernelTimestamps::default(),
@@ -589,34 +601,39 @@ impl CudaContext {
         &mut self,
         kernel_id: &KernelId,
         kernel: Box<dyn CubeTask<CudaCompiler>>,
-        logger: &mut DebugLogger,
+        logger: &mut ServerLogger,
         mode: ExecutionMode,
     ) {
         #[cfg(feature = "compilation-cache")]
-        let name = kernel_id.stable_format();
+        let name = if let Some(cache) = &self.ptx_cache {
+            let name = kernel_id.stable_format();
+
+            if let Some(entry) = cache.get(&name) {
+                log::trace!("Using PTX cache");
+                self.load_ptx(
+                    entry.ptx.clone(),
+                    kernel_id.clone(),
+                    entry.entrypoint_name.clone(),
+                    CubeDim {
+                        x: entry.cube_dim.0,
+                        y: entry.cube_dim.1,
+                        z: entry.cube_dim.2,
+                    },
+                    entry.shared_mem_bytes,
+                );
+                return;
+            }
+            Some(name)
+        } else {
+            None
+        };
 
-        #[cfg(feature = "compilation-cache")]
-        if let Some(entry) = self.ptx_cache.get(&name) {
-            log::trace!("Using PTX cache");
-            self.load_ptx(
-                entry.ptx.clone(),
-                kernel_id.clone(),
-                entry.entrypoint_name.clone(),
-                CubeDim {
-                    x: entry.cube_dim.0,
-                    y: entry.cube_dim.1,
-                    z: entry.cube_dim.2,
-                },
-                entry.shared_mem_bytes,
-            );
-            return;
-        }
         log::trace!("Compiling kernel");
 
         let mut kernel_compiled =
             kernel.compile(&mut Default::default(), &self.compilation_options, mode);
 
-        if logger.is_activated() {
+        if logger.compilation_activated() {
             kernel_compiled.debug_info = Some(DebugInformation::new("cpp", kernel_id.clone()));
 
             if let Ok(formatted) = format_cpp(&kernel_compiled.source) {
@@ -639,13 +656,13 @@ impl CudaContext {
         #[cfg(feature = "compilation-cache")]
         let cluster_dim = compute_kernel.cluster_dim;
 
-        let kernel_compiled = logger.debug(kernel_compiled);
+        let kernel_compiled = logger.log_compilation(kernel_compiled);
 
         let ptx = unsafe {
             // I'd like to set the name to the kernel name, but keep getting UTF-8 errors so let's
             // leave it `None` for now
-            let program =
-                cudarc::nvrtc::result::create_program(kernel_compiled.source, None).unwrap();
+            let source = CString::from_str(&kernel_compiled.source).unwrap();
+            let program = cudarc::nvrtc::result::create_program(source.as_c_str(), None).unwrap();
             if cudarc::nvrtc::result::compile_program(program, &options).is_err() {
                 let log_raw = cudarc::nvrtc::result::get_program_log(program).unwrap();
                 let log_ptr = log_raw.as_ptr();
@@ -668,18 +685,20 @@ impl CudaContext {
             kernel_compiled.repr.unwrap();
 
         #[cfg(feature = "compilation-cache")]
-        self.ptx_cache
-            .insert(
-                name,
-                PtxCacheEntry {
-                    entrypoint_name: kernel_compiled.entrypoint_name.clone(),
-                    cube_dim: (cube_dim.x, cube_dim.y, cube_dim.z),
-                    shared_mem_bytes: repr.shared_memory_size(),
-                    cluster_dim: cluster_dim.map(|cluster| (cluster.x, cluster.y, cluster.z)),
-                    ptx: ptx.clone(),
-                },
-            )
-            .unwrap();
+        if let Some(cache) = &mut self.ptx_cache {
+            cache
+                .insert(
+                    name.unwrap(),
+                    PtxCacheEntry {
+                        entrypoint_name: kernel_compiled.entrypoint_name.clone(),
+                        cube_dim: (cube_dim.x, cube_dim.y, cube_dim.z),
+                        shared_mem_bytes: repr.shared_memory_size(),
+                        cluster_dim: cluster_dim.map(|cluster| (cluster.x, cluster.y, cluster.z)),
+                        ptx: ptx.clone(),
+                    },
+                )
+                .unwrap();
+        }
 
         self.load_ptx(
             ptx,
@@ -757,7 +776,7 @@ impl CudaContext {
 impl CudaServer {
     /// Create a new cuda server.
     pub(crate) fn new(ctx: CudaContext) -> Self {
-        let logger = DebugLogger::default();
+        let logger = ServerLogger::default();
         Self { ctx, logger }
     }
 
@@ -765,7 +784,7 @@ impl CudaServer {
         self.get_context_with_logger().0
     }
 
-    fn get_context_with_logger(&mut self) -> (&mut CudaContext, &mut DebugLogger) {
+    fn get_context_with_logger(&mut self) -> (&mut CudaContext, &mut ServerLogger) {
         unsafe {
             cudarc::driver::result::ctx::set_current(self.ctx.context).unwrap();
         };
diff --git a/crates/cubecl-hip/src/compute/server.rs b/crates/cubecl-hip/src/compute/server.rs
index 62272408e7..06dd458605 100644
--- a/crates/cubecl-hip/src/compute/server.rs
+++ b/crates/cubecl-hip/src/compute/server.rs
@@ -12,8 +12,8 @@ use cubecl_core::compute::DebugInformation;
 use cubecl_core::{Feature, server::Bindings};
 use cubecl_core::{KernelId, prelude::*};
 use cubecl_hip_sys::{HIP_SUCCESS, hiprtcResult_HIPRTC_SUCCESS};
-use cubecl_runtime::debug::{DebugLogger, ProfileLevel};
 use cubecl_runtime::kernel_timestamps::KernelTimestamps;
+use cubecl_runtime::logging::{ProfileLevel, ServerLogger};
 use cubecl_runtime::memory_management::MemoryUsage;
 use cubecl_runtime::storage::BindingResource;
 use cubecl_runtime::{
@@ -32,7 +32,7 @@ use cubecl_common::cache::{Cache, CacheOption};
 #[derive(Debug)]
 pub struct HipServer {
     ctx: HipContext,
-    logger: DebugLogger,
+    logger: ServerLogger,
 }
 
 #[derive(Debug)]
@@ -272,7 +272,7 @@ impl ComputeServer for HipServer {
                         name.to_string()
                     }
                 }
-                cubecl_runtime::debug::ProfileLevel::Full => {
+                cubecl_runtime::logging::ProfileLevel::Full => {
                     format!("{name}: {kernel_id} CubeCount {count:?}")
                 }
             };
@@ -363,7 +363,7 @@ impl HipContext {
         &mut self,
         kernel_id: &KernelId,
         cube_kernel: Box<dyn CubeTask<HipCompiler>>,
-        logger: &mut DebugLogger,
+        logger: &mut ServerLogger,
         mode: ExecutionMode,
     ) {
         #[cfg(feature = "compilation-cache")]
@@ -389,14 +389,14 @@ impl HipContext {
         let mut jitc_kernel =
             cube_kernel.compile(&mut Default::default(), &self.compilation_options, mode);
 
-        if logger.is_activated() {
+        if logger.compilation_activated() {
             jitc_kernel.debug_info = Some(DebugInformation::new("cpp", kernel_id.clone()));
 
             if let Ok(formatted) = format_cpp(&jitc_kernel.source) {
                 jitc_kernel.source = formatted;
             }
         }
-        let jitc_kernel = logger.debug(jitc_kernel);
+        let jitc_kernel = logger.log_compilation(jitc_kernel);
 
         // Create HIP Program
         let program = unsafe {
@@ -579,7 +579,7 @@ impl HipContext {
 impl HipServer {
     /// Create a new hip server.
     pub(crate) fn new(ctx: HipContext) -> Self {
-        let logger = DebugLogger::default();
+        let logger = ServerLogger::default();
         Self { ctx, logger }
     }
 
@@ -587,7 +587,7 @@ impl HipServer {
         self.get_context_with_logger().0
     }
 
-    fn get_context_with_logger(&mut self) -> (&mut HipContext, &mut DebugLogger) {
+    fn get_context_with_logger(&mut self) -> (&mut HipContext, &mut ServerLogger) {
         (&mut self.ctx, &mut self.logger)
     }
 }
diff --git a/crates/cubecl-runtime/Cargo.toml b/crates/cubecl-runtime/Cargo.toml
index 19b9ccabf7..f0b5495dff 100644
--- a/crates/cubecl-runtime/Cargo.toml
+++ b/crates/cubecl-runtime/Cargo.toml
@@ -24,7 +24,7 @@ default = [
     "cubecl-common/default",
 ]
 exclusive-memory-only = []
-std = ["cubecl-common/std"]
+std = ["cubecl-common/std", "toml", "dirs"]
 storage-bytes = []
 
 [dependencies]
@@ -38,6 +38,8 @@ hashbrown = { workspace = true }
 log = { workspace = true }
 variadics_please = { workspace = true }
 serde = { workspace = true }
+toml = { workspace = true, optional = true }
+dirs = { workspace = true, optional = true }
 
 # Persistent cache deps - has to match the autotune_persistent_cache cfg.
 [target.'cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))'.dependencies]
diff --git a/crates/cubecl-runtime/src/config/autotune.rs b/crates/cubecl-runtime/src/config/autotune.rs
new file mode 100644
index 0000000000..f7ef5b949c
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/autotune.rs
@@ -0,0 +1,61 @@
+#[cfg(feature = "std")]
+use super::cache::CacheConfig;
+use super::logger::{LogLevel, LoggerConfig};
+
+/// Configuration for autotuning in CubeCL.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct AutotuneConfig {
+    /// Logger configuration for autotune logs, using autotune-specific log levels.
+    #[serde(default)]
+    pub logger: LoggerConfig<AutotuneLogLevel>,
+
+    /// Autotune level, controlling the intensity of autotuning.
+    #[serde(default)]
+    pub level: AutotuneLevel,
+
+    /// Cache location for storing autotune results.
+    #[serde(default)]
+    #[cfg(feature = "std")]
+    pub cache: CacheConfig,
+}
+
+/// Log levels for autotune logging in CubeCL.
+#[derive(Default, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
+pub enum AutotuneLogLevel {
+    /// Autotune logging is disabled.
+    #[serde(rename = "disabled")]
+    Disabled,
+
+    /// Minimal autotune information is logged such as the fastest kernel selected and a few
+    /// statistics (default).
+    #[default]
+    #[serde(rename = "minimal")]
+    Minimal,
+
+    /// Full autotune details are logged.
+    #[serde(rename = "full")]
+    Full,
+}
+
+impl LogLevel for AutotuneLogLevel {}
+
+/// Autotune levels controlling the intensity of autotuning.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub enum AutotuneLevel {
+    /// Minimal autotuning effort.
+    #[serde(rename = "minimal")]
+    Minimal,
+
+    /// Balanced autotuning effort (default).
+    #[default]
+    #[serde(rename = "balanced")]
+    Balanced,
+
+    /// Increased autotuning effort.
+    #[serde(rename = "extensive")]
+    Extensive,
+
+    /// Maximum autotuning effort.
+    #[serde(rename = "full")]
+    Full,
+}
diff --git a/crates/cubecl-runtime/src/config/base.rs b/crates/cubecl-runtime/src/config/base.rs
new file mode 100644
index 0000000000..864de53742
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/base.rs
@@ -0,0 +1,207 @@
+use super::{autotune::AutotuneConfig, compilation::CompilationConfig, profiling::ProfilingConfig};
+use alloc::sync::Arc;
+
+/// Static mutex holding the global configuration, initialized as `None`.
+static CUBE_GLOBAL_CONFIG: spin::Mutex<Option<Arc<GlobalConfig>>> = spin::Mutex::new(None);
+
+/// Represents the global configuration for CubeCL, combining profiling, autotuning, and compilation settings.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct GlobalConfig {
+    /// Configuration for profiling CubeCL operations.
+    #[serde(default)]
+    pub profiling: ProfilingConfig,
+
+    /// Configuration for autotuning performance parameters.
+    #[serde(default)]
+    pub autotune: AutotuneConfig,
+
+    /// Configuration for compilation settings.
+    #[serde(default)]
+    pub compilation: CompilationConfig,
+}
+
+impl GlobalConfig {
+    /// Retrieves the current global configuration, loading it from the current directory if not set.
+    ///
+    /// If no configuration is set, it attempts to load one from `cubecl.toml` or `CubeCL.toml` in the
+    /// current directory or its parents. If no file is found, a default configuration is used.
+    ///
+    /// # Notes
+    ///
+    /// Calling this function is somewhat expensive, because of a global static lock. The config format
+    /// is optimized for parsing, not for consumption. A good practice is to use a local static atomic
+    /// value that you can populate with the appropriate value from the global config during
+    /// initialization of the atomic value.
+    ///
+    /// For example, the autotune level uses a [core::sync::atomic::AtomicI32] with an initial
+    /// value of `-1` to indicate an uninitialized state. It is then set to the proper value based on
+    /// the [super::autotune::AutotuneLevel] config. All subsequent fetches of the value are
+    /// lock-free.
+    pub fn get() -> Arc<Self> {
+        let mut state = CUBE_GLOBAL_CONFIG.lock();
+        if state.as_ref().is_none() {
+            #[cfg(feature = "std")]
+            let config = Self::from_current_dir();
+            #[cfg(feature = "std")]
+            let config = config.override_from_env();
+            #[cfg(not(feature = "std"))]
+            let config = Self::default();
+            *state = Some(Arc::new(config));
+        }
+
+        state.as_ref().cloned().unwrap()
+    }
+
+    #[cfg(feature = "std")]
+    /// Save the default configuration to the provided file path.
+    pub fn save_default<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<()> {
+        use std::io::Write;
+
+        let config = Self::get();
+        let content =
+            toml::to_string_pretty(config.as_ref()).expect("Default config should be serializable");
+        let mut file = std::fs::File::create(path)?;
+        file.write_all(content.as_bytes())?;
+
+        Ok(())
+    }
+
+    /// Sets the global configuration to the provided value.
+    ///
+    /// # Panics
+    /// Panics if the configuration has already been set or read, as it cannot be overridden.
+    ///
+    /// # Warning
+    /// This method must be called at the start of the program, before any calls to `get`. Attempting
+    /// to set the configuration after it has been initialized will cause a panic.
+    pub fn set(config: Self) {
+        let mut state = CUBE_GLOBAL_CONFIG.lock();
+        if state.is_some() {
+            panic!("Cannot set the global configuration multiple times.");
+        }
+        *state = Some(Arc::new(config));
+    }
+
+    #[cfg(feature = "std")]
+    /// Overrides configuration fields based on environment variables.
+    pub fn override_from_env(mut self) -> Self {
+        use super::compilation::CompilationLogLevel;
+        use crate::config::{
+            autotune::{AutotuneLevel, AutotuneLogLevel},
+            profiling::ProfilingLogLevel,
+        };
+
+        if let Ok(val) = std::env::var("CUBECL_DEBUG_LOG") {
+            self.compilation.logger.level = CompilationLogLevel::Full;
+            self.profiling.logger.level = ProfilingLogLevel::Medium;
+            self.autotune.logger.level = AutotuneLogLevel::Full;
+
+            match val.as_str() {
+                "stdout" => {
+                    self.compilation.logger.stdout = true;
+                }
+                "stderr" => {
+                    self.compilation.logger.stderr = true;
+                }
+                "1" | "true" => {
+                    let file_path = "/tmp/cubecl.log";
+                    self.compilation.logger.file = Some(file_path.into());
+                    self.profiling.logger.file = Some(file_path.into());
+                    self.autotune.logger.file = Some(file_path.into());
+                }
+                "0" | "false" => {
+                    self.compilation.logger.level = CompilationLogLevel::Disabled;
+                    self.profiling.logger.level = ProfilingLogLevel::Disabled;
+                    self.autotune.logger.level = AutotuneLogLevel::Disabled;
+                }
+                file_path => {
+                    self.compilation.logger.file = Some(file_path.into());
+                    self.profiling.logger.file = Some(file_path.into());
+                    self.autotune.logger.file = Some(file_path.into());
+                }
+            }
+        };
+
+        if let Ok(val) = std::env::var("CUBECL_DEBUG_OPTION") {
+            match val.as_str() {
+                "debug" => {
+                    self.compilation.logger.level = CompilationLogLevel::Full;
+                    self.profiling.logger.level = ProfilingLogLevel::Medium;
+                    self.autotune.logger.level = AutotuneLogLevel::Full;
+                }
+                "debug-full" => {
+                    self.compilation.logger.level = CompilationLogLevel::Full;
+                    self.profiling.logger.level = ProfilingLogLevel::Full;
+                    self.autotune.logger.level = AutotuneLogLevel::Full;
+                }
+                "profile" => {
+                    self.profiling.logger.level = ProfilingLogLevel::Basic;
+                }
+                "profile-medium" => {
+                    self.profiling.logger.level = ProfilingLogLevel::Medium;
+                }
+                "profile-full" => {
+                    self.profiling.logger.level = ProfilingLogLevel::Full;
+                }
+                _ => {}
+            }
+        };
+
+        if let Ok(val) = std::env::var("CUBECL_AUTOTUNE_LEVEL") {
+            match val.as_str() {
+                "minimal" | "0" => {
+                    self.autotune.level = AutotuneLevel::Minimal;
+                }
+                "balanced" | "1" => {
+                    self.autotune.level = AutotuneLevel::Balanced;
+                }
+                "extensive" | "2" => {
+                    self.autotune.level = AutotuneLevel::Extensive;
+                }
+                "full" | "3" => {
+                    self.autotune.level = AutotuneLevel::Full;
+                }
+                _ => {}
+            }
+        }
+
+        self
+    }
+
+    // Loads configuration from `cubecl.toml` or `CubeCL.toml` in the current directory or its parents.
+    //
+    // Traverses up the directory tree until a valid configuration file is found or the root is reached.
+    // Returns a default configuration if no file is found.
+    #[cfg(feature = "std")]
+    fn from_current_dir() -> Self {
+        let mut dir = std::env::current_dir().unwrap();
+
+        loop {
+            if let Ok(content) = Self::from_file_path(dir.join("cubecl.toml")) {
+                return content;
+            }
+
+            if let Ok(content) = Self::from_file_path(dir.join("CubeCL.toml")) {
+                return content;
+            }
+
+            if !dir.pop() {
+                break;
+            }
+        }
+
+        Self::default()
+    }
+
+    // Loads configuration from a specified file path.
+    #[cfg(feature = "std")]
+    fn from_file_path<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
+        let content = std::fs::read_to_string(path)?;
+        let config: Self = match toml::from_str(&content) {
+            Ok(val) => val,
+            Err(err) => panic!("The file provided doesn't have the right format => {err:?}"),
+        };
+
+        Ok(config)
+    }
+}
diff --git a/crates/cubecl-runtime/src/config/cache.rs b/crates/cubecl-runtime/src/config/cache.rs
new file mode 100644
index 0000000000..ed564efd39
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/cache.rs
@@ -0,0 +1,48 @@
+/// Cache location options.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub enum CacheConfig {
+    /// Stores cache in the current working directory.
+    #[serde(rename = "local")]
+    Local,
+
+    /// Stores cache in the project's `target` directory (default).
+    #[default]
+    #[serde(rename = "target")]
+    Target,
+
+    /// Stores cache in the system's local configuration directory.
+    #[serde(rename = "global")]
+    Global,
+
+    /// Stores cache in a user-specified file path.
+    #[serde(rename = "file")]
+    File(std::path::PathBuf),
+}
+
+impl CacheConfig {
+    /// Returns the root directory for the cache.
+    pub fn root(&self) -> std::path::PathBuf {
+        match self {
+            Self::Local => std::env::current_dir().unwrap(),
+            Self::Target => {
+                let dir_original = std::env::current_dir().unwrap();
+                let mut dir = dir_original.clone();
+
+                // Search for Cargo.toml in parent directories to locate project root.
+                loop {
+                    if let Ok(true) = std::fs::exists(dir.join("Cargo.toml")) {
+                        return dir.join("target");
+                    }
+
+                    if !dir.pop() {
+                        break;
+                    }
+                }
+
+                dir_original.join("target")
+            }
+            Self::Global => dirs::config_local_dir().unwrap(),
+            Self::File(path_buf) => path_buf.clone(),
+        }
+    }
+}
diff --git a/crates/cubecl-runtime/src/config/compilation.rs b/crates/cubecl-runtime/src/config/compilation.rs
new file mode 100644
index 0000000000..1a6d566705
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/compilation.rs
@@ -0,0 +1,18 @@
+#[cfg(feature = "std")]
+use super::cache::CacheConfig;
+use super::logger::{BinaryLogLevel, LoggerConfig};
+
+/// Configuration for compilation settings in CubeCL.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct CompilationConfig {
+    /// Logger configuration for compilation logs, using binary log levels.
+    #[serde(default)]
+    pub logger: LoggerConfig<CompilationLogLevel>,
+    /// Cache location for storing compiled kernels.
+    #[serde(default)]
+    #[cfg(feature = "std")]
+    pub cache: Option<CacheConfig>,
+}
+
+/// Type alias for the log level used in compilation logging.
+pub type CompilationLogLevel = BinaryLogLevel;
diff --git a/crates/cubecl-runtime/src/config/logger.rs b/crates/cubecl-runtime/src/config/logger.rs
new file mode 100644
index 0000000000..90e2638c7d
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/logger.rs
@@ -0,0 +1,403 @@
+use super::GlobalConfig;
+use crate::config::{
+    autotune::AutotuneLogLevel, compilation::CompilationLogLevel, profiling::ProfilingLogLevel,
+};
+use alloc::{string::ToString, sync::Arc, vec::Vec};
+use core::fmt::Display;
+use hashbrown::HashMap;
+
+#[cfg(feature = "std")]
+use std::{
+    fs::{File, OpenOptions},
+    io::{BufWriter, Write},
+    path::PathBuf,
+};
+
+/// Configuration for logging in CubeCL, parameterized by a log level type.
+///
+/// Note that you can use multiple loggers at the same time.
+#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
+#[serde(bound = "")]
+pub struct LoggerConfig<L: LogLevel> {
+    /// Path to the log file, if file logging is enabled (requires `std` feature).
+    #[serde(default)]
+    #[cfg(feature = "std")]
+    pub file: Option<PathBuf>,
+
+    /// Whether to append to the log file (true) or overwrite it (false). Defaults to true.
+    ///
+    /// ## Notes
+    ///
+    /// This parameter might get ignored based on other loggers config.
+    #[serde(default = "append_default")]
+    pub append: bool,
+
+    /// Whether to log to standard output.
+    #[serde(default)]
+    pub stdout: bool,
+
+    /// Whether to log to standard error.
+    #[serde(default)]
+    pub stderr: bool,
+
+    /// Optional crate-level logging configuration (e.g., info, debug, trace).
+    #[serde(default)]
+    pub log: Option<LogCrateLevel>,
+
+    /// The log level for this logger, determining verbosity.
+    #[serde(default)]
+    pub level: L,
+}
+
+/// Log levels using the `log` crate.
+///
+/// This enum defines verbosity levels for crate-level logging.
+#[derive(
+    Clone, Copy, Debug, Default, serde::Serialize, serde::Deserialize, Hash, PartialEq, Eq,
+)]
+pub enum LogCrateLevel {
+    /// Logs informational messages.
+    #[default]
+    #[serde(rename = "info")]
+    Info,
+
+    /// Logs debugging messages.
+    #[serde(rename = "debug")]
+    Debug,
+
+    /// Logs trace-level messages.
+    #[serde(rename = "trace")]
+    Trace,
+}
+
+impl LogLevel for u32 {}
+
+fn append_default() -> bool {
+    true
+}
+
+/// Trait for types that can be used as log levels in `LoggerConfig`.
+pub trait LogLevel:
+    serde::de::DeserializeOwned + serde::Serialize + Clone + Copy + core::fmt::Debug + Default
+{
+}
+
+/// Central logging utility for CubeCL, managing multiple log outputs.
+#[derive(Debug)]
+pub struct Logger {
+    /// Collection of logger instances (file, stdout, stderr, or crate-level).
+    loggers: Vec<LoggerKind>,
+
+    /// Indices of loggers used for compilation logging.
+    compilation_index: Vec<usize>,
+
+    /// Indices of loggers used for profiling logging.
+    profiling_index: Vec<usize>,
+
+    /// Indices of loggers used for autotuning logging.
+    autotune_index: Vec<usize>,
+
+    /// Global configuration for logging settings.
+    pub config: Arc<GlobalConfig>,
+}
+
+impl Default for Logger {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl Logger {
+    /// Creates a new `Logger` instance based on the global configuration.
+    ///
+    /// Initializes loggers for compilation, profiling, and autotuning based on the settings in
+    /// `GlobalConfig`.
+    ///
+    /// Note that creating a logger is quite expensive.
+    pub fn new() -> Self {
+        let config = GlobalConfig::get();
+        let mut loggers = Vec::new();
+        let mut compilation_index = Vec::new();
+        let mut profiling_index = Vec::new();
+        let mut autotune_index = Vec::new();
+
+        #[derive(Hash, PartialEq, Eq)]
+        enum LoggerId {
+            #[cfg(feature = "std")]
+            File(PathBuf),
+            #[cfg(feature = "std")]
+            Stdout,
+            #[cfg(feature = "std")]
+            Stderr,
+            LogCrate(LogCrateLevel),
+        }
+
+        let mut logger2index = HashMap::<LoggerId, usize>::new();
+
+        fn new_logger<S: Clone, ID: Fn(S) -> LoggerId, LG: Fn(S) -> LoggerKind>(
+            setting_index: &mut Vec<usize>,
+            loggers: &mut Vec<LoggerKind>,
+            logger2index: &mut HashMap<LoggerId, usize>,
+            state: S,
+            func_id: ID,
+            func_logger: LG,
+        ) {
+            let id = func_id(state.clone());
+
+            if let Some(index) = logger2index.get(&id) {
+                setting_index.push(*index);
+            } else {
+                let logger = func_logger(state);
+                let index = loggers.len();
+                logger2index.insert(id, index);
+                loggers.push(logger);
+                setting_index.push(index);
+            }
+        }
+
+        fn register_logger<L: LogLevel>(
+            #[allow(unused_variables)] kind: &LoggerConfig<L>, // not used in no-std
+            #[allow(unused_variables)] append: bool,           // not used in no-std
+            level: Option<LogCrateLevel>,
+            setting_index: &mut Vec<usize>,
+            loggers: &mut Vec<LoggerKind>,
+            logger2index: &mut HashMap<LoggerId, usize>,
+        ) {
+            #[cfg(feature = "std")]
+            if let Some(file) = &kind.file {
+                new_logger(
+                    setting_index,
+                    loggers,
+                    logger2index,
+                    (file, append),
+                    |(file, _append)| LoggerId::File(file.clone()),
+                    |(file, append)| LoggerKind::File(FileLogger::new(file, append)),
+                );
+            }
+
+            #[cfg(feature = "std")]
+            if kind.stdout {
+                new_logger(
+                    setting_index,
+                    loggers,
+                    logger2index,
+                    (),
+                    |_| LoggerId::Stdout,
+                    |_| LoggerKind::Stdout,
+                );
+            }
+
+            #[cfg(feature = "std")]
+            if kind.stderr {
+                new_logger(
+                    setting_index,
+                    loggers,
+                    logger2index,
+                    (),
+                    |_| LoggerId::Stderr,
+                    |_| LoggerKind::Stderr,
+                );
+            }
+
+            if let Some(level) = level {
+                new_logger(
+                    setting_index,
+                    loggers,
+                    logger2index,
+                    level,
+                    LoggerId::LogCrate,
+                    LoggerKind::Log,
+                );
+            }
+        }
+
+        if let CompilationLogLevel::Disabled = config.compilation.logger.level {
+        } else {
+            register_logger(
+                &config.compilation.logger,
+                config.compilation.logger.append,
+                config.compilation.logger.log,
+                &mut compilation_index,
+                &mut loggers,
+                &mut logger2index,
+            )
+        }
+
+        if let ProfilingLogLevel::Disabled = config.profiling.logger.level {
+        } else {
+            register_logger(
+                &config.profiling.logger,
+                config.profiling.logger.append,
+                config.profiling.logger.log,
+                &mut profiling_index,
+                &mut loggers,
+                &mut logger2index,
+            )
+        }
+
+        if let AutotuneLogLevel::Disabled = config.autotune.logger.level {
+        } else {
+            register_logger(
+                &config.autotune.logger,
+                config.autotune.logger.append,
+                config.autotune.logger.log,
+                &mut autotune_index,
+                &mut loggers,
+                &mut logger2index,
+            )
+        }
+
+        Self {
+            loggers,
+            compilation_index,
+            profiling_index,
+            autotune_index,
+            config,
+        }
+    }
+
+    /// Logs a message for compilation, directing it to all configured compilation loggers.
+    pub fn log_compilation<S: Display>(&mut self, msg: &S) {
+        let length = self.compilation_index.len();
+        if length > 1 {
+            let msg = msg.to_string();
+            for i in 0..length {
+                let index = self.compilation_index[i];
+                self.log(&msg, index)
+            }
+        } else if let Some(index) = self.compilation_index.first() {
+            self.log(&msg, *index)
+        }
+    }
+
+    /// Logs a message for profiling, directing it to all configured profiling loggers.
+    pub fn log_profiling<S: Display>(&mut self, msg: &S) {
+        let length = self.profiling_index.len();
+        if length > 1 {
+            let msg = msg.to_string();
+            for i in 0..length {
+                let index = self.profiling_index[i];
+                self.log(&msg, index)
+            }
+        } else if let Some(index) = self.profiling_index.first() {
+            self.log(&msg, *index)
+        }
+    }
+
+    /// Logs a message for autotuning, directing it to all configured autotuning loggers.
+    pub fn log_autotune<S: Display>(&mut self, msg: &S) {
+        let length = self.autotune_index.len();
+        if length > 1 {
+            let msg = msg.to_string();
+            for i in 0..length {
+                let index = self.autotune_index[i];
+                self.log(&msg, index)
+            }
+        } else if let Some(index) = self.autotune_index.first() {
+            self.log(&msg, *index)
+        }
+    }
+
+    /// Returns the current autotune log level from the global configuration.
+    pub fn log_level_autotune(&self) -> AutotuneLogLevel {
+        self.config.autotune.logger.level
+    }
+
+    /// Returns the current compilation log level from the global configuration.
+    pub fn log_level_compilation(&self) -> CompilationLogLevel {
+        self.config.compilation.logger.level
+    }
+
+    /// Returns the current profiling log level from the global configuration.
+    pub fn log_level_profiling(&self) -> ProfilingLogLevel {
+        self.config.profiling.logger.level
+    }
+
+    fn log<S: Display>(&mut self, msg: &S, index: usize) {
+        let logger = &mut self.loggers[index];
+        logger.log(msg);
+    }
+}
+
+/// Binary log level for enabling or disabling logging.
+///
+/// This enum provides a simple on/off toggle for logging.
+#[derive(Default, Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub enum BinaryLogLevel {
+    /// Logging is disabled.
+    #[default]
+    #[serde(rename = "disabled")]
+    Disabled,
+
+    /// Logging is fully enabled.
+    #[serde(rename = "full")]
+    Full,
+}
+
+impl LogLevel for BinaryLogLevel {}
+
+/// Represents different types of loggers.
+#[derive(Debug)]
+enum LoggerKind {
+    /// Logs to a file.
+    #[cfg(feature = "std")]
+    File(FileLogger),
+
+    /// Logs to standard output.
+    #[cfg(feature = "std")]
+    Stdout,
+
+    /// Logs to standard error.
+    #[cfg(feature = "std")]
+    Stderr,
+
+    /// Logs using the `log` crate with a specified level.
+    Log(LogCrateLevel),
+}
+
+impl LoggerKind {
+    fn log<S: Display>(&mut self, msg: &S) {
+        match self {
+            #[cfg(feature = "std")]
+            LoggerKind::File(file_logger) => file_logger.log(msg),
+            #[cfg(feature = "std")]
+            LoggerKind::Stdout => println!("{msg}"),
+            #[cfg(feature = "std")]
+            LoggerKind::Stderr => eprintln!("{msg}"),
+            LoggerKind::Log(level) => match level {
+                LogCrateLevel::Info => log::info!("{msg}"),
+                LogCrateLevel::Trace => log::debug!("{msg}"),
+                LogCrateLevel::Debug => log::trace!("{msg}"),
+            },
+        }
+    }
+}
+
+/// Logger that writes messages to a file.
+#[derive(Debug)]
+#[cfg(feature = "std")]
+struct FileLogger {
+    writer: BufWriter<File>,
+}
+
+#[cfg(feature = "std")]
+impl FileLogger {
+    // Creates a new file logger.
+    fn new(path: &PathBuf, append: bool) -> Self {
+        let file = OpenOptions::new()
+            .append(append)
+            .create(true)
+            .open(path)
+            .unwrap();
+
+        Self {
+            writer: BufWriter::new(file),
+        }
+    }
+
+    // Logs a message to the file, flushing the buffer to ensure immediate write.
+    fn log<S: Display>(&mut self, msg: &S) {
+        writeln!(self.writer, "{msg}").expect("Should be able to log debug information.");
+        self.writer.flush().expect("Can complete write operation.");
+    }
+}
diff --git a/crates/cubecl-runtime/src/config/mod.rs b/crates/cubecl-runtime/src/config/mod.rs
new file mode 100644
index 0000000000..ad2c586b45
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/mod.rs
@@ -0,0 +1,14 @@
+/// Autotune config module.
+pub mod autotune;
+/// Compilation config module.
+pub mod compilation;
+/// Profiling config module.
+pub mod profiling;
+
+mod base;
+#[cfg(feature = "std")]
+pub(crate) mod cache;
+mod logger;
+
+pub use base::*;
+pub use logger::Logger;
diff --git a/crates/cubecl-runtime/src/config/profiling.rs b/crates/cubecl-runtime/src/config/profiling.rs
new file mode 100644
index 0000000000..0cab8d1829
--- /dev/null
+++ b/crates/cubecl-runtime/src/config/profiling.rs
@@ -0,0 +1,32 @@
+use super::logger::{LogLevel, LoggerConfig};
+
+/// Configuration for profiling settings in CubeCL.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct ProfilingConfig {
+    /// Logger configuration for profiling logs, using profiling-specific log levels.
+    #[serde(default)]
+    pub logger: LoggerConfig<ProfilingLogLevel>,
+}
+
+/// Log levels for profiling in CubeCL.
+#[derive(Default, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
+pub enum ProfilingLogLevel {
+    /// Profiling logging is disabled.
+    #[default]
+    #[serde(rename = "disabled")]
+    Disabled,
+
+    /// Basic profiling information is logged.
+    #[serde(rename = "basic")]
+    Basic,
+
+    /// Medium level of profiling details is logged.
+    #[serde(rename = "medium")]
+    Medium,
+
+    /// Full profiling details are logged.
+    #[serde(rename = "full")]
+    Full,
+}
+
+impl LogLevel for ProfilingLogLevel {}
diff --git a/crates/cubecl-runtime/src/debug.rs b/crates/cubecl-runtime/src/debug.rs
deleted file mode 100644
index 00ce153645..0000000000
--- a/crates/cubecl-runtime/src/debug.rs
+++ /dev/null
@@ -1,433 +0,0 @@
-use core::fmt::Display;
-
-#[cfg(feature = "std")]
-use std::{
-    fs::{File, OpenOptions},
-    io::{BufWriter, Write},
-    path::PathBuf,
-};
-
-#[cfg(feature = "std")]
-use profile::*;
-
-#[cfg(feature = "std")]
-mod profile {
-    use core::fmt::Display;
-    use std::collections::HashMap;
-
-    #[derive(Debug, Default)]
-    pub(crate) struct Profiled {
-        durations: HashMap<String, ProfileItem>,
-    }
-
-    #[derive(Debug, Default, Clone)]
-    pub(crate) struct ProfileItem {
-        total_duration: core::time::Duration,
-        num_computed: usize,
-    }
-
-    impl Profiled {
-        pub fn update(&mut self, name: &String, duration: core::time::Duration) {
-            let name = if name.contains("\n") {
-                name.split("\n").next().unwrap()
-            } else {
-                name
-            };
-            if let Some(item) = self.durations.get_mut(name) {
-                item.update(duration);
-            } else {
-                self.durations.insert(
-                    name.to_string(),
-                    ProfileItem {
-                        total_duration: duration,
-                        num_computed: 1,
-                    },
-                );
-            }
-        }
-    }
-
-    impl Display for Profiled {
-        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
-            let header_name = "Name";
-            let header_num_computed = "Num Computed";
-            let header_duration = "Duration";
-            let header_ratio = "Ratio";
-
-            let mut ratio_len = header_ratio.len();
-            let mut name_len = header_name.len();
-            let mut num_computed_len = header_num_computed.len();
-            let mut duration_len = header_duration.len();
-
-            let mut total_duration = core::time::Duration::from_secs(0);
-            let mut total_computed = 0;
-
-            let mut items: Vec<(String, String, String, core::time::Duration)> = self
-                .durations
-                .iter()
-                .map(|(key, item)| {
-                    let name = key.clone();
-                    let num_computed = format!("{}", item.num_computed);
-                    let duration = format!("{:?}", item.total_duration);
-
-                    name_len = usize::max(name_len, name.len());
-                    num_computed_len = usize::max(num_computed_len, num_computed.len());
-                    duration_len = usize::max(duration_len, duration.len());
-
-                    total_duration += item.total_duration;
-                    total_computed += item.num_computed;
-
-                    (name, num_computed, duration, item.total_duration)
-                })
-                .collect();
-
-            let total_duration_fmt = format!("{:?}", total_duration);
-            let total_compute_fmt = format!("{:?}", total_computed);
-            let total_ratio_fmt = "100 %";
-
-            duration_len = usize::max(duration_len, total_duration_fmt.len());
-            num_computed_len = usize::max(num_computed_len, total_compute_fmt.len());
-            ratio_len = usize::max(ratio_len, total_ratio_fmt.len());
-
-            let line_length = name_len + duration_len + num_computed_len + ratio_len + 11;
-
-            let write_line = |char: &str, f: &mut core::fmt::Formatter<'_>| {
-                writeln!(f, "|{}| ", char.repeat(line_length))
-            };
-            items.sort_by(|(_, _, _, a), (_, _, _, b)| b.cmp(a));
-
-            write_line("⎺", f)?;
-
-            writeln!(
-                f,
-                "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
-                header_name,
-                header_duration,
-                header_num_computed,
-                header_ratio,
-                width_name = name_len,
-                width_duration = duration_len,
-                width_num_computed = num_computed_len,
-                width_ratio = ratio_len,
-            )?;
-
-            write_line("⎼", f)?;
-
-            for (name, num_computed, duration, num) in items {
-                let ratio = (100 * num.as_micros()) / total_duration.as_micros();
-
-                writeln!(
-                    f,
-                    "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
-                    name,
-                    duration,
-                    num_computed,
-                    format!("{} %", ratio),
-                    width_name = name_len,
-                    width_duration = duration_len,
-                    width_num_computed = num_computed_len,
-                    width_ratio = ratio_len,
-                )?;
-            }
-
-            write_line("⎼", f)?;
-
-            writeln!(
-                f,
-                "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
-                "Total",
-                total_duration_fmt,
-                total_compute_fmt,
-                total_ratio_fmt,
-                width_name = name_len,
-                width_duration = duration_len,
-                width_num_computed = num_computed_len,
-                width_ratio = ratio_len,
-            )?;
-
-            write_line("⎯", f)?;
-
-            Ok(())
-        }
-    }
-
-    impl ProfileItem {
-        pub fn update(&mut self, duration: core::time::Duration) {
-            self.total_duration += duration;
-            self.num_computed += 1;
-        }
-    }
-}
-
-#[derive(Debug, Copy, Clone)]
-/// Control the amount of info being display when profiling.
-pub enum ProfileLevel {
-    /// Provide only the summary information about kernels being run.
-    Basic,
-    /// Provide the summary information about kernels being run with their trace.
-    Medium,
-    /// Provide more information about kernels being run.
-    Full,
-}
-
-#[derive(Debug)]
-/// The various debugging options available.
-pub enum DebugOptions {
-    /// Debug the compilation.
-    Debug,
-    /// Profile each kernel executed.
-    #[cfg(feature = "std")]
-    Profile(ProfileLevel),
-    /// Enable all options.
-    #[cfg(feature = "std")]
-    All(ProfileLevel),
-}
-
-/// Debugging logger.
-#[derive(Debug, Default)]
-pub struct DebugLogger {
-    kind: DebugLoggerKind,
-    #[cfg(feature = "std")]
-    profiled: Profiled,
-}
-
-/// Debugging logger.
-#[derive(Debug)]
-pub enum DebugLoggerKind {
-    #[cfg(feature = "std")]
-    /// Log debugging information into a file.
-    File(DebugFileLogger, DebugOptions),
-    #[cfg(feature = "std")]
-    /// Log debugging information into standard output.
-    Stdout(DebugOptions),
-    /// Don't log debugging information.
-    None,
-}
-
-impl Default for DebugLoggerKind {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl DebugLogger {
-    /// Returns the profile level, none if profiling is deactivated.
-    pub fn profile_level(&self) -> Option<ProfileLevel> {
-        self.kind.profile_level()
-    }
-
-    /// Register a profiled task.
-    #[cfg_attr(not(feature = "std"), expect(unused))]
-    pub fn register_profiled<Name>(&mut self, name: Name, duration: core::time::Duration)
-    where
-        Name: Display,
-    {
-        #[cfg(feature = "std")]
-        {
-            let name = name.to_string();
-            self.profiled.update(&name, duration);
-
-            match self.kind.profile_level().unwrap_or(ProfileLevel::Basic) {
-                ProfileLevel::Basic => {}
-                _ => self.kind.register_profiled(name, duration),
-            }
-        }
-    }
-    /// Returns whether the debug logger is activated.
-    pub fn is_activated(&self) -> bool {
-        !matches!(self.kind, DebugLoggerKind::None)
-    }
-    /// Log the argument to a file when the debug logger is activated.
-    pub fn debug<I>(&mut self, arg: I) -> I
-    where
-        I: Display,
-    {
-        self.kind.debug(arg)
-    }
-
-    /// Show the profiling summary if activated and reset its state.
-    pub fn profile_summary(&mut self) {
-        #[cfg(feature = "std")]
-        if self.profile_level().is_some() {
-            let mut profiled = Default::default();
-            core::mem::swap(&mut self.profiled, &mut profiled);
-
-            match &mut self.kind {
-                #[cfg(feature = "std")]
-                DebugLoggerKind::File(file, _) => {
-                    file.log(&format!("{}", profiled));
-                }
-                #[cfg(feature = "std")]
-                DebugLoggerKind::Stdout(_) => println!("{profiled}"),
-                _ => (),
-            }
-        }
-    }
-}
-
-impl DebugLoggerKind {
-    #[cfg(not(feature = "std"))]
-    /// Create a new debug logger.
-    pub fn new() -> Self {
-        Self::None
-    }
-
-    /// Create a new debug logger.
-    #[cfg(feature = "std")]
-    pub fn new() -> Self {
-        let flag = match std::env::var("CUBECL_DEBUG_LOG") {
-            Ok(val) => val,
-            Err(_) => return Self::None,
-        };
-        let level = match std::env::var("CUBECL_DEBUG_OPTION") {
-            Ok(val) => val,
-            Err(_) => "debug|profile".to_string(),
-        };
-
-        let mut debug = false;
-        let mut profile = None;
-        level.as_str().split("|").for_each(|flag| match flag {
-            "debug" => {
-                debug = true;
-            }
-            "profile" => {
-                profile = Some(ProfileLevel::Basic);
-            }
-            "profile-medium" => {
-                profile = Some(ProfileLevel::Medium);
-            }
-            "profile-full" => {
-                profile = Some(ProfileLevel::Full);
-            }
-            _ => {}
-        });
-
-        let option = if let Some(level) = profile {
-            if debug {
-                DebugOptions::All(level)
-            } else {
-                DebugOptions::Profile(level)
-            }
-        } else {
-            DebugOptions::Debug
-        };
-
-        if let Ok(activated) = str::parse::<u8>(&flag) {
-            if activated == 1 {
-                return Self::File(DebugFileLogger::new(None), option);
-            } else {
-                return Self::None;
-            }
-        };
-
-        if let Ok(activated) = str::parse::<bool>(&flag) {
-            if activated {
-                return Self::File(DebugFileLogger::new(None), option);
-            } else {
-                return Self::None;
-            }
-        };
-
-        if let "stdout" = flag.as_str() {
-            Self::Stdout(option)
-        } else {
-            Self::File(DebugFileLogger::new(Some(&flag)), option)
-        }
-    }
-
-    /// Returns the profile level, none if profiling is deactivated.
-    #[cfg(feature = "std")]
-    fn profile_level(&self) -> Option<ProfileLevel> {
-        let option = match self {
-            DebugLoggerKind::File(_, option) => option,
-            DebugLoggerKind::Stdout(option) => option,
-            DebugLoggerKind::None => {
-                return None;
-            }
-        };
-        match option {
-            DebugOptions::Debug => None,
-            DebugOptions::Profile(level) => Some(*level),
-            DebugOptions::All(level) => Some(*level),
-        }
-    }
-
-    /// Returns the profile level, none if profiling is deactivated.
-    #[cfg(not(feature = "std"))]
-    fn profile_level(&self) -> Option<ProfileLevel> {
-        None
-    }
-
-    #[cfg(feature = "std")]
-    fn register_profiled(&mut self, name: String, duration: core::time::Duration) {
-        match self {
-            #[cfg(feature = "std")]
-            DebugLoggerKind::File(file, _) => {
-                file.log(&format!("| {duration:<10?} | {name}"));
-            }
-            #[cfg(feature = "std")]
-            DebugLoggerKind::Stdout(_) => println!("| {duration:<10?} | {name}"),
-            _ => (),
-        }
-    }
-
-    fn debug<I>(&mut self, arg: I) -> I
-    where
-        I: Display,
-    {
-        match self {
-            #[cfg(feature = "std")]
-            DebugLoggerKind::File(file, option) => {
-                match option {
-                    DebugOptions::Debug | DebugOptions::All(_) => {
-                        file.log(&arg);
-                    }
-                    DebugOptions::Profile(_) => (),
-                };
-                arg
-            }
-            #[cfg(feature = "std")]
-            DebugLoggerKind::Stdout(option) => {
-                match option {
-                    DebugOptions::Debug | DebugOptions::All(_) => {
-                        println!("{arg}");
-                    }
-                    DebugOptions::Profile(_) => (),
-                };
-                arg
-            }
-            DebugLoggerKind::None => arg,
-        }
-    }
-}
-
-/// Log debugging information into a file.
-#[cfg(feature = "std")]
-#[derive(Debug)]
-pub struct DebugFileLogger {
-    writer: BufWriter<File>,
-}
-
-#[cfg(feature = "std")]
-impl DebugFileLogger {
-    fn new(file_path: Option<&str>) -> Self {
-        let path = match file_path {
-            Some(path) => PathBuf::from(path),
-            None => PathBuf::from("/tmp/cubecl.log"),
-        };
-
-        let file = OpenOptions::new()
-            .append(true)
-            .create(true)
-            .open(path)
-            .unwrap();
-
-        Self {
-            writer: BufWriter::new(file),
-        }
-    }
-    fn log<S: Display>(&mut self, msg: &S) {
-        writeln!(self.writer, "{msg}").expect("Should be able to log debug information.");
-        self.writer.flush().expect("Can complete write operation.");
-    }
-}
diff --git a/crates/cubecl-runtime/src/lib.rs b/crates/cubecl-runtime/src/lib.rs
index 54a4234a56..b4cfde3413 100644
--- a/crates/cubecl-runtime/src/lib.rs
+++ b/crates/cubecl-runtime/src/lib.rs
@@ -25,6 +25,9 @@ pub mod server;
 /// Compute Storage module.
 pub mod storage;
 
+/// CubeCL config module.
+pub mod config;
+
 mod feature_set;
 
 mod base;
@@ -32,8 +35,8 @@ pub use base::*;
 pub use cubecl_common::benchmark;
 
 pub use feature_set::*;
-/// Debugging utilities.
-pub mod debug;
+/// Logging utilities to be used by a compute server.
+pub mod logging;
 
 /// TMA-related runtime types
 pub mod tma;
diff --git a/crates/cubecl-runtime/src/logging/mod.rs b/crates/cubecl-runtime/src/logging/mod.rs
new file mode 100644
index 0000000000..85646a0240
--- /dev/null
+++ b/crates/cubecl-runtime/src/logging/mod.rs
@@ -0,0 +1,6 @@
+mod profiling;
+pub use profiling::*;
+
+mod server;
+
+pub use server::*;
diff --git a/crates/cubecl-runtime/src/logging/profiling.rs b/crates/cubecl-runtime/src/logging/profiling.rs
new file mode 100644
index 0000000000..c857da18dc
--- /dev/null
+++ b/crates/cubecl-runtime/src/logging/profiling.rs
@@ -0,0 +1,161 @@
+use alloc::{
+    format,
+    string::{String, ToString},
+    vec::Vec,
+};
+use core::fmt::Display;
+use hashbrown::HashMap;
+
+#[derive(Debug, Default)]
+pub(crate) struct Profiled {
+    durations: HashMap<String, ProfileItem>,
+}
+
+#[derive(Debug, Default, Clone)]
+pub(crate) struct ProfileItem {
+    total_duration: core::time::Duration,
+    num_computed: usize,
+}
+
+#[derive(Debug, Copy, Clone)]
+/// Control the amount of info being display when profiling.
+pub enum ProfileLevel {
+    /// Provide only the summary information about kernels being run.
+    Basic,
+    /// Provide the summary information about kernels being run with their trace.
+    Medium,
+    /// Provide more information about kernels being run.
+    Full,
+}
+
+impl Profiled {
+    pub fn update(&mut self, name: &String, duration: core::time::Duration) {
+        let name = if name.contains("\n") {
+            name.split("\n").next().unwrap()
+        } else {
+            name
+        };
+        if let Some(item) = self.durations.get_mut(name) {
+            item.update(duration);
+        } else {
+            self.durations.insert(
+                name.to_string(),
+                ProfileItem {
+                    total_duration: duration,
+                    num_computed: 1,
+                },
+            );
+        }
+    }
+}
+
+impl Display for Profiled {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let header_name = "Name";
+        let header_num_computed = "Num Computed";
+        let header_duration = "Duration";
+        let header_ratio = "Ratio";
+
+        let mut ratio_len = header_ratio.len();
+        let mut name_len = header_name.len();
+        let mut num_computed_len = header_num_computed.len();
+        let mut duration_len = header_duration.len();
+
+        let mut total_duration = core::time::Duration::from_secs(0);
+        let mut total_computed = 0;
+
+        let mut items: Vec<(String, String, String, core::time::Duration)> = self
+            .durations
+            .iter()
+            .map(|(key, item)| {
+                let name = key.clone();
+                let num_computed = format!("{}", item.num_computed);
+                let duration = format!("{:?}", item.total_duration);
+
+                name_len = usize::max(name_len, name.len());
+                num_computed_len = usize::max(num_computed_len, num_computed.len());
+                duration_len = usize::max(duration_len, duration.len());
+
+                total_duration += item.total_duration;
+                total_computed += item.num_computed;
+
+                (name, num_computed, duration, item.total_duration)
+            })
+            .collect();
+
+        let total_duration_fmt = format!("{:?}", total_duration);
+        let total_compute_fmt = format!("{:?}", total_computed);
+        let total_ratio_fmt = "100 %";
+
+        duration_len = usize::max(duration_len, total_duration_fmt.len());
+        num_computed_len = usize::max(num_computed_len, total_compute_fmt.len());
+        ratio_len = usize::max(ratio_len, total_ratio_fmt.len());
+
+        let line_length = name_len + duration_len + num_computed_len + ratio_len + 11;
+
+        let write_line = |char: &str, f: &mut core::fmt::Formatter<'_>| {
+            writeln!(f, "|{}| ", char.repeat(line_length))
+        };
+        items.sort_by(|(_, _, _, a), (_, _, _, b)| b.cmp(a));
+
+        write_line("⎺", f)?;
+
+        writeln!(
+            f,
+            "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
+            header_name,
+            header_duration,
+            header_num_computed,
+            header_ratio,
+            width_name = name_len,
+            width_duration = duration_len,
+            width_num_computed = num_computed_len,
+            width_ratio = ratio_len,
+        )?;
+
+        write_line("⎼", f)?;
+
+        for (name, num_computed, duration, num) in items {
+            let ratio = (100 * num.as_micros()) / total_duration.as_micros();
+
+            writeln!(
+                f,
+                "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
+                name,
+                duration,
+                num_computed,
+                format!("{} %", ratio),
+                width_name = name_len,
+                width_duration = duration_len,
+                width_num_computed = num_computed_len,
+                width_ratio = ratio_len,
+            )?;
+        }
+
+        write_line("⎼", f)?;
+
+        writeln!(
+            f,
+            "| {:<width_name$} | {:<width_duration$} | {:<width_num_computed$} | {:<width_ratio$} |",
+            "Total",
+            total_duration_fmt,
+            total_compute_fmt,
+            total_ratio_fmt,
+            width_name = name_len,
+            width_duration = duration_len,
+            width_num_computed = num_computed_len,
+            width_ratio = ratio_len,
+        )?;
+
+        write_line("⎯", f)?;
+
+        Ok(())
+    }
+}
+
+impl ProfileItem {
+    pub fn update(&mut self, duration: core::time::Duration) {
+        self.total_duration += duration;
+        self.num_computed += 1;
+    }
+}
diff --git a/crates/cubecl-runtime/src/logging/server.rs b/crates/cubecl-runtime/src/logging/server.rs
new file mode 100644
index 0000000000..d7542ec472
--- /dev/null
+++ b/crates/cubecl-runtime/src/logging/server.rs
@@ -0,0 +1,172 @@
+use core::fmt::Display;
+
+use crate::config::Logger;
+use alloc::format;
+use alloc::string::{String, ToString};
+
+use super::{ProfileLevel, Profiled};
+
+/// Server logger.
+#[derive(Debug, Default)]
+pub struct ServerLogger {
+    kind: DebugLoggerKind,
+    profiled: Profiled,
+}
+
+#[derive(Debug)]
+/// The various logging options available.
+enum ServerLoggerOptions {
+    /// Debug only the compilation.
+    CompilationOnly,
+    /// Profile each kernel executed.
+    ProfileOnly(ProfileLevel),
+    /// Enable all options.
+    All(ProfileLevel),
+}
+
+/// Debugging logger.
+#[derive(Debug)]
+enum DebugLoggerKind {
+    /// Activated logger.
+    Activated(Logger, ServerLoggerOptions),
+    /// Don't log information.
+    None,
+}
+
+impl Default for DebugLoggerKind {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl ServerLogger {
+    /// Returns the profile level, none if profiling is deactivated.
+    pub fn profile_level(&self) -> Option<ProfileLevel> {
+        self.kind.profile_level()
+    }
+
+    /// Returns true if compilation info should be logged.
+    pub fn compilation_activated(&self) -> bool {
+        match &self.kind {
+            DebugLoggerKind::Activated(_, options) => match options {
+                ServerLoggerOptions::CompilationOnly => true,
+                ServerLoggerOptions::All(..) => true,
+                ServerLoggerOptions::ProfileOnly(..) => false,
+            },
+            DebugLoggerKind::None => false,
+        }
+    }
+
+    /// Register a profiled task.
+    pub fn register_profiled<Name>(&mut self, name: Name, duration: core::time::Duration)
+    where
+        Name: Display,
+    {
+        let name = name.to_string();
+        self.profiled.update(&name, duration);
+
+        match self.kind.profile_level().unwrap_or(ProfileLevel::Basic) {
+            ProfileLevel::Basic => {}
+            _ => self.kind.register_profiled(name, duration),
+        }
+    }
+
+    /// Log the argument to a file when the compilation logger is activated.
+    pub fn log_compilation<I>(&mut self, arg: I) -> I
+    where
+        I: Display,
+    {
+        self.kind.log_compilation(arg)
+    }
+
+    /// Show the profiling summary if activated and reset its state.
+    pub fn profile_summary(&mut self) {
+        if self.profile_level().is_some() {
+            let mut profiled = Default::default();
+            core::mem::swap(&mut self.profiled, &mut profiled);
+
+            if let DebugLoggerKind::Activated(logger, _) = &mut self.kind {
+                logger.log_profiling(&profiled);
+            }
+        }
+    }
+}
+
+impl DebugLoggerKind {
+    /// Create a new server logger.
+    pub fn new() -> Self {
+        use crate::config::{compilation::CompilationLogLevel, profiling::ProfilingLogLevel};
+
+        let logger = Logger::new();
+
+        let mut profile = None;
+
+        match logger.config.profiling.logger.level {
+            ProfilingLogLevel::Disabled => {}
+            ProfilingLogLevel::Basic => {
+                profile = Some(ProfileLevel::Basic);
+            }
+            ProfilingLogLevel::Medium => {
+                profile = Some(ProfileLevel::Medium);
+            }
+            ProfilingLogLevel::Full => {
+                profile = Some(ProfileLevel::Full);
+            }
+        };
+
+        let option = if let Some(level) = profile {
+            if let CompilationLogLevel::Full = logger.config.compilation.logger.level {
+                ServerLoggerOptions::All(level)
+            } else {
+                ServerLoggerOptions::ProfileOnly(level)
+            }
+        } else {
+            if let CompilationLogLevel::Disabled = logger.config.compilation.logger.level {
+                return Self::None;
+            }
+
+            ServerLoggerOptions::CompilationOnly
+        };
+
+        Self::Activated(logger, option)
+    }
+
+    /// Returns the profile level, none if profiling is deactivated.
+    fn profile_level(&self) -> Option<ProfileLevel> {
+        let option = match self {
+            DebugLoggerKind::Activated(_, option) => option,
+            DebugLoggerKind::None => {
+                return None;
+            }
+        };
+        match option {
+            ServerLoggerOptions::CompilationOnly => None,
+            ServerLoggerOptions::ProfileOnly(level) => Some(*level),
+            ServerLoggerOptions::All(level) => Some(*level),
+        }
+    }
+
+    fn register_profiled(&mut self, name: String, duration: core::time::Duration) {
+        if let DebugLoggerKind::Activated(logger, _) = self {
+            logger.log_profiling(&format!("| {duration:<10?} | {name}"));
+        }
+    }
+
+    fn log_compilation<I>(&mut self, arg: I) -> I
+    where
+        I: Display,
+    {
+        match self {
+            DebugLoggerKind::Activated(logger, option) => {
+                match option {
+                    ServerLoggerOptions::CompilationOnly | ServerLoggerOptions::All(_) => {
+                        logger.log_compilation(&arg);
+                    }
+                    ServerLoggerOptions::ProfileOnly(_) => (),
+                };
+                arg
+            }
+            DebugLoggerKind::None => arg,
+        }
+    }
+}
diff --git a/crates/cubecl-runtime/src/tune/tune_cache.rs b/crates/cubecl-runtime/src/tune/tune_cache.rs
index bb29a2c11d..bc57e9f4b5 100644
--- a/crates/cubecl-runtime/src/tune/tune_cache.rs
+++ b/crates/cubecl-runtime/src/tune/tune_cache.rs
@@ -76,11 +76,13 @@ impl<K: AutotuneKey> TuneCache<K> {
     ) -> Self {
         #[cfg(autotune_persistent_cache)]
         {
+            let root = crate::config::GlobalConfig::get().autotune.cache.root();
+            let options = cubecl_common::cache::CacheOption::default();
             let mut cache = TuneCache {
                 in_memory_cache: HashMap::new(),
                 persistent_cache: Cache::new(
-                    format!("autotune/{device_id}/{name}"),
-                    Default::default(),
+                    format!("{device_id}/{name}"),
+                    options.root(root).name("autotune"),
                 ),
             };
             cache.load();
diff --git a/crates/cubecl-runtime/src/tune/tuner.rs b/crates/cubecl-runtime/src/tune/tuner.rs
index 9f4f6594b9..8e28337359 100644
--- a/crates/cubecl-runtime/src/tune/tuner.rs
+++ b/crates/cubecl-runtime/src/tune/tuner.rs
@@ -11,6 +11,7 @@ use cubecl_common::benchmark::{BenchmarkComputations, BenchmarkDurations};
 
 use crate::channel::ComputeChannel;
 use crate::client::ComputeClient;
+use crate::config::{Logger, autotune::AutotuneLogLevel};
 use crate::server::ComputeServer;
 use crate::tune::{TuneBenchmark, TuneCache};
 
@@ -20,6 +21,7 @@ use super::{AutotuneKey, AutotuneOutput, TunableSet, TuneCacheResult};
 /// Executes autotune benchmarking and caching
 pub struct Tuner<K: AutotuneKey> {
     tune_cache: TuneCache<K>,
+    logger: Logger,
     channel: (Sender<AutotuneMessage<K>>, Receiver<AutotuneMessage<K>>),
     pub(crate) autotuning: HashSet<K>,
 }
@@ -35,14 +37,24 @@ pub struct AutotuneOutcome {
     index: usize,
     computation: BenchmarkComputations,
 }
+
+impl core::fmt::Display for AutotuneOutcome {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(
+            f,
+            "Autotune[{}] name {} => {:?}",
+            self.index, self.name, self.computation
+        )
+    }
+}
+
 enum AutotuneMessage<K> {
     Done {
         key: K,
         fastest_index: usize,
+        results: Vec<Result<AutotuneOutcome, String>>,
         #[cfg(autotune_persistent_cache)]
         checksum: String,
-        #[cfg(autotune_persistent_cache)]
-        results: Vec<Result<AutotuneOutcome, String>>,
         #[cfg(feature = "autotune-checks")]
         autotune_checks: alloc::boxed::Box<dyn FnOnce() + Send>,
     },
@@ -71,6 +83,7 @@ impl<K: AutotuneKey> Tuner<K> {
 
         Self {
             tune_cache: TuneCache::new(name, device_id),
+            logger: Logger::new(),
             channel,
             autotuning: HashSet::new(),
         }
@@ -84,6 +97,10 @@ impl<K: AutotuneKey> Tuner<K> {
     /// Fetch the fastest autotune operation index for an autotune key and validate the checksum.
     #[cfg(autotune_persistent_cache)]
     pub fn validate_checksum(&mut self, key: &K, checksum: &str) {
+        if let AutotuneLogLevel::Full = self.logger.log_level_autotune() {
+            self.logger
+                .log_autotune(&format!("validate checksum key={key}, checksum={checksum}"));
+        }
         self.tune_cache.validate_checksum(key, checksum)
     }
 
@@ -96,13 +113,62 @@ impl<K: AutotuneKey> Tuner<K> {
             AutotuneMessage::Done {
                 key,
                 fastest_index,
+                results,
                 #[cfg(autotune_persistent_cache)]
                 checksum,
                 #[cfg(autotune_persistent_cache)]
-                results,
                 #[cfg(feature = "autotune-checks")]
                     autotune_checks: check,
             } => {
+                match self.logger.log_level_autotune() {
+                    AutotuneLogLevel::Minimal => {
+                        let top_times = results
+                            .iter()
+                            .map(|r| {
+                                let time = r
+                                    .as_ref()
+                                    .map(|r| r.computation.median)
+                                    .unwrap_or(Duration::MAX);
+
+                                let index = r.as_ref().map(|r| r.index).unwrap_or_default();
+                                (index, time)
+                            })
+                            .take(3)
+                            .collect::<Vec<_>>();
+
+                        let result = results
+                            .first()
+                            .expect("At least one kernel needed.")
+                            .as_ref()
+                            .expect("At least one kernel has to succeed.");
+
+                        self.logger.log_autotune(&format!(
+                            "Fastest result {}-{key}. \n Top 3 times: {top_times:?}",
+                            result.name,
+                        ));
+                    }
+                    AutotuneLogLevel::Full => {
+                        let result = results
+                            .first()
+                            .expect("At least one kernel needed.")
+                            .as_ref()
+                            .expect("At least one kernel has to succeed.");
+
+                        self.logger
+                            .log_autotune(&format!("Fastest result {}-{key}.", result.name,));
+
+                        for result in results.iter() {
+                            match result {
+                                Ok(val) => {
+                                    self.logger.log_autotune(&format!("{val}"));
+                                }
+                                Err(_) => todo!(),
+                            }
+                        }
+                    }
+                    AutotuneLogLevel::Disabled => {}
+                };
+
                 self.tune_cache.cache_insert(key.clone(), fastest_index);
 
                 #[cfg(feature = "autotune-checks")]
@@ -155,10 +221,9 @@ impl<K: AutotuneKey> Tuner<K> {
                 break 'message AutotuneMessage::Done {
                     key,
                     fastest_index: autotunables[0].0,
+                    results: Vec::new(),
                     #[cfg(autotune_persistent_cache)]
                     checksum: tunables.compute_checksum(),
-                    #[cfg(autotune_persistent_cache)]
-                    results: Vec::new(),
                     #[cfg(feature = "autotune-checks")]
                     autotune_checks: Box::new(|| {}),
                 };
@@ -234,32 +299,12 @@ impl<K: AutotuneKey> Tuner<K> {
                     .as_ref()
                     .expect("At least one kernel has to succeed.");
 
-                let top_times = bench_results
-                    .iter()
-                    .map(|r| {
-                        let time = r
-                            .as_ref()
-                            .map(|r| r.computation.median)
-                            .unwrap_or(Duration::MAX);
-
-                        let index = r.as_ref().map(|r| r.index).unwrap_or_default();
-                        (index, time)
-                    })
-                    .take(3)
-                    .collect::<Vec<_>>();
-
-                log::info!(
-                    "Fastest result {}-{key_clone}. \n Top 3 times: {top_times:?}",
-                    result.name,
-                );
-
                 AutotuneMessage::Done {
                     key: key_clone,
                     fastest_index: result.index,
+                    results: bench_results,
                     #[cfg(autotune_persistent_cache)]
                     checksum,
-                    #[cfg(autotune_persistent_cache)]
-                    results: bench_results,
                     #[cfg(feature = "autotune-checks")]
                     autotune_checks: Box::new(|| {
                         check_autotune_outputs(checks_outputs);
diff --git a/crates/cubecl-runtime/src/tune/util.rs b/crates/cubecl-runtime/src/tune/util.rs
index ccef6fbf73..1191ebd8e2 100644
--- a/crates/cubecl-runtime/src/tune/util.rs
+++ b/crates/cubecl-runtime/src/tune/util.rs
@@ -1,5 +1,7 @@
 use core::sync::atomic::{AtomicI32, Ordering};
 
+use crate::config::GlobalConfig;
+
 /// Autotune levels:
 ///
 /// '0' => Minimal autotune: scaled anchor of '1.25'.
@@ -7,7 +9,6 @@ use core::sync::atomic::{AtomicI32, Ordering};
 /// '2' => More autotune: scaled anchor of '0.75'.
 /// '3' => Autotune everything without anchor.
 static AUTOTUNE_LEVEL: AtomicI32 = AtomicI32::new(-1);
-static DEFAULT_LEVEL: u32 = 1;
 
 /// Anchor a number to a power of the provided base.
 ///
@@ -43,20 +44,15 @@ pub fn anchor(x: usize, max: Option<usize>, min: Option<usize>, base: Option<usi
 fn load_autotune_level() -> u32 {
     let autotune_level = AUTOTUNE_LEVEL.load(Ordering::Relaxed);
     if autotune_level == -1 {
-        #[cfg(feature = "std")]
-        {
-            let level: u32 = std::env::var("CUBECL_AUTOTUNE_LEVEL")
-                .map(|value| {
-                    value
-                        .parse()
-                        .expect("'CUBECL_AUTOTUNE_LEVEL' should be an integer.")
-                })
-                .unwrap_or(DEFAULT_LEVEL);
-            AUTOTUNE_LEVEL.store(level as i32, Ordering::Relaxed);
-            level
-        }
-        #[cfg(not(feature = "std"))]
-        DEFAULT_LEVEL
+        let config = GlobalConfig::get();
+        let level = match config.autotune.level {
+            crate::config::autotune::AutotuneLevel::Minimal => 0,
+            crate::config::autotune::AutotuneLevel::Balanced => 1,
+            crate::config::autotune::AutotuneLevel::Extensive => 2,
+            crate::config::autotune::AutotuneLevel::Full => 3,
+        };
+        AUTOTUNE_LEVEL.store(level, Ordering::Relaxed);
+        level as u32
     } else {
         autotune_level as u32
     }
diff --git a/crates/cubecl-spirv/src/compiler.rs b/crates/cubecl-spirv/src/compiler.rs
index 5064480640..2137bce1ec 100644
--- a/crates/cubecl-spirv/src/compiler.rs
+++ b/crates/cubecl-spirv/src/compiler.rs
@@ -1,7 +1,7 @@
 use cubecl_common::ExecutionMode;
 use cubecl_core::{Metadata, WgpuCompilationOptions, ir as core, prelude::FastMath};
 use cubecl_opt::{BasicBlock, NodeIndex, Optimizer, OptimizerBuilder, Uniformity};
-use cubecl_runtime::debug::DebugLogger;
+use cubecl_runtime::config::{GlobalConfig, compilation::CompilationLogLevel};
 use std::{
     collections::HashSet,
     fmt::Debug,
@@ -79,6 +79,13 @@ impl<T: SpirvTarget> Clone for SpirvCompiler<T> {
     }
 }
 
+fn debug_symbols_activated() -> bool {
+    match GlobalConfig::get().compilation.logger.level {
+        CompilationLogLevel::Full => true,
+        CompilationLogLevel::Disabled => false,
+    }
+}
+
 impl<T: SpirvTarget> Default for SpirvCompiler<T> {
     fn default() -> Self {
         Self {
@@ -94,7 +101,7 @@ impl<T: SpirvTarget> Default for SpirvCompiler<T> {
             opt: Default::default(),
             uniformity: Default::default(),
             current_block: Default::default(),
-            debug_symbols: DebugLogger::default().is_activated(),
+            debug_symbols: debug_symbols_activated(),
             fp_math_mode: FPFastMathMode::NONE,
             visited: Default::default(),
             metadata: Default::default(),
@@ -189,7 +196,7 @@ impl<Target: SpirvTarget> SpirvCompiler<Target> {
     pub fn compile_kernel(&mut self, kernel: KernelDefinition) -> (Module, Optimizer) {
         let options = kernel.options.clone();
 
-        self.debug_symbols = DebugLogger::default().is_activated() || options.debug_symbols;
+        self.debug_symbols = debug_symbols_activated() || options.debug_symbols;
         self.fp_math_mode = match self.compilation_options.supports_fp_fast_math {
             true => convert_math_mode(options.fp_math_mode),
             false => FPFastMathMode::NONE,
diff --git a/crates/cubecl-wgpu/src/compute/server.rs b/crates/cubecl-wgpu/src/compute/server.rs
index 206dcf983b..0b23131fed 100644
--- a/crates/cubecl-wgpu/src/compute/server.rs
+++ b/crates/cubecl-wgpu/src/compute/server.rs
@@ -15,7 +15,7 @@ use cubecl_core::{
 };
 use cubecl_runtime::TimeMeasurement;
 use cubecl_runtime::{
-    debug::{DebugLogger, ProfileLevel},
+    logging::{ProfileLevel, ServerLogger},
     memory_management::MemoryDeviceProperties,
     server::{self, ComputeServer},
     storage::BindingResource,
@@ -28,7 +28,7 @@ use wgpu::ComputePipeline;
 pub struct WgpuServer {
     pub(crate) device: wgpu::Device,
     pipelines: HashMap<KernelId, Arc<ComputePipeline>>,
-    logger: DebugLogger,
+    logger: ServerLogger,
     duration_profiled: Option<Duration>,
     stream: WgpuStream,
     pub compilation_options: WgpuCompilationOptions,
@@ -48,7 +48,7 @@ impl WgpuServer {
         backend: wgpu::Backend,
         time_measurement: TimeMeasurement,
     ) -> Self {
-        let logger = DebugLogger::default();
+        let logger = ServerLogger::default();
 
         let stream = WgpuStream::new(
             device.clone(),
@@ -85,13 +85,13 @@ impl WgpuServer {
         let mut compiler = compiler(self.backend);
         let mut compile = compiler.compile(self, kernel, mode);
 
-        if self.logger.is_activated() {
+        if self.logger.compilation_activated() {
             compile.debug_info = Some(DebugInformation::new(
                 compiler.lang_tag(),
                 kernel_id.clone(),
             ));
         }
-        let compile = self.logger.debug(compile);
+        let compile = self.logger.log_compilation(compile);
         // /!\ Do not delete the following commented code.
         // This is usefull while working on the metal compiler.
         // Also the errors are printed nicely which is not the case when this is the runtime
diff --git a/crates/cubecl-wgpu/src/runtime.rs b/crates/cubecl-wgpu/src/runtime.rs
index 3e27ce6ab1..595592527d 100644
--- a/crates/cubecl-wgpu/src/runtime.rs
+++ b/crates/cubecl-wgpu/src/runtime.rs
@@ -12,7 +12,7 @@ use cubecl_runtime::{
     ComputeRuntime, TimeMeasurement,
     channel::MutexComputeChannel,
     client::ComputeClient,
-    debug::{DebugLogger, ProfileLevel},
+    logging::{ProfileLevel, ServerLogger},
 };
 use cubecl_runtime::{DeviceProperties, memory_management::HardwareProperties};
 use cubecl_runtime::{memory_management::MemoryDeviceProperties, storage::ComputeStorage};
@@ -302,8 +302,8 @@ async fn request_adapter(
     device: &WgpuDevice,
     backend: wgpu::Backend,
 ) -> (wgpu::Instance, wgpu::Adapter) {
-    let debug = DebugLogger::default();
-    let instance_flags = match (debug.profile_level(), debug.is_activated()) {
+    let debug = ServerLogger::default();
+    let instance_flags = match (debug.profile_level(), debug.compilation_activated()) {
         (Some(ProfileLevel::Full), _) => InstanceFlags::advanced_debugging(),
         (_, true) => InstanceFlags::debugging(),
         (_, false) => InstanceFlags::default(),
diff --git a/crates/cubecl/benches/matmul.rs b/crates/cubecl/benches/matmul.rs
index 8d91f04c9c..9e2f5837e3 100644
--- a/crates/cubecl/benches/matmul.rs
+++ b/crates/cubecl/benches/matmul.rs
@@ -6,6 +6,7 @@ use cubecl_linalg::matmul::{self, AsyncLoadingStrategy, components::MatmulPrecis
 use cubecl::benchmark::{Benchmark, TimingMethod};
 use cubecl::future;
 use cubecl_linalg::tensor::TensorHandle;
+use cubecl_runtime::config::GlobalConfig;
 
 impl<R: Runtime, MP: MatmulPrecision> Benchmark for MatmulBench<R, MP> {
     type Args = (TensorHandle<R, MP::EI>, TensorHandle<R, MP::EI>);
@@ -43,6 +44,7 @@ impl<R: Runtime, MP: MatmulPrecision> Benchmark for MatmulBench<R, MP> {
     }
 
     fn sync(&self) {
+        GlobalConfig::get();
         future::block_on(self.client.sync())
     }
 
diff --git a/crates/cubecl/src/lib.rs b/crates/cubecl/src/lib.rs
index 93015bdf48..565589a2cd 100644
--- a/crates/cubecl/src/lib.rs
+++ b/crates/cubecl/src/lib.rs
@@ -1,5 +1,7 @@
 pub use cubecl_core::*;
 
+pub use cubecl_runtime::config;
+
 #[cfg(feature = "wgpu")]
 pub use cubecl_wgpu as wgpu;
 


Reviews:
user: login: syl20bnr
body: Excellent ! Do you plan to add tests to validate the default/override mechanism etc... ?
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T15:18:28Z
user: login: louisfd
body: LGTM, minor comments only
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-05T15:31:46Z

Review comments:
diff_hunk: @@ -81,25 +82,39 @@ impl CacheOption {
         self
     }
 
-    /// The root directory for the cache.
+    /// The name for the cache.
     ///
-    /// It will appear in the directory "$HOME/.cache/{root}/"
-    pub fn root<R: Into<String>>(mut self, root: R) -> Self {
-        self.root = Some(root.into());
+    /// It will appear in the directory "$HOME/.cache/{name}/"
+    pub fn name<R: Into<String>>(mut self, name: R) -> Self {
+        self.name = Some(name.into());
         self
     }
 
-    fn resolve(self) -> (Vec<u8>, String, String, Duration) {
+    /// The root path for the cache.
+    ///
+    /// It will appear in the directory "{path}/{name}/"
+    pub fn root<R: Into<PathBuf>>(mut self, path: R) -> Self {
+        self.root = Some(path.into());
+        self
+    }
+
+    fn resolve(self) -> (Vec<u8>, String, String, PathBuf, Duration) {
         let separator = self.separator.unwrap_or_else(|| b"\n".to_vec());
         let version = self
             .version
             .unwrap_or_else(|| std::env!("CARGO_PKG_VERSION").to_string());
-        let root = self.root.unwrap_or_else(|| "cubecl".to_string());
+        let name = self.name.unwrap_or_else(|| "cubecl".to_string());
         let duration = self
             .lock_max_duration
             .unwrap_or_else(|| Duration::from_secs(30));
+        let root = match self.root {
+            Some(root) => root,
+            None => dirs::home_dir()
+                .expect("An home directory should exist")
path: crates/cubecl-common/src/cache.rs
user: login: louisfd
body: A home
created_at: 2025-05-05T15:19:47Z
author_association: MEMBER
diff_hunk: @@ -47,7 +48,7 @@ use cubecl_common::cache::{Cache, CacheOption};
 #[derive(Debug)]
 pub struct CudaServer {
     ctx: CudaContext,
-    logger: DebugLogger,
+    logger: SeverLogger,
path: crates/cubecl-cuda/src/compute/server.rs
user: login: louisfd
body: Sever -> Server
created_at: 2025-05-05T15:21:27Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+#[cfg(feature = "std")]
+use super::cache::CacheConfig;
+use super::logger::{LogLevel, LoggerConfig};
+
+/// Configuration for autotuning in CubeCL.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct AutotuneConfig {
+    /// Logger configuration for autotune logs, using autotune-specific log levels.
+    #[serde(default)]
+    pub logger: LoggerConfig<AutotuneLogLevel>,
+
+    /// Autotune level, controlling the intensity of autotuning.
+    #[serde(default)]
+    pub level: AutotuneLevel,
+
+    /// Cache location for storing autotune results.
+    #[serde(default)]
+    #[cfg(feature = "std")]
+    pub cache: CacheConfig,
+}
+
+/// Log levels for autotune logging in CubeCL.
+#[derive(Default, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
+pub enum AutotuneLogLevel {
+    /// Autotune logging is disabled.
+    #[serde(rename = "disabled")]
+    Disabled,
+
+    /// Minimal autotune information is logged (default).
path: crates/cubecl-runtime/src/config/autotune.rs
user: login: louisfd
body: What are the minimal informations?
created_at: 2025-05-05T15:23:23Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,60 @@
+#[cfg(feature = "std")]
+use super::cache::CacheConfig;
+use super::logger::{LogLevel, LoggerConfig};
+
+/// Configuration for autotuning in CubeCL.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub struct AutotuneConfig {
+    /// Logger configuration for autotune logs, using autotune-specific log levels.
+    #[serde(default)]
+    pub logger: LoggerConfig<AutotuneLogLevel>,
+
+    /// Autotune level, controlling the intensity of autotuning.
+    #[serde(default)]
+    pub level: AutotuneLevel,
+
+    /// Cache location for storing autotune results.
+    #[serde(default)]
+    #[cfg(feature = "std")]
+    pub cache: CacheConfig,
+}
+
+/// Log levels for autotune logging in CubeCL.
+#[derive(Default, Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
+pub enum AutotuneLogLevel {
+    /// Autotune logging is disabled.
+    #[serde(rename = "disabled")]
+    Disabled,
+
+    /// Minimal autotune information is logged (default).
+    #[default]
+    #[serde(rename = "minimal")]
+    Minimal,
+
+    /// Full autotune details are logged.
+    #[serde(rename = "full")]
+    Full,
+}
+
+impl LogLevel for AutotuneLogLevel {}
+
+/// Autotune levels controlling the intensity of autotuning.
+#[derive(Default, Clone, Debug, serde::Serialize, serde::Deserialize)]
+pub enum AutotuneLevel {
+    /// Minimal autotuning effort.
+    #[serde(rename = "minimal")]
+    Minimal,
+
+    /// Balanced autotuning effort (default).
+    #[default]
+    #[serde(rename = "balanced")]
+    Balanced,
+
+    /// Increased autotuning effort.
+    #[serde(rename = "extensive")]
+    Extensive,
+
+    /// Maximum autotuning effort.
+    #[serde(rename = "full")]
+    Exhaustive,
path: crates/cubecl-runtime/src/config/autotune.rs
user: login: louisfd
body: Weird to have Exhaustive renamed to "full"
created_at: 2025-05-05T15:23:57Z
author_association: MEMBER

PR comments:
user: login: nathanielsimard
created_at: 2025-05-05T15:42:10Z
body: > Excellent ! Do you plan to add tests to validate the default/override mechanism etc... ?

It's serde, I don't think I'll need tests to validate the generated code from serde.
author_association: MEMBER
user: login: syl20bnr
created_at: 2025-05-05T15:55:13Z
body: @nathanielsimard not talking about testing the serialization but the default / config / env var override behavior. There are 3 cases here:

- no config -> should load default
- config -> should load config
- config + env var -> should load env var
author_association: MEMBER
user: login: nathanielsimard
created_at: 2025-05-05T16:08:42Z
body: > @nathanielsimard not talking about testing the serialization but the default / config / env var override behavior. There are 3 cases here:
> 
> * no config -> should load default
> * config -> should load config
> * config + env var -> should load env var

Yeah we could make those, but I'm confident it works without those tests. Real IO tests are a pain to execute, they need synchronization and all, that setup would be more complicated than the code being tested (the code tested is trivial). If we add more logics we could add tests to validate that logic.
author_association: MEMBER