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