use crate::id::KernelId;
use cubecl_common::hash::{StableHash, StableHasher};
#[cfg(compilation_cache)]
use cubecl_environment::persistence::{CacheOption, Namespace, StoreOptions};
use cubecl_environment::persistence::{Store, StoreKey, StoreValue};
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, 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,
}
}
pub fn from_artifact_id<V: core::hash::Hash>(
id: &super::ArtifactId<V>,
build_id: StableHash,
) -> Self {
use core::hash::Hash;
let mut hasher = StableHasher::new();
id.kernel.stable_hash().hash(&mut hasher);
id.variant.hash(&mut hasher);
Self {
id: hasher.finalize(),
build_id,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct StoreNames {
pub kernels: &'static str,
pub sources: &'static str,
}
#[derive(Debug)]
pub struct ArtifactStore<A> {
by_kernel: Option<Store<KernelCacheKey, A>>,
by_source: Option<Store<StableHash, KernelCacheKey>>,
build_id: StableHash,
}
impl<A: StoreValue> ArtifactStore<A> {
pub fn new(backend: &'static str, fingerprint: impl AsRef<str>) -> Self {
Self {
by_kernel: compilation_store(backend, fingerprint),
by_source: None,
build_id: build_id_hash(),
}
}
pub fn with_sources(names: StoreNames, fingerprint: impl AsRef<str>) -> Self {
let by_kernel = compilation_store(names.kernels, fingerprint.as_ref());
let by_source = by_kernel
.is_some()
.then(|| compilation_store(names.sources, fingerprint))
.flatten();
Self {
by_kernel,
by_source,
build_id: build_id_hash(),
}
}
pub fn persists(&self) -> bool {
self.by_kernel.is_some()
}
pub fn is_enabled(&self) -> bool {
self.by_kernel.is_some()
}
pub fn take(&mut self, kernel: &KernelId) -> Option<A> {
let key = KernelCacheKey::new(kernel, self.build_id);
let artifact = self.by_kernel.as_mut()?.remove(&key)?;
log::trace!("Using the compilation store");
Some(artifact)
}
pub fn take_variant<V: core::hash::Hash>(&mut self, id: &super::ArtifactId<V>) -> Option<A> {
let key = KernelCacheKey::from_artifact_id(id, self.build_id);
let artifact = self.by_kernel.as_mut()?.remove(&key)?;
log::trace!("Using the compilation store for variant");
Some(artifact)
}
pub fn store_variant<V: core::hash::Hash>(
&mut self,
id: &super::ArtifactId<V>,
artifact: A,
) -> bool {
let Some(by_kernel) = self.by_kernel.as_mut() else {
return false;
};
let key = KernelCacheKey::from_artifact_id(id, self.build_id);
store_compiled(by_kernel, key, artifact)
}
pub fn purge(&mut self, kernel: &KernelId) {
if let Some(by_kernel) = self.by_kernel.as_mut() {
let key = KernelCacheKey::new(kernel, self.build_id);
by_kernel.purge_key(&key);
}
}
pub fn purge_variant<V: core::hash::Hash>(&mut self, id: &super::ArtifactId<V>) {
if let Some(by_kernel) = self.by_kernel.as_mut() {
let key = KernelCacheKey::from_artifact_id(id, self.build_id);
by_kernel.purge_key(&key);
}
}
pub fn take_by_source(&mut self, source: &str) -> Option<A> {
let by_kernel = self.by_kernel.as_mut()?;
let by_source = self.by_source.as_mut()?;
let hash = StableHasher::hash_one(&source);
let key = *by_source.get_mut(&hash)?;
let artifact = if key.build_id == self.build_id {
by_kernel.get_mut(&key)?.clone()
} else {
by_source.purge_key(&hash);
by_kernel.purge_key(&key)?
};
log::trace!("Using the compilation store, by source");
Some(artifact)
}
pub fn keep(&mut self, kernel: &KernelId, artifact: A, source: Option<&str>) -> bool {
let Some(by_kernel) = self.by_kernel.as_mut() else {
return false;
};
let key = KernelCacheKey::new(kernel, self.build_id);
let stored = store_compiled(by_kernel, key, artifact);
if let Some(source) = source
&& let Some(by_source) = self.by_source.as_mut()
{
store_compiled(by_source, StableHasher::hash_one(&source), key);
}
stored
}
}
#[cfg(all(test, compilation_cache))]
mod tests {
use super::*;
use alloc::vec;
struct First;
struct Second;
struct Third;
fn store(root: &std::path::Path) -> ArtifactStore<u32> {
fn lazy<K: StoreKey, V: StoreValue>(namespace: &str) -> Store<K, V> {
Store::new(
StoreOptions::new()
.storage(Namespace::new(namespace))
.cache(CacheOption::Lazy),
)
}
cubecl_environment::environment::set_root(root);
ArtifactStore {
by_kernel: Some(lazy("kernels")),
by_source: Some(lazy("sources")),
build_id: build_id_hash(),
}
}
#[test]
#[serial_test::serial(records)]
fn kernels_sharing_a_source_each_keep_it() {
let root = tempfile::tempdir().unwrap();
let mut store = store(root.path());
let kernels = [
KernelId::new::<First>(),
KernelId::new::<Second>(),
KernelId::new::<Third>(),
];
store.keep(&kernels[0], 7, Some("shared"));
for kernel in &kernels[1..] {
let artifact = store.take_by_source("shared").expect("kept by source");
store.keep(kernel, artifact, Some("shared"));
}
for kernel in &kernels {
assert_eq!(store.take(kernel), Some(7));
}
}
#[test]
#[serial_test::serial(records)]
fn an_earlier_build_is_moved() {
let root = tempfile::tempdir().unwrap();
let mut store = store(root.path());
let current = store.build_id;
let kernel = KernelId::new::<First>();
store.build_id = StableHasher::hash_one(&"an earlier build");
store.keep(&kernel, 7, Some("shared"));
store.build_id = current;
assert_eq!(store.take_by_source("shared"), Some(7));
store.build_id = StableHasher::hash_one(&"an earlier build");
assert_eq!(store.take(&kernel), None, "moved out");
}
}