Skip to main content

burn_compute/tune/
tune_cache.rs

1#[cfg(feature = "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#[cfg(feature = "autotune-persistent-cache")]
10use std_imports::*;
11
12#[cfg(feature = "autotune-persistent-cache")]
13use serde::{Deserialize, Serialize};
14
15use super::AutotuneKey;
16use super::AutotuneOperation;
17use super::AutotuneOperationSet;
18use alloc::boxed::Box;
19use hashbrown::HashMap;
20
21#[cfg(feature = "autotune-persistent-cache")]
22/// Return the file path for the persistent cache on disk
23/// prefix should be the device id computed at the backend level
24pub fn get_persistent_cache_file_path(prefix: &str) -> PathBuf {
25    let home_dir = dirs::home_dir().expect("An home directory should exist");
26    let path_dir = home_dir.join(".cache").join("burn").join("autotune");
27    let path = Path::new(&path_dir);
28    path.join(format!("{}-autotune-cache.json", prefix))
29}
30
31/// In-memory cache entry
32#[derive(Debug)]
33pub(crate) struct InMemoryCacheEntry {
34    #[cfg(feature = "autotune-persistent-cache")]
35    checksum_checked: bool,
36    fastest_index: usize,
37}
38
39/// Persistent cache entry
40#[cfg(feature = "autotune-persistent-cache")]
41#[derive(Debug, Serialize, Deserialize)]
42pub(crate) struct PersistentCacheEntry {
43    checksum: String,
44    fastest_index: usize,
45}
46
47/// Use to find and reuse the best kernel for some input
48#[derive(Debug)]
49pub(crate) struct TuneCache<K> {
50    in_memory_cache: HashMap<K, InMemoryCacheEntry>,
51    #[cfg(feature = "autotune-persistent-cache")]
52    persistent_cache: HashMap<K, PersistentCacheEntry>,
53    #[cfg(feature = "autotune-persistent-cache")]
54    device_id: String,
55}
56
57/// Result of the cache try
58pub enum TuneCacheResult<K> {
59    /// An operation is found and given
60    Hit(Box<dyn AutotuneOperation>),
61    /// No operation is found and the set is given back for ownership
62    Miss(Box<dyn AutotuneOperationSet<K>>),
63}
64
65impl<K: AutotuneKey> TuneCache<K> {
66    pub(crate) fn new(
67        #[cfg_attr(not(feature = "autotune-persistent-cache"), allow(unused_variables))]
68        device_id: &str,
69    ) -> Self {
70        #[cfg(feature = "autotune-persistent-cache")]
71        {
72            let mut cache = TuneCache {
73                in_memory_cache: HashMap::new(),
74                persistent_cache: HashMap::new(),
75                device_id: device_id.to_string(),
76            };
77            if let Err(e) = cache.load() {
78                log::warn!(
79                    "Unable to load autotune cache. Cache will be ignored ({}).",
80                    e
81                );
82            }
83            cache
84        }
85
86        #[cfg(not(feature = "autotune-persistent-cache"))]
87        {
88            TuneCache {
89                in_memory_cache: HashMap::new(),
90            }
91        }
92    }
93
94    pub(crate) fn find_fastest(&self, key: &K) -> Option<usize> {
95        let result = self.in_memory_cache.get(key);
96
97        let val = match result {
98            Some(val) => val,
99            None => return None,
100        };
101
102        #[cfg(feature = "autotune-persistent-cache")]
103        if val.checksum_checked {
104            Some(val.fastest_index)
105        } else {
106            None
107        }
108
109        #[cfg(not(feature = "autotune-persistent-cache"))]
110        Some(val.fastest_index)
111    }
112
113    pub(crate) fn try_cache(
114        &mut self,
115        autotune_operation_set: Box<dyn AutotuneOperationSet<K>>,
116    ) -> TuneCacheResult<K> {
117        let key = autotune_operation_set.key();
118        let result = self.in_memory_cache.get_mut(&key);
119
120        #[cfg(feature = "autotune-persistent-cache")]
121        {
122            if let Some(InMemoryCacheEntry {
123                checksum_checked,
124                fastest_index,
125            }) = result
126            {
127                if !*checksum_checked {
128                    let checksum = autotune_operation_set.compute_checksum();
129                    let persistent_entry = self
130                        .persistent_cache
131                        .get(&key)
132                        .expect("Both caches should be in sync");
133                    if checksum != persistent_entry.checksum {
134                        return TuneCacheResult::Miss(autotune_operation_set);
135                    }
136                    *checksum_checked = true;
137                }
138                return TuneCacheResult::Hit(autotune_operation_set.fastest(*fastest_index));
139            }
140        }
141
142        #[cfg(not(feature = "autotune-persistent-cache"))]
143        {
144            if let Some(InMemoryCacheEntry { fastest_index, .. }) = result {
145                return TuneCacheResult::Hit(autotune_operation_set.fastest(*fastest_index));
146            }
147        }
148
149        TuneCacheResult::Miss(autotune_operation_set)
150    }
151
152    pub(crate) fn cache_insert(&mut self, key: K, fastest_index: usize) {
153        self.in_memory_cache.insert(
154            key,
155            InMemoryCacheEntry {
156                #[cfg(feature = "autotune-persistent-cache")]
157                checksum_checked: true,
158                fastest_index,
159            },
160        );
161    }
162
163    #[cfg(feature = "autotune-persistent-cache")]
164    pub(crate) fn persistent_cache_insert(
165        &mut self,
166        key: K,
167        checksum: String,
168        fastest_index: usize,
169    ) {
170        self.persistent_cache.insert(
171            key,
172            PersistentCacheEntry {
173                checksum,
174                fastest_index,
175            },
176        );
177    }
178
179    /// Load the persistent cache data from disk
180    #[cfg(feature = "autotune-persistent-cache")]
181    pub(crate) fn load(&mut self) -> Result<(), io::Error> {
182        let file_path = self.get_persistent_cache_file_path();
183        // note: reading file from memory is faster than using
184        // serde from_reader with a buffered reader
185        // see issue:
186        // https://github.com/serde-rs/json/issues/160
187        match fs::read_to_string(file_path) {
188            Ok(data) => {
189                let data: Vec<(K, PersistentCacheEntry)> = serde_json::from_str(&data)?;
190                for (key, value) in data.into_iter() {
191                    self.persistent_cache.insert(key, value);
192                }
193                Ok(())
194            }
195            Err(e) => {
196                if e.kind() == std::io::ErrorKind::NotFound {
197                    Ok(())
198                } else {
199                    Err(e)
200                }
201            }
202        }?;
203        for (key, entry) in self.persistent_cache.iter() {
204            self.in_memory_cache.insert(
205                key.clone(),
206                InMemoryCacheEntry {
207                    checksum_checked: false,
208                    fastest_index: entry.fastest_index,
209                },
210            );
211        }
212        Ok(())
213    }
214
215    /// Save the persistent cache on disk
216    #[cfg(feature = "autotune-persistent-cache")]
217    pub(crate) fn save(&self) {
218        let file_path = self.get_persistent_cache_file_path();
219        if let Some(parent_dir) = file_path.parent() {
220            if !parent_dir.exists() {
221                fs::create_dir_all(parent_dir).unwrap_or_else(|_| {
222                    panic!(
223                    "Should be able to create directory '{}' for autotune persistent cache file",
224                    parent_dir.to_str().unwrap())
225                });
226            }
227        }
228        let file = File::create(file_path.clone()).unwrap_or_else(|_| {
229            panic!(
230                "Should be able to open autotune persistent cache file '{}'",
231                file_path.to_str().unwrap()
232            )
233        });
234        let data = self.persistent_cache.iter().collect::<Vec<_>>();
235        serde_json::to_writer_pretty(file, &data)
236            .expect("Should be able to write to autotune persistent cache");
237    }
238
239    /// Return the file path for the persistent cache on disk
240    #[cfg(feature = "autotune-persistent-cache")]
241    pub fn get_persistent_cache_file_path(&self) -> PathBuf {
242        get_persistent_cache_file_path(&self.device_id)
243    }
244}