burn_optim/grad_clipping/
base.rs1use burn_core as burn;
2
3use burn::{
4 config::Config,
5 tensor::{DType, FloatDType, Tensor},
6};
7
8#[derive(Config, Debug)]
10pub enum GradientClippingConfig {
11 Value(f32),
13
14 Norm(f32),
16}
17
18impl GradientClippingConfig {
19 pub fn init(&self) -> GradientClipping {
25 match self {
26 GradientClippingConfig::Value(val) => GradientClipping::Value(*val),
27 GradientClippingConfig::Norm(val) => GradientClipping::Norm(*val),
28 }
29 }
30}
31
32#[derive(Clone)]
36pub enum GradientClipping {
37 Value(f32),
39
40 Norm(f32),
42}
43
44impl GradientClipping {
45 pub fn clip_gradient<const D: usize>(&self, grad: Tensor<D>) -> Tensor<D> {
55 match self {
56 GradientClipping::Value(threshold) => self.clip_by_value(grad, *threshold),
57 GradientClipping::Norm(max_norm) => self.clip_by_norm(grad, *max_norm),
58 }
59 }
60
61 fn clip_by_value<const D: usize>(&self, grad: Tensor<D>, threshold: f32) -> Tensor<D> {
62 let greater_mask = grad.clone().greater_scalar(threshold);
63 let lower_mask = grad.clone().lower_scalar(-threshold);
64
65 let clipped_grad = grad.mask_fill(greater_mask, threshold);
66
67 clipped_grad.mask_fill(lower_mask, -threshold)
68 }
69
70 fn clip_by_norm<const D: usize>(&self, grad: Tensor<D>, threshold: f32) -> Tensor<D> {
71 let dtype = grad.dtype();
74 let grad = match dtype {
75 DType::F16 | DType::BF16 => grad.cast(FloatDType::F32),
76 _ => grad,
77 };
78
79 let norm = Self::l2_norm(grad.clone());
80 let min_positive = grad
81 .dtype()
82 .finfo()
83 .unwrap_or(FloatDType::F32.finfo())
84 .min_positive;
85 let clip_coef = threshold / norm.add_scalar(min_positive);
86 let clip_coef_clamped = clip_coef.clamp_max(1.0);
87 grad.mul(clip_coef_clamped.unsqueeze()).cast(dtype)
88 }
89
90 fn l2_norm<const D: usize>(tensor: Tensor<D>) -> Tensor<1> {
91 let squared = tensor.square();
92 let sum = squared.sum();
93 sum.sqrt()
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use burn::tensor::Tensor;
101
102 #[test]
103 fn test_clip_by_value() {
104 let gradient: Tensor<2> = Tensor::from_floats(
105 [
106 [0.6294, 0.0940, 0.8176, 0.8824, 0.5228, 0.4310],
107 [0.7152, 0.9559, 0.7893, 0.5684, 0.5939, 0.8883],
108 ],
109 &Default::default(),
110 );
111
112 let clipped_gradient = GradientClipping::Value(0.5).clip_gradient(gradient);
113 let clipped_gradient_data = clipped_gradient.into_data();
114
115 for value in clipped_gradient_data.iter::<f32>() {
116 assert!(value <= 0.5);
117 }
118 }
119
120 #[test]
121 fn test_clip_by_norm() {
122 let gradient: Tensor<2> = Tensor::from_floats(
123 [
124 [0.6294, 0.0940, 0.8176, 0.8824, 0.5228, 0.4310],
125 [0.7152, 0.9559, 0.7893, 0.5684, 0.5939, 0.8883],
126 ],
127 &Default::default(),
128 );
129
130 let clipped_gradient = GradientClipping::Norm(2.2).clip_gradient(gradient);
131 let clipped_gradient_data = clipped_gradient.into_data();
132
133 for value in clipped_gradient_data.iter::<f32>() {
134 assert!(value <= 0.88);
135 }
136 }
137 #[test]
138 fn test_clip_by_norm_no_clipping() {
139 let gradient: Tensor<2> = Tensor::from_floats(
140 [[0.3, 0.4, 0.5, 0.2], [0.1, 0.6, 0.3, 0.4]],
141 &Default::default(),
142 );
143
144 let clipped_gradient = GradientClipping::Norm(2.2).clip_gradient(gradient.clone());
145
146 clipped_gradient
147 .into_data()
148 .assert_eq(&gradient.into_data(), true);
149 }
150
151 #[test]
152 fn test_clip_by_norm_f16_does_not_overflow() {
153 let gradient = Tensor::<1>::from_floats([300.0], &Default::default()).cast(FloatDType::F16);
154
155 let clipped_gradient = GradientClipping::Norm(1.0).clip_gradient(gradient);
156
157 assert_eq!(clipped_gradient.dtype(), DType::F16);
158 let actual = clipped_gradient.into_scalar::<f32>();
159 assert!((actual - 1.0).abs() < 1e-3, "actual={actual}");
160 }
161
162 #[test]
163 fn test_clip_by_norm_f16_norm_above_f16_max() {
164 let gradient =
165 Tensor::<1>::from_floats([60000.0, 60000.0], &Default::default()).cast(FloatDType::F16);
166
167 let clipped_gradient = GradientClipping::Norm(1.0).clip_gradient(gradient);
168
169 let expected = core::f32::consts::FRAC_1_SQRT_2;
170 for value in clipped_gradient.into_data().iter::<f32>() {
171 assert!((value - expected).abs() < 1e-3, "value={value}");
172 }
173 }
174}