Skip to main content

burn_core/module/param/
flag.rs

1use alloc::format;
2use core::fmt::{Display, Formatter, Result as FmtResult};
3
4use burn_tensor::Device;
5
6use crate::module::{
7    Content, Devices, Module, ModuleDisplay, ModuleDisplayDefault, ModuleMapper, ModuleVisitor,
8    Param, ParamId, ParameterValue,
9};
10
11/// A boolean parameter value used for module-owned control state.
12///
13/// [`Param<Flag>`] gives the value an identity in the module tree, allowing parameter groups to
14/// select it by id or path just like other module-owned values. The field holding the flag gives
15/// it its meaning; for example, `Dropout::training` uses an enabled flag to permit stochastic
16/// behavior on an autodiff device.
17///
18/// # Records
19///
20/// Flags are runtime configuration, not persisted model state. Module records omit their value,
21/// lifecycle state and [`ParamId`]; loading a record preserves the destination module's flag
22/// exactly as configured. This mirrors tensor parameters, whose values and ids are recorded but
23/// whose `require_grad` configuration comes from the destination module.
24///
25/// # Devices
26///
27/// Flags are host-side control state and do not reside on a compute device. Moving or forking a
28/// module preserves a flag unchanged, and flags do not contribute entries to [`Module::devices`].
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub struct Flag(bool);
31
32impl Flag {
33    /// Create a flag with the given value.
34    pub const fn new(value: bool) -> Self {
35        Self(value)
36    }
37
38    /// Whether the flag is enabled.
39    pub const fn is_enabled(&self) -> bool {
40        self.0
41    }
42
43    /// Return the flag with the given value.
44    pub const fn with(self, value: bool) -> Self {
45        Self(value)
46    }
47}
48
49impl Display for Flag {
50    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
51        match self.0 {
52            true => f.write_str("enabled"),
53            false => f.write_str("disabled"),
54        }
55    }
56}
57
58impl super::sealed::Sealed for Flag {
59    fn is_active(&self) -> bool {
60        self.is_enabled()
61    }
62}
63
64impl ParameterValue for Flag {}
65
66impl Param<Flag> {
67    /// Create an identified flag with a fresh parameter id.
68    pub fn from_bool(value: bool) -> Self {
69        Self::initialized(ParamId::new(), Flag::new(value))
70    }
71
72    /// Return this parameter with its flag set to the given value.
73    pub fn with_value(self, value: bool) -> Self {
74        let mut flag = self.map(|flag| flag.with(value));
75        flag.is_active = value;
76        flag
77    }
78}
79
80impl Module for Param<Flag> {
81    fn map<M: ModuleMapper>(self, mapper: &mut M) -> Self {
82        mapper.map_flag(self)
83    }
84
85    fn visit<V: ModuleVisitor>(&self, visitor: &mut V) {
86        visitor.visit_flag(self);
87    }
88
89    fn to_device(self, _device: &Device) -> Self {
90        self
91    }
92
93    fn fork(self, _device: &Device) -> Self {
94        self
95    }
96
97    fn collect_devices(&self, devices: Devices) -> Devices {
98        devices
99    }
100
101    fn valid(&self) -> Self {
102        let enabled = self.is_active;
103        let mut flag = Self::initialized(self.id, Flag::new(false));
104        flag.is_active = enabled;
105        flag
106    }
107
108    fn train(self) -> Self {
109        let enabled = self.is_active;
110        let mut flag = Self::initialized(self.id, Flag::new(enabled));
111        flag.is_active = enabled;
112        flag
113    }
114}
115
116impl ModuleDisplayDefault for Param<Flag> {
117    fn content(&self, content: Content) -> Option<Content> {
118        content.add_formatted(&format!("{}", self.val())).optional()
119    }
120}
121
122impl ModuleDisplay for Param<Flag> {}
123
124#[cfg(test)]
125mod tests {
126    use crate::test_device;
127
128    use super::*;
129
130    #[test]
131    fn inference_temporarily_disables_an_enabled_flag() {
132        let flag = Param::<Flag>::from_bool(true);
133        let valid = flag.valid();
134
135        assert!(!valid.is_enabled());
136        assert!(valid.train().is_enabled());
137    }
138
139    #[test]
140    fn a_disabled_flag_stays_disabled_across_an_inference_round_trip() {
141        let flag = Param::<Flag>::from_bool(true).with_value(false);
142        let valid = flag.valid();
143
144        assert!(!valid.is_enabled());
145        assert!(!valid.train().is_enabled());
146    }
147
148    #[test]
149    fn enabling_a_flag_during_validation_is_preserved_by_train() {
150        let flag = Param::<Flag>::from_bool(false).valid().with_value(true);
151
152        assert!(flag.is_enabled());
153        assert!(flag.is_active);
154
155        let flag = flag.train();
156
157        assert!(flag.is_enabled());
158        assert!(flag.is_active);
159    }
160
161    #[test]
162    fn mapping_a_validation_flag_preserves_what_train_restores() {
163        let flag = Param::<Flag>::from_bool(true)
164            .valid()
165            .map(|flag| flag.with(false));
166
167        assert!(!flag.is_enabled());
168        assert!(flag.is_active);
169
170        let flag = flag.train();
171
172        assert!(flag.is_enabled());
173        assert!(flag.is_active);
174    }
175
176    #[test]
177    fn flag_transitions_preserve_identity() {
178        let flag = Param::<Flag>::from_bool(true);
179        let id = flag.id;
180
181        assert_eq!(flag.clone().with_value(false).id, id);
182        assert_eq!(flag.valid().id, id);
183        assert_eq!(flag.train().id, id);
184    }
185
186    #[test]
187    fn flags_are_device_independent() {
188        let flag = Param::<Flag>::from_bool(true);
189        let id = flag.id;
190        let device = test_device();
191
192        let moved = flag.clone().to_device(&device);
193        assert!(moved.is_enabled());
194        assert_eq!(moved.id, id);
195        assert!(moved.devices().is_empty());
196
197        let forked = flag.fork(&device);
198        assert!(forked.is_enabled());
199        assert_eq!(forked.id, id);
200        assert!(forked.devices().is_empty());
201    }
202
203    #[test]
204    fn no_grad_does_not_disable_a_flag() {
205        let flag = Param::<Flag>::from_bool(true).no_grad();
206
207        assert!(flag.is_enabled());
208    }
209
210    #[cfg(feature = "autodiff")]
211    #[test]
212    fn validation_state_is_restored_only_by_train() {
213        let device = test_device().autodiff();
214        let valid = Param::<Flag>::from_bool(true).valid();
215
216        assert!(!valid.is_enabled());
217        assert!(!valid.clone().to_device(&device).is_enabled());
218        assert!(!valid.clone().fork(&device).is_enabled());
219        assert!(valid.train().is_enabled());
220    }
221}