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<D: DeviceOps>(device: &D) -> Option<DeviceSettings> {
47 REGISTRY
48 .read()
49 .get(&Self::key(device))
50 .and_then(|settings| settings.get().copied())
51 }
52
53 fn get_or_insert<D: DeviceOps>(
55 device: &D,
56 default_fn: impl FnOnce() -> DeviceSettings,
57 ) -> DeviceSettings {
58 let key = Self::key(device);
59 #[cfg(feature = "std")]
60 {
61 let cached = LOCAL_CACHE.with(|cache| cache.borrow().get(&key).copied());
62 if let Some(settings) = cached {
63 return settings;
64 }
65
66 let settings = {
68 let read = REGISTRY.read();
69 read.get(&key).cloned()
70 }
71 .unwrap_or_else(|| {
72 let mut map = REGISTRY.write();
73 Arc::clone(map.entry(key).or_default())
74 });
75
76 let settings = *settings.get_or_init(default_fn);
77
78 LOCAL_CACHE.with(|cache| {
79 cache.borrow_mut().insert(key, settings);
80 });
81
82 settings
83 }
84 #[cfg(not(feature = "std"))]
85 {
86 let settings = {
87 let read = REGISTRY.read();
88 read.get(&key).cloned()
89 }
90 .unwrap_or_else(|| {
91 let mut map = REGISTRY.write();
92 Arc::clone(map.entry(key).or_default())
93 });
94
95 settings.call_once(default_fn);
96 *settings.get().unwrap()
97 }
98 }
99
100 fn init<D: DeviceOps>(device: &D, settings: DeviceSettings) -> Result<(), DeviceError> {
104 let key = Self::key(device);
105 let mut map = REGISTRY.write();
106 let cell = map.entry(key).or_insert_with(|| Arc::new(OnceLock::new()));
107
108 #[cfg(feature = "std")]
109 return cell
110 .set(settings)
111 .map_err(|_| DeviceError::already_initialized(device));
112
113 #[cfg(not(feature = "std"))]
114 if cell.get().is_some() {
115 Err(DeviceError::already_initialized(device))
116 } else {
117 cell.call_once(|| settings);
118 Ok(())
119 }
120 }
121
122 fn key<D: Device>(device: &D) -> RegistryKey {
124 (device.to_id(), TypeId::of::<D>())
125 }
126}
127
128#[cfg(feature = "std")]
129thread_local! {
130 static LOCAL_CACHE: core::cell::RefCell<HashMap<RegistryKey, DeviceSettings>> =
132 core::cell::RefCell::new(HashMap::new());
133}
134
135pub fn get_or_init_device_settings<B: Backend>(device: &B::Device) -> DeviceSettings {
137 DeviceSettingsRegistry::get_or_insert(device, || device.defaults())
138}
139
140pub fn get_device_settings<B: Backend>(device: &B::Device) -> DeviceSettings {
146 DeviceSettingsRegistry::get(device).unwrap_or_else(|| device.defaults())
147}
148
149fn check_dtype_support<B: Backend>(
150 device: &B::Device,
151 dtype: impl Into<DType>,
152) -> Result<(), DeviceError> {
153 let dtype = dtype.into();
154 if B::supports_dtype(device, dtype) {
157 Ok(())
158 } else {
159 Err(DeviceError::unsupported_dtype(device, dtype))
160 }
161}
162
163pub fn set_default_dtypes<B: Backend>(
192 device: &B::Device,
193 float_dtype: impl Into<FloatDType>,
194 int_dtype: impl Into<IntDType>,
195 bool_dtype: impl Into<BoolDType>,
196) -> Result<(), DeviceError> {
197 let float_dtype = float_dtype.into();
198 let int_dtype = int_dtype.into();
199 let bool_dtype = bool_dtype.into();
200 check_dtype_support::<B>(device, float_dtype)?;
201 check_dtype_support::<B>(device, int_dtype)?;
202 check_dtype_support::<B>(device, bool_dtype)?;
203
204 let q_config = device.defaults().quantization;
205 let settings = DeviceSettings::new(float_dtype, int_dtype, bool_dtype, q_config);
206
207 initialize_unchecked(device, settings)?;
208 Ok(())
209}
210
211fn initialize_unchecked<D: DeviceOps>(
213 device: &D,
214 settings: DeviceSettings,
215) -> Result<(), DeviceError> {
216 DeviceSettingsRegistry::init(device, settings)
217}
218
219#[cfg(all(test, feature = "std"))]
220mod tests {
221 use serial_test::serial;
222
223 use super::*;
224
225 fn clear_registry() {
226 REGISTRY.write().clear();
227 }
228
229 #[derive(Clone, Debug, Default, PartialEq, new)]
230 pub struct TestDeviceA {
231 index: u16,
232 }
233
234 impl Device for TestDeviceA {
235 fn from_id(device_id: DeviceId) -> Self {
236 Self {
237 index: device_id.index_id,
238 }
239 }
240
241 fn to_id(&self) -> DeviceId {
242 DeviceId {
243 type_id: 0,
244 index_id: self.index,
245 }
246 }
247 }
248
249 impl DeviceOps for TestDeviceA {
250 fn defaults(&self) -> DeviceSettings {
251 DeviceSettings::with_dtypes(FloatDType::F32, IntDType::I32, BoolDType::Native)
252 }
253 }
254
255 #[derive(Clone, Debug, Default, PartialEq, new)]
256 pub struct TestDeviceB {
257 index: u16,
258 }
259
260 impl Device for TestDeviceB {
261 fn from_id(device_id: DeviceId) -> Self {
262 Self {
263 index: device_id.index_id,
264 }
265 }
266
267 fn to_id(&self) -> DeviceId {
268 DeviceId {
269 type_id: 0,
270 index_id: self.index,
271 }
272 }
273 }
274
275 impl DeviceOps for TestDeviceB {
276 fn defaults(&self) -> DeviceSettings {
277 DeviceSettings::with_dtypes(FloatDType::F32, IntDType::I32, BoolDType::Native)
278 }
279 }
280
281 fn get_test_device_settings<D: DeviceOps>(device: &D) -> DeviceSettings {
282 DeviceSettingsRegistry::get_or_insert(device, || device.defaults())
283 }
284
285 #[test]
286 #[serial]
287 fn default_settings_returned_when_uninitialized() {
288 clear_registry(); let device = TestDeviceA::new(0);
291
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, device.defaults());
297 }
298
299 #[test]
300 #[serial]
301 fn initialized_settings_are_returned() {
302 clear_registry(); let device = TestDeviceA::new(0);
305 let settings =
306 DeviceSettings::with_dtypes(FloatDType::BF16, IntDType::I32, BoolDType::Native);
307
308 initialize_unchecked(&device, settings).unwrap();
309 let s1 = get_test_device_settings(&device);
310 let s2 = get_test_device_settings(&device);
311
312 assert_eq!(s1, s2);
313 assert_eq!(s1, settings);
314 assert_eq!(s2, settings);
315 }
316
317 #[test]
318 #[serial]
319 fn querying_settings_leaves_initialization_open() {
320 clear_registry();
321
322 let device = TestDeviceA::new(2);
323 assert_eq!(DeviceSettingsRegistry::get(&device), None);
324
325 let settings =
326 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I64, BoolDType::Native);
327 initialize_unchecked(&device, settings).unwrap();
328
329 assert_eq!(DeviceSettingsRegistry::get(&device), Some(settings));
330 assert_eq!(get_test_device_settings(&device), settings);
331 }
332
333 #[test]
334 #[serial]
335 fn settings_are_device_id_specific() {
336 clear_registry(); let d1 = TestDeviceA::new(0);
339 let d2 = TestDeviceA::new(1);
340 let settings =
341 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I64, BoolDType::Native);
342
343 initialize_unchecked(&d1, settings).unwrap();
344
345 let s1 = get_test_device_settings(&d1);
346 let s2 = get_test_device_settings(&d2);
347
348 assert_ne!(s1, s2);
349 assert_eq!(s1, settings);
350 assert_eq!(s2, d2.defaults());
351 }
352
353 #[test]
354 #[serial]
355 fn settings_are_device_type_specific() {
356 clear_registry(); let d1 = TestDeviceA::new(0);
359 let d2 = TestDeviceB::new(0);
360 let settings =
361 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I64, BoolDType::Native);
362
363 initialize_unchecked(&d2, settings).unwrap();
364
365 let s1 = get_test_device_settings(&d1);
366 let s2 = get_test_device_settings(&d2);
367
368 assert_ne!(s1, s2);
369 assert_eq!(s1, d1.defaults());
370 assert_eq!(s2, settings);
371 }
372
373 #[test]
374 #[serial]
375 fn initialization_after_default_returns_error() {
376 clear_registry(); let device = TestDeviceA::new(0);
379 let _before = get_test_device_settings(&device);
381
382 let settings =
383 DeviceSettings::with_dtypes(FloatDType::BF16, IntDType::I64, BoolDType::Native);
384 let result = initialize_unchecked(&device, settings);
385
386 assert!(matches!(
387 result,
388 Err(DeviceError::AlreadyInitialized { .. })
389 ));
390 }
391
392 #[test]
393 #[serial]
394 fn second_initialization_returns_error() {
395 clear_registry(); let device = TestDeviceA::new(0);
398 let settings =
399 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I32, BoolDType::Native);
400 initialize_unchecked(&device, settings).unwrap();
401
402 let result = initialize_unchecked(&device, device.defaults());
403 assert!(matches!(
404 result,
405 Err(DeviceError::AlreadyInitialized { .. })
406 ));
407 }
408
409 #[cfg(feature = "std")]
410 #[test]
411 #[serial]
412 fn initialized_settings_are_global() {
413 clear_registry();
414
415 let device = TestDeviceA::new(0);
416 let settings =
417 DeviceSettings::with_dtypes(FloatDType::F16, IntDType::I32, BoolDType::Native);
418
419 initialize_unchecked(&device, settings).unwrap();
420 let settings_actual = get_test_device_settings(&device);
421 assert_eq!(settings_actual, settings);
422
423 let seen_by_new_thread =
425 std::thread::spawn(move || get_test_device_settings(&TestDeviceA::new(0)))
426 .join()
427 .unwrap();
428 assert_eq!(seen_by_new_thread, settings_actual);
429 }
430}