1pub use burn_std::device::*;
2use burn_std::{BoolDType, DType, FloatDType, IntDType};
3pub use burn_std::{DeviceError, DeviceSettings};
4
5use burn_std::sync::{Arc, LazyLock, RwLock};
6
7use core::any::TypeId;
8
9#[cfg(feature = "std")]
10pub use std::collections::HashMap;
11#[cfg(feature = "std")]
12use std::sync::OnceLock;
13
14#[cfg(not(feature = "std"))]
15pub use hashbrown::HashMap;
16#[cfg(not(feature = "std"))]
17use spin::Once as OnceLock;
18
19use crate::Backend;
20
21pub trait DeviceOps: Clone + Default + PartialEq + Send + Sync + core::fmt::Debug + Device {
23 fn id(&self) -> DeviceId {
25 self.to_id()
26 }
27
28 fn defaults(&self) -> DeviceSettings;
30}
31
32type RegistryKey = (DeviceId, TypeId);
34
35static REGISTRY: LazyLock<RwLock<HashMap<RegistryKey, Arc<OnceLock<DeviceSettings>>>>> =
40 LazyLock::new(|| RwLock::new(HashMap::new()));
41
42struct DeviceSettingsRegistry;
43
44impl DeviceSettingsRegistry {
45 fn get_or_insert<D: DeviceOps>(
47 device: &D,
48 default_fn: impl FnOnce() -> DeviceSettings,
49 ) -> DeviceSettings {
50 let key = Self::key(device);
51 #[cfg(feature = "std")]
52 {
53 let cached = LOCAL_CACHE.with(|cache| cache.borrow().get(&key).copied());
54 if let Some(settings) = cached {
55 return settings;
56 }
57
58 let settings = {
60 let read = REGISTRY.read();
61 read.get(&key).cloned()
62 }
63 .unwrap_or_else(|| {
64 let mut map = REGISTRY.write();
65 Arc::clone(map.entry(key).or_default())
66 });
67
68 let settings = *settings.get_or_init(default_fn);
69
70 LOCAL_CACHE.with(|cache| {
71 cache.borrow_mut().insert(key, settings);
72 });
73
74 settings
75 }
76 #[cfg(not(feature = "std"))]
77 {
78 let settings = {
79 let read = REGISTRY.read();
80 read.get(&key).cloned()
81 }
82 .unwrap_or_else(|| {
83 let mut map = REGISTRY.write();
84 Arc::clone(map.entry(key).or_default())
85 });
86
87 settings.call_once(default_fn);
88 *settings.get().unwrap()
89 }
90 }
91
92 fn init<D: DeviceOps>(device: &D, settings: DeviceSettings) -> Result<(), DeviceError> {
96 let key = Self::key(device);
97 let mut map = REGISTRY.write();
98 let cell = map.entry(key).or_insert_with(|| Arc::new(OnceLock::new()));
99
100 #[cfg(feature = "std")]
101 return cell
102 .set(settings)
103 .map_err(|_| DeviceError::already_initialized(device));
104
105 #[cfg(not(feature = "std"))]
106 if cell.get().is_some() {
107 Err(DeviceError::already_initialized(device))
108 } else {
109 cell.call_once(|| settings);
110 Ok(())
111 }
112 }
113
114 fn key<D: Device>(device: &D) -> RegistryKey {
116 (device.to_id(), TypeId::of::<D>())
117 }
118}
119
120#[cfg(feature = "std")]
121thread_local! {
122 static LOCAL_CACHE: core::cell::RefCell<HashMap<RegistryKey, DeviceSettings>> =
124 core::cell::RefCell::new(HashMap::new());
125}
126
127pub fn get_device_settings<B: Backend>(device: &B::Device) -> DeviceSettings {
129 DeviceSettingsRegistry::get_or_insert(device, || device.defaults())
130}
131
132fn check_dtype_support<B: Backend>(
133 device: &B::Device,
134 dtype: impl Into<DType>,
135) -> Result<(), DeviceError> {
136 let dtype = dtype.into();
137 if B::supports_dtype(device, dtype) {
140 Ok(())
141 } else {
142 Err(DeviceError::unsupported_dtype(device, dtype))
143 }
144}
145
146pub fn set_default_dtypes<B: Backend>(
175 device: &B::Device,
176 float_dtype: impl Into<FloatDType>,
177 int_dtype: impl Into<IntDType>,
178 bool_dtype: impl Into<BoolDType>,
179) -> Result<(), DeviceError> {
180 let float_dtype = float_dtype.into();
181 let int_dtype = int_dtype.into();
182 let bool_dtype = bool_dtype.into();
183 check_dtype_support::<B>(device, float_dtype)?;
184 check_dtype_support::<B>(device, int_dtype)?;
185 check_dtype_support::<B>(device, bool_dtype)?;
186
187 let q_config = device.defaults().quantization;
188 let settings = DeviceSettings::new(float_dtype, int_dtype, bool_dtype, q_config);
189
190 initialize_unchecked(device, settings)?;
191 Ok(())
192}
193
194fn initialize_unchecked<D: DeviceOps>(
196 device: &D,
197 settings: DeviceSettings,
198) -> Result<(), DeviceError> {
199 DeviceSettingsRegistry::init(device, settings)
200}
201
202#[cfg(all(test, feature = "std"))]
203mod tests {
204 use serial_test::serial;
205
206 use super::*;
207
208 fn clear_registry() {
209 REGISTRY.write().clear();
210 }
211
212 #[derive(Clone, Debug, Default, PartialEq, new)]
213 pub struct TestDeviceA {
214 index: u16,
215 }
216
217 impl Device for TestDeviceA {
218 fn from_id(device_id: DeviceId) -> Self {
219 Self {
220 index: device_id.index_id,
221 }
222 }
223
224 fn to_id(&self) -> DeviceId {
225 DeviceId {
226 type_id: 0,
227 index_id: self.index,
228 }
229 }
230 }
231
232 impl DeviceOps for TestDeviceA {
233 fn defaults(&self) -> DeviceSettings {
234 DeviceSettings::with_dtypes(FloatDType::F32, IntDType::I32, BoolDType::Native)
235 }
236 }
237
238 #[derive(Clone, Debug, Default, PartialEq, new)]
239 pub struct TestDeviceB {
240 index: u16,
241 }
242
243 impl Device for TestDeviceB {
244 fn from_id(device_id: DeviceId) -> Self {
245 Self {
246 index: device_id.index_id,
247 }
248 }
249
250 fn to_id(&self) -> DeviceId {
251 DeviceId {
252 type_id: 0,
253 index_id: self.index,
254 }
255 }
256 }
257
258 impl DeviceOps for TestDeviceB {
259 fn defaults(&self) -> DeviceSettings {
260 DeviceSettings::with_dtypes(FloatDType::F32, IntDType::I32, BoolDType::Native)
261 }
262 }
263
264 fn get_test_device_settings<D: DeviceOps>(device: &D) -> DeviceSettings {
265 DeviceSettingsRegistry::get_or_insert(device, || device.defaults())
266 }
267
268 #[test]
269 #[serial]
270 fn default_settings_returned_when_uninitialized() {
271 clear_registry(); let device = TestDeviceA::new(0);
274
275 let s1 = get_test_device_settings(&device);
276 let s2 = get_test_device_settings(&device);
277
278 assert_eq!(s1, s2);
279 assert_eq!(s1, device.defaults());
280 }
281
282 #[test]
283 #[serial]
284 fn initialized_settings_are_returned() {
285 clear_registry(); let device = TestDeviceA::new(0);
288 let settings =
289 DeviceSettings::with_dtypes(FloatDType::BF16, IntDType::I32, BoolDType::Native);
290
291 initialize_unchecked(&device, settings).unwrap();
292 let s1 = get_test_device_settings(&device);
293 let s2 = get_test_device_settings(&device);
294
295 assert_eq!(s1, s2);
296 assert_eq!(s1, settings);
297 assert_eq!(s2, settings);
298 }
299
300 #[test]
301 #[serial]
302 fn settings_are_device_id_specific() {
303 clear_registry(); let d1 = TestDeviceA::new(0);
306 let d2 = TestDeviceA::new(1);
307 let settings =
308 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I64, BoolDType::Native);
309
310 initialize_unchecked(&d1, settings).unwrap();
311
312 let s1 = get_test_device_settings(&d1);
313 let s2 = get_test_device_settings(&d2);
314
315 assert_ne!(s1, s2);
316 assert_eq!(s1, settings);
317 assert_eq!(s2, d2.defaults());
318 }
319
320 #[test]
321 #[serial]
322 fn settings_are_device_type_specific() {
323 clear_registry(); let d1 = TestDeviceA::new(0);
326 let d2 = TestDeviceB::new(0);
327 let settings =
328 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I64, BoolDType::Native);
329
330 initialize_unchecked(&d2, settings).unwrap();
331
332 let s1 = get_test_device_settings(&d1);
333 let s2 = get_test_device_settings(&d2);
334
335 assert_ne!(s1, s2);
336 assert_eq!(s1, d1.defaults());
337 assert_eq!(s2, settings);
338 }
339
340 #[test]
341 #[serial]
342 fn initialization_after_default_returns_error() {
343 clear_registry(); let device = TestDeviceA::new(0);
346 let _before = get_test_device_settings(&device);
348
349 let settings =
350 DeviceSettings::with_dtypes(FloatDType::BF16, IntDType::I64, BoolDType::Native);
351 let result = initialize_unchecked(&device, settings);
352
353 assert!(matches!(
354 result,
355 Err(DeviceError::AlreadyInitialized { .. })
356 ));
357 }
358
359 #[test]
360 #[serial]
361 fn second_initialization_returns_error() {
362 clear_registry(); let device = TestDeviceA::new(0);
365 let settings =
366 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I32, BoolDType::Native);
367 initialize_unchecked(&device, settings).unwrap();
368
369 let result = initialize_unchecked(&device, device.defaults());
370 assert!(matches!(
371 result,
372 Err(DeviceError::AlreadyInitialized { .. })
373 ));
374 }
375
376 #[cfg(feature = "std")]
377 #[test]
378 #[serial]
379 fn initialized_settings_are_global() {
380 clear_registry();
381
382 let device = TestDeviceA::new(0);
383 let settings =
384 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I32, BoolDType::Native);
385
386 initialize_unchecked(&device, settings).unwrap();
387 let settings_actual = get_test_device_settings(&device);
388 assert_eq!(settings_actual, settings);
389
390 let seen_by_new_thread =
392 std::thread::spawn(move || get_test_device_settings(&TestDeviceA::new(0)))
393 .join()
394 .unwrap();
395 assert_eq!(seen_by_new_thread, settings_actual);
396 }
397}