Skip to main content

burn_core/module/param/
running.rs

1use super::ParamId;
2use crate::module::{
3    Content, Module, ModuleDisplay, ModuleDisplayDefault, ModuleMapper, ModuleVisitor, Param,
4};
5
6use alloc::string::ToString;
7use alloc::vec::Vec;
8
9use burn_std::sync::{Arc, Mutex};
10use burn_tensor::{Device, Tensor};
11
12#[cfg(feature = "std")]
13mod threading {
14    pub(super) use std::collections::HashMap;
15    pub(super) use std::thread::ThreadId;
16
17    #[inline(always)]
18    pub(super) fn get_thread_current_id() -> ThreadId {
19        std::thread::current().id()
20    }
21}
22
23#[cfg(not(feature = "std"))]
24mod threading {
25    pub(super) use burn_std::sync::ThreadId;
26    pub(super) use hashbrown::HashMap;
27
28    #[inline(always)]
29    pub(super) fn get_thread_current_id() -> ThreadId {
30        panic!("Current thread id is not available")
31    }
32}
33
34// Re-export items from the disabled/enabled blocks
35use threading::*;
36
37/// A state that can be updated during the forward pass while being thread safe.
38///
39/// # Note
40///
41/// The state value is the average of all updates on all threads.
42#[derive(Clone, Debug)]
43pub struct RunningState<V> {
44    id: ParamId,
45    values: Arc<Mutex<HashMap<ThreadId, V>>>,
46    value: Arc<Mutex<V>>,
47}
48
49// Implement display for the module
50
51impl<V> core::fmt::Display for RunningState<V> {
52    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
53        write!(f, "RunningState(id={})", self.id)
54    }
55}
56
57impl<V> ModuleDisplayDefault for RunningState<V> {
58    fn content(&self, content: Content) -> Option<Content> {
59        content
60            .add_formatted(&"RunningState".to_string())
61            .optional()
62    }
63}
64
65impl<V> ModuleDisplay for RunningState<V> {}
66
67impl<const D: usize> Module for RunningState<Tensor<D>> {
68    fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
69        let tensor = self.value.lock();
70        let param = Param::initialized(self.id, tensor.clone());
71        visitor.visit_float(&param)
72    }
73
74    fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
75        let mut tensor = self.value.lock();
76        let param = Param::initialized(self.id, tensor.clone());
77        let param_out = mapper.map_float(param);
78        let (_, tensor_out, _) = param_out.consume();
79
80        // Running state is a buffer, not an optimizer target. Mappers may transform its value,
81        // but must not make it require gradients.
82        *tensor = tensor_out.set_require_grad(false);
83        core::mem::drop(tensor);
84
85        self
86    }
87
88    fn to_device(self, device: &Device) -> Self {
89        let mut tensor = self.value.lock();
90        let tensor_out = tensor.clone().to_device(device);
91
92        *tensor = tensor_out;
93        core::mem::drop(tensor);
94
95        self
96    }
97
98    fn fork(self, device: &Device) -> Self {
99        self.to_device(device) // Same thing here since no grad.
100    }
101
102    fn collect_devices(&self, mut devices: Vec<Device>) -> Vec<Device> {
103        let device = self.value.lock().device();
104
105        if !devices.contains(&device) {
106            devices.push(device)
107        }
108
109        devices
110    }
111
112    fn materialize(self) -> Self {
113        self
114    }
115
116    fn valid(&self) -> Self {
117        self.sync();
118        let value = self.value();
119
120        RunningState::with_id(self.id, value.without_autodiff())
121    }
122
123    fn train(self) -> Self {
124        self.sync();
125        let value = self.value();
126
127        RunningState::with_id(self.id, Tensor::from_inner(value))
128    }
129}
130
131impl<const D: usize> RunningState<Tensor<D>> {
132    /// Create a new running state.
133    pub fn new(value: Tensor<D>) -> Self {
134        Self {
135            id: ParamId::new(),
136            values: Arc::new(Mutex::new(HashMap::new())),
137            value: Arc::new(Mutex::new(value)),
138        }
139    }
140
141    /// Create a new running state.
142    pub fn with_id(id: ParamId, value: Tensor<D>) -> Self {
143        Self {
144            id,
145            values: Arc::new(Mutex::new(HashMap::new())),
146            value: Arc::new(Mutex::new(value)),
147        }
148    }
149
150    /// Create a new running state from a record.
151    pub fn from_record(record: Param<Tensor<D>>) -> Self {
152        let tensor = record.val();
153        Self {
154            id: record.id,
155            values: Arc::new(Mutex::new(HashMap::new())),
156            value: Arc::new(Mutex::new(tensor)),
157        }
158    }
159
160    /// Update the value on the current thread.
161    pub fn update(&self, value: Tensor<D>) {
162        let thread_id = get_thread_current_id();
163        let mut map = self.values.lock();
164
165        if map.contains_key(&thread_id) {
166            self.update_value(&mut map);
167        }
168
169        map.insert(thread_id, value);
170    }
171
172    /// Get the current value,
173    ///
174    /// # Note
175    ///
176    /// The current value might be outdated by one update.
177    pub fn value(&self) -> Tensor<D> {
178        let value = self.value.lock();
179        value.clone()
180    }
181
182    /// Get the current value and make sure it is sync.
183    ///
184    /// # Note
185    ///
186    /// Don't use this function after an update on the same thread where other threads might have to
187    /// register their update before the actual synchronization needs to happen.
188    pub fn value_sync(&self) -> Tensor<D> {
189        let thread_id = get_thread_current_id();
190        let mut map = self.values.lock();
191
192        if map.contains_key(&thread_id) {
193            self.update_value(&mut map);
194        }
195
196        let value = self.value.lock();
197        value.clone()
198    }
199
200    fn sync(&self) {
201        let mut map = self.values.lock();
202
203        if !map.is_empty() {
204            self.update_value(&mut map);
205        }
206    }
207
208    fn update_value(&self, map: &mut HashMap<ThreadId, Tensor<D>>) {
209        let mut value_updated: Option<Tensor<D>> = None;
210        let mut counter = 0;
211
212        for (_key, tensor) in map.drain() {
213            counter += 1;
214
215            value_updated = match value_updated {
216                Some(current) => {
217                    let device = current.device();
218                    Some(tensor.to_device(&device).add(current))
219                }
220                None => Some(tensor),
221            };
222        }
223
224        if let Some(value) = value_updated {
225            let value = value.div_scalar(counter);
226            let mut value_old = self.value.lock();
227            *value_old = value;
228        }
229    }
230}