ferrotensor_trainer/
scaler.rs1use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
7
8pub struct GradScaler {
35 scale: f32,
37 growth_factor: f32,
39 backoff_factor: f32,
41 growth_interval: u32,
43 consecutive_good_steps: u32,
45 found_overflow: bool,
47 enabled: bool,
49}
50
51#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
53pub struct GradScalerCheckpoint {
54 pub scale: f32,
56 pub growth_factor: f32,
58 pub backoff_factor: f32,
60 pub growth_interval: u32,
62 pub consecutive_good_steps: u32,
64 pub found_overflow: bool,
66 pub enabled: bool,
68}
69
70impl GradScaler {
71 pub fn new() -> Self {
75 Self {
76 scale: 65536.0, growth_factor: 2.0,
78 backoff_factor: 0.5,
79 growth_interval: 2000,
80 consecutive_good_steps: 0,
81 found_overflow: false,
82 enabled: true,
83 }
84 }
85
86 pub fn with_params(
88 init_scale: f32,
89 growth_factor: f32,
90 backoff_factor: f32,
91 growth_interval: u32,
92 ) -> Self {
93 Self {
94 scale: init_scale,
95 growth_factor,
96 backoff_factor,
97 growth_interval,
98 consecutive_good_steps: 0,
99 found_overflow: false,
100 enabled: true,
101 }
102 }
103
104 pub fn scale_factor(&self) -> f32 {
106 if self.enabled {
107 self.scale
108 } else {
109 1.0
110 }
111 }
112
113 pub fn scale_loss(&self, loss: &UnifiedTensor, ctx: &WgpuContext, engine: &ComputeEngine) {
117 if !self.enabled {
118 return;
119 }
120
121 engine.gradient_scale(ctx, loss, self.scale);
123 }
124
125 pub fn unscale_and_check(
130 &mut self,
131 gradients: &[UnifiedTensor],
132 ctx: &WgpuContext,
133 engine: &ComputeEngine,
134 ) -> bool {
135 if !self.enabled {
136 return true;
137 }
138
139 let inv_scale = 1.0 / self.scale;
140 let mut has_overflow = false;
141
142 for grad in gradients {
143 engine.gradient_scale(ctx, grad, inv_scale);
145
146 let data = pollster::block_on(grad.to_vec(ctx));
148 if data.iter().any(|&x| x.is_infinite() || x.is_nan()) {
149 has_overflow = true;
150 break;
151 }
152 }
153
154 self.found_overflow = has_overflow;
155 !has_overflow
156 }
157
158 pub fn update(&mut self) {
162 if !self.enabled {
163 return;
164 }
165
166 if self.found_overflow {
167 self.scale *= self.backoff_factor;
169 self.consecutive_good_steps = 0;
170
171 if self.scale < 1.0 {
173 self.scale = 1.0;
174 }
175 } else {
176 self.consecutive_good_steps += 1;
178
179 if self.consecutive_good_steps >= self.growth_interval {
181 self.scale *= self.growth_factor;
182 self.consecutive_good_steps = 0;
183
184 if self.scale > 65536.0 * 256.0 {
186 self.scale = 65536.0 * 256.0;
187 }
188 }
189 }
190
191 self.found_overflow = false;
193 }
194
195 pub fn had_overflow(&self) -> bool {
197 self.found_overflow
198 }
199
200 pub fn set_enabled(&mut self, enabled: bool) {
202 self.enabled = enabled;
203 }
204
205 pub fn is_enabled(&self) -> bool {
207 self.enabled
208 }
209
210 pub fn stats(&self) -> GradScalerStats {
212 GradScalerStats {
213 scale: self.scale,
214 consecutive_good_steps: self.consecutive_good_steps,
215 enabled: self.enabled,
216 }
217 }
218
219 pub fn checkpoint(&self) -> GradScalerCheckpoint {
221 GradScalerCheckpoint {
222 scale: self.scale,
223 growth_factor: self.growth_factor,
224 backoff_factor: self.backoff_factor,
225 growth_interval: self.growth_interval,
226 consecutive_good_steps: self.consecutive_good_steps,
227 found_overflow: self.found_overflow,
228 enabled: self.enabled,
229 }
230 }
231
232 pub fn load_checkpoint(&mut self, checkpoint: &GradScalerCheckpoint) {
234 self.scale = checkpoint.scale;
235 self.growth_factor = checkpoint.growth_factor;
236 self.backoff_factor = checkpoint.backoff_factor;
237 self.growth_interval = checkpoint.growth_interval;
238 self.consecutive_good_steps = checkpoint.consecutive_good_steps;
239 self.found_overflow = checkpoint.found_overflow;
240 self.enabled = checkpoint.enabled;
241 }
242}
243
244impl Default for GradScaler {
245 fn default() -> Self {
246 Self::new()
247 }
248}
249
250#[derive(Debug, Clone)]
252pub struct GradScalerStats {
253 pub scale: f32,
255 pub consecutive_good_steps: u32,
257 pub enabled: bool,
259}
260
261impl std::fmt::Display for GradScalerStats {
262 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
263 write!(
264 f,
265 "GradScaler(scale={:.0}, good_steps={}, enabled={})",
266 self.scale, self.consecutive_good_steps, self.enabled
267 )
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_scaler_creation() {
277 let scaler = GradScaler::new();
278 assert_eq!(scaler.scale_factor(), 65536.0);
279 assert!(scaler.is_enabled());
280 }
281
282 #[test]
283 fn test_scaler_update_no_overflow() {
284 let mut scaler = GradScaler::with_params(1024.0, 2.0, 0.5, 5);
285
286 for _ in 0..5 {
288 scaler.found_overflow = false;
289 scaler.update();
290 }
291
292 assert_eq!(scaler.scale, 2048.0);
294 }
295
296 #[test]
297 fn test_scaler_update_with_overflow() {
298 let mut scaler = GradScaler::with_params(1024.0, 2.0, 0.5, 5);
299
300 scaler.found_overflow = true;
302 scaler.update();
303
304 assert_eq!(scaler.scale, 512.0);
306 assert_eq!(scaler.consecutive_good_steps, 0);
307 }
308
309 #[test]
310 fn test_scaler_disabled() {
311 let mut scaler = GradScaler::new();
312 scaler.set_enabled(false);
313
314 assert_eq!(scaler.scale_factor(), 1.0);
315 assert!(!scaler.is_enabled());
316 }
317}