use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
pub struct GradScaler {
scale: f32,
growth_factor: f32,
backoff_factor: f32,
growth_interval: u32,
consecutive_good_steps: u32,
found_overflow: bool,
enabled: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct GradScalerCheckpoint {
pub scale: f32,
pub growth_factor: f32,
pub backoff_factor: f32,
pub growth_interval: u32,
pub consecutive_good_steps: u32,
pub found_overflow: bool,
pub enabled: bool,
}
impl GradScaler {
pub fn new() -> Self {
Self {
scale: 65536.0, growth_factor: 2.0,
backoff_factor: 0.5,
growth_interval: 2000,
consecutive_good_steps: 0,
found_overflow: false,
enabled: true,
}
}
pub fn with_params(
init_scale: f32,
growth_factor: f32,
backoff_factor: f32,
growth_interval: u32,
) -> Self {
Self {
scale: init_scale,
growth_factor,
backoff_factor,
growth_interval,
consecutive_good_steps: 0,
found_overflow: false,
enabled: true,
}
}
pub fn scale_factor(&self) -> f32 {
if self.enabled {
self.scale
} else {
1.0
}
}
pub fn scale_loss(&self, loss: &UnifiedTensor, ctx: &WgpuContext, engine: &ComputeEngine) {
if !self.enabled {
return;
}
engine.gradient_scale(ctx, loss, self.scale);
}
pub fn unscale_and_check(
&mut self,
gradients: &[UnifiedTensor],
ctx: &WgpuContext,
engine: &ComputeEngine,
) -> bool {
if !self.enabled {
return true;
}
let inv_scale = 1.0 / self.scale;
let mut has_overflow = false;
for grad in gradients {
engine.gradient_scale(ctx, grad, inv_scale);
let data = pollster::block_on(grad.to_vec(ctx));
if data.iter().any(|&x| x.is_infinite() || x.is_nan()) {
has_overflow = true;
break;
}
}
self.found_overflow = has_overflow;
!has_overflow
}
pub fn update(&mut self) {
if !self.enabled {
return;
}
if self.found_overflow {
self.scale *= self.backoff_factor;
self.consecutive_good_steps = 0;
if self.scale < 1.0 {
self.scale = 1.0;
}
} else {
self.consecutive_good_steps += 1;
if self.consecutive_good_steps >= self.growth_interval {
self.scale *= self.growth_factor;
self.consecutive_good_steps = 0;
if self.scale > 65536.0 * 256.0 {
self.scale = 65536.0 * 256.0;
}
}
}
self.found_overflow = false;
}
pub fn had_overflow(&self) -> bool {
self.found_overflow
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn stats(&self) -> GradScalerStats {
GradScalerStats {
scale: self.scale,
consecutive_good_steps: self.consecutive_good_steps,
enabled: self.enabled,
}
}
pub fn checkpoint(&self) -> GradScalerCheckpoint {
GradScalerCheckpoint {
scale: self.scale,
growth_factor: self.growth_factor,
backoff_factor: self.backoff_factor,
growth_interval: self.growth_interval,
consecutive_good_steps: self.consecutive_good_steps,
found_overflow: self.found_overflow,
enabled: self.enabled,
}
}
pub fn load_checkpoint(&mut self, checkpoint: &GradScalerCheckpoint) {
self.scale = checkpoint.scale;
self.growth_factor = checkpoint.growth_factor;
self.backoff_factor = checkpoint.backoff_factor;
self.growth_interval = checkpoint.growth_interval;
self.consecutive_good_steps = checkpoint.consecutive_good_steps;
self.found_overflow = checkpoint.found_overflow;
self.enabled = checkpoint.enabled;
}
}
impl Default for GradScaler {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct GradScalerStats {
pub scale: f32,
pub consecutive_good_steps: u32,
pub enabled: bool,
}
impl std::fmt::Display for GradScalerStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"GradScaler(scale={:.0}, good_steps={}, enabled={})",
self.scale, self.consecutive_good_steps, self.enabled
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scaler_creation() {
let scaler = GradScaler::new();
assert_eq!(scaler.scale_factor(), 65536.0);
assert!(scaler.is_enabled());
}
#[test]
fn test_scaler_update_no_overflow() {
let mut scaler = GradScaler::with_params(1024.0, 2.0, 0.5, 5);
for _ in 0..5 {
scaler.found_overflow = false;
scaler.update();
}
assert_eq!(scaler.scale, 2048.0);
}
#[test]
fn test_scaler_update_with_overflow() {
let mut scaler = GradScaler::with_params(1024.0, 2.0, 0.5, 5);
scaler.found_overflow = true;
scaler.update();
assert_eq!(scaler.scale, 512.0);
assert_eq!(scaler.consecutive_good_steps, 0);
}
#[test]
fn test_scaler_disabled() {
let mut scaler = GradScaler::new();
scaler.set_enabled(false);
assert_eq!(scaler.scale_factor(), 1.0);
assert!(!scaler.is_enabled());
}
}