cubecl_runtime/tune/
tune_cache.rs1#[cfg(autotune_persistent_cache)]
2mod std_imports {
3 pub use std::fs;
4 pub use std::fs::File;
5 pub use std::io;
6 pub use std::path::Path;
7 pub use std::path::PathBuf;
8}
9
10#[cfg(autotune_persistent_cache)]
11use std_imports::*;
12
13#[cfg(autotune_persistent_cache)]
14use serde::{Deserialize, Serialize};
15
16use super::AutotuneKey;
17use hashbrown::HashMap;
18
19#[cfg(autotune_persistent_cache)]
20pub fn get_persistent_cache_file_path(prefix: &str) -> PathBuf {
23 let home_dir = dirs::home_dir().expect("An home directory should exist");
24 let path_dir = home_dir.join(".cache").join("cubecl").join("autotune");
25 let path = Path::new(&path_dir);
26 path.join(format!("{}-autotune-cache.json", prefix))
27}
28
29#[derive(Debug)]
31pub(crate) enum CacheEntry {
32 Done {
33 checksum_matches: Option<bool>,
34 fastest_index: usize,
35 },
36 Pending,
37}
38
39#[cfg(autotune_persistent_cache)]
41#[derive(Debug, Serialize, Deserialize)]
42pub(crate) struct PersistentCacheEntry {
43 checksum: String,
44 fastest_index: usize,
45}
46
47#[derive(Debug)]
49pub(crate) struct TuneCache<K> {
50 in_memory_cache: HashMap<K, CacheEntry>,
51 #[cfg(autotune_persistent_cache)]
52 persistent_cache: HashMap<K, PersistentCacheEntry>,
53 #[cfg(autotune_persistent_cache)]
54 device_id: String,
55 #[cfg(autotune_persistent_cache)]
56 name: String,
57}
58
59#[derive(Debug)]
61pub enum TuneCacheResult {
62 Hit {
64 fastest_index: usize,
66 },
67 Unchecked,
69 Pending,
71 Miss,
73}
74
75impl<K: AutotuneKey> TuneCache<K> {
76 pub(crate) fn new(
77 #[cfg_attr(not(autotune_persistent_cache), allow(unused_variables))] name: &str,
78 #[cfg_attr(not(autotune_persistent_cache), allow(unused_variables))] device_id: &str,
79 ) -> Self {
80 #[cfg(autotune_persistent_cache)]
81 {
82 let mut cache = TuneCache {
83 in_memory_cache: HashMap::new(),
84 persistent_cache: HashMap::new(),
85 device_id: device_id.to_string(),
86 name: name.to_string(),
87 };
88 if let Err(e) = cache.load() {
89 log::warn!(
90 "Unable to load autotune cache. Cache will be ignored ({}).",
91 e
92 );
93 }
94 cache
95 }
96
97 #[cfg(not(autotune_persistent_cache))]
98 {
99 TuneCache {
100 in_memory_cache: HashMap::new(),
101 }
102 }
103 }
104
105 pub fn fastest(&self, key: &K) -> TuneCacheResult {
106 let result = self.in_memory_cache.get(key);
107
108 let Some(val) = result else {
109 return TuneCacheResult::Miss;
110 };
111
112 match val {
113 CacheEntry::Done {
114 checksum_matches,
115 fastest_index,
116 } => {
117 if cfg!(autotune_persistent_cache) {
118 match checksum_matches {
119 None => TuneCacheResult::Unchecked, Some(false) => TuneCacheResult::Miss, Some(true) => TuneCacheResult::Hit {
122 fastest_index: *fastest_index,
123 },
124 }
125 } else {
126 let _ = checksum_matches;
127 TuneCacheResult::Hit {
128 fastest_index: *fastest_index,
129 }
130 }
131 }
132 CacheEntry::Pending {} => TuneCacheResult::Pending,
133 }
134 }
135
136 #[cfg(autotune_persistent_cache)]
137 pub fn validate_checksum(&mut self, key: &K, checksum: &str) {
138 let result = self.in_memory_cache.get_mut(key);
139 let Some(val) = result else {
140 return;
141 };
142
143 if let CacheEntry::Done {
144 checksum_matches, ..
145 } = val
146 {
147 if checksum_matches.is_none() {
148 let persistent_entry = self
149 .persistent_cache
150 .get(key)
151 .expect("Both caches should be in sync");
152
153 *checksum_matches = Some(checksum == persistent_entry.checksum);
154 }
155 }
156 }
157
158 pub(crate) fn mark_pending(&mut self, key: K) {
159 self.in_memory_cache.insert(key, CacheEntry::Pending);
160 }
161
162 pub(crate) fn cache_insert(&mut self, key: K, fastest_index: usize) {
163 self.in_memory_cache.insert(
164 key,
165 CacheEntry::Done {
166 checksum_matches: Some(true),
167 fastest_index,
168 },
169 );
170 }
171}
172
173#[cfg(autotune_persistent_cache)]
174impl<K: AutotuneKey> TuneCache<K> {
175 pub(crate) fn persistent_cache_insert(
176 &mut self,
177 key: K,
178 checksum: String,
179 fastest_index: usize,
180 ) {
181 self.persistent_cache.insert(
182 key,
183 PersistentCacheEntry {
184 checksum,
185 fastest_index,
186 },
187 );
188 }
189
190 pub(crate) fn load(&mut self) -> Result<(), io::Error> {
192 let file_path = self.get_persistent_cache_file_path();
193 match fs::read_to_string(file_path) {
198 Ok(data) => {
199 let data: Vec<(K, PersistentCacheEntry)> = serde_json::from_str(&data)?;
200 for (key, value) in data.into_iter() {
201 self.persistent_cache.insert(key, value);
202 }
203 Ok(())
204 }
205 Err(e) => {
206 if e.kind() == std::io::ErrorKind::NotFound {
207 Ok(())
208 } else {
209 Err(e)
210 }
211 }
212 }?;
213 for (key, entry) in self.persistent_cache.iter() {
214 self.in_memory_cache.insert(
215 key.clone(),
216 CacheEntry::Done {
217 checksum_matches: None,
218 fastest_index: entry.fastest_index,
219 },
220 );
221 }
222 Ok(())
223 }
224
225 pub(crate) fn save(&self) {
227 let file_path = self.get_persistent_cache_file_path();
228 if let Some(parent_dir) = file_path.parent() {
229 if !parent_dir.exists() {
230 fs::create_dir_all(parent_dir).unwrap_or_else(|_| {
231 panic!(
232 "Should be able to create directory '{}' for autotune persistent cache file",
233 parent_dir.to_str().unwrap())
234 });
235 }
236 }
237 let file = File::create(file_path.clone()).unwrap_or_else(|_| {
238 panic!(
239 "Should be able to open autotune persistent cache file '{}'",
240 file_path.to_str().unwrap()
241 )
242 });
243 let data = self.persistent_cache.iter().collect::<Vec<_>>();
244 serde_json::to_writer_pretty(file, &data)
245 .expect("Should be able to write to autotune persistent cache");
246 }
247
248 pub fn get_persistent_cache_file_path(&self) -> PathBuf {
250 let name = sanitize_filename::sanitize(&self.name);
251 let device_id = sanitize_filename::sanitize(&self.device_id);
252 get_persistent_cache_file_path(&format!("{name}/{device_id}"))
253 }
254}