pub use cubecl_runtime::compiler::*;
use crate::id::KernelId;
use core::hash::Hash;
use cubecl_common::hash::{StableHash, StableHasher};
use cubecl_environment::collections::HashMap;
#[cfg(compilation_cache)]
use cubecl_environment::persistence::{CacheOption, Namespace, StoreOptions};
use cubecl_environment::persistence::{Store, StoreKey, StoreValue};
use cubecl_environment::records::{Record, RecordEffect, RecordLevel, Span};
pub type BuildId = Option<&'static [u8]>;
pub fn build_id_hash() -> StableHash {
StableHasher::hash_one(&buildid::build_id())
}
pub fn compilation_store<K: StoreKey, V: StoreValue>(
backend: &'static str,
fingerprint: impl AsRef<str>,
) -> Option<Store<K, V>> {
#[cfg(compilation_cache)]
{
use crate::config::RuntimeConfig;
if !crate::config::CubeClRuntimeConfig::get().compilation.cache {
return None;
}
Some(Store::new(
StoreOptions::new()
.storage(Namespace::scoped(backend, fingerprint))
.cache(CacheOption::Lazy),
))
}
#[cfg(not(compilation_cache))]
{
let _ = (backend, fingerprint);
None
}
}
pub fn store_compiled<K: StoreKey, V: StoreValue>(
store: &mut Store<K, V>,
key: K,
value: V,
) -> bool {
match store.insert(key, value) {
Ok(()) => true,
Err(err) => {
log::warn!("Unable to cache the compiled kernel: {}", err.reason());
false
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct CompilationRecord {
pub kernel: alloc::string::String,
pub key: KernelCacheKey,
pub ir: Option<alloc::string::String>,
pub outcome: CompilationOutcome,
pub duration: core::time::Duration,
pub source: Option<alloc::string::String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum CompilationOutcome {
Compiled,
Loaded,
Rekeyed,
}
impl Record for CompilationRecord {
const KIND: &'static str = "compilation";
}
#[derive(Debug)]
pub struct CompilationRecording {
open: Option<OpenRecording>,
}
#[derive(Debug)]
struct OpenRecording {
span: Span,
kernel: &'static str,
key: KernelCacheKey,
ir: Option<alloc::string::String>,
source: Option<alloc::string::String>,
}
impl CompilationRecording {
pub fn new(kernel_id: &KernelId) -> Self {
let open = Span::new().map(|span| OpenRecording {
span,
kernel: kernel_id.type_name(),
key: KernelCacheKey::new(kernel_id, build_id_hash()),
ir: None,
source: None,
});
Self { open }
}
pub fn defined(&mut self, definition: &crate::kernel::KernelDefinition) {
if let Some(open) = self.open.as_mut().filter(|_| keeps_code()) {
open.ir = Some(alloc::format!("{}", definition.body));
}
}
pub fn source(&mut self, source: &str) {
if let Some(open) = self.open.as_mut().filter(|_| keeps_code()) {
open.source = Some(source.into());
}
}
pub fn loaded(self) {
self.close(CompilationOutcome::Loaded, RecordEffect::Observed);
}
pub fn compiled(self, stored: bool) {
self.close(CompilationOutcome::Compiled, effect(stored));
}
pub fn rekeyed(self, stored: bool) {
self.close(CompilationOutcome::Rekeyed, effect(stored));
}
fn close(self, outcome: CompilationOutcome, effect: RecordEffect) {
let Some(open) = self.open else {
return;
};
let Some(duration) = open.span.elapsed() else {
return;
};
let record = CompilationRecord {
kernel: open.kernel.into(),
key: open.key,
ir: open.ir,
outcome,
duration,
source: open.source,
};
open.span.close(effect, &record);
}
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
)]
pub struct KernelCacheKey {
pub id: StableHash,
pub build_id: StableHash,
}
impl KernelCacheKey {
pub fn new(id: &KernelId, build_id: StableHash) -> Self {
Self {
id: id.stable_hash(),
build_id,
}
}
}
#[derive(Debug)]
pub struct CompilationCache<K, V> {
entries: HashMap<K, V>,
generation: Option<u32>,
}
impl<K: Eq + Hash, V> CompilationCache<K, V> {
pub fn mirroring<SK: StoreKey, SV: StoreValue>(store: &Option<Store<SK, SV>>) -> Self {
Self {
entries: HashMap::new(),
generation: store
.is_some()
.then(cubecl_environment::environment::generation),
}
}
pub fn unbound() -> Self {
Self {
entries: HashMap::new(),
generation: None,
}
}
pub fn get(&mut self, key: &K) -> Option<&V> {
self.reset_if_switched();
self.entries.get(key)
}
pub fn contains(&mut self, key: &K) -> bool {
self.reset_if_switched();
self.entries.contains_key(key)
}
pub fn insert(&mut self, key: K, value: V) {
self.reset_if_switched();
self.entries.insert(key, value);
}
fn reset_if_switched(&mut self) {
let Some(generation) = self.generation else {
return;
};
let current = cubecl_environment::environment::generation();
if current == generation {
return;
}
log::debug!("Environment switched, dropping the in-memory compilation cache");
self.generation = Some(current);
self.entries.clear();
}
}
fn keeps_code() -> bool {
cubecl_environment::records::level() == RecordLevel::Full
}
fn effect(stored: bool) -> RecordEffect {
if stored {
RecordEffect::Changed
} else {
RecordEffect::Observed
}
}