@@ -181,6 +181,80 @@ def test_half_precision_gaussian_weights_with_many_bins_are_finite(self, dtype):
181181 self .assertEqual (probability .dtype , image .dtype )
182182 self .assertEqual (weight .device , image .device )
183183 self .assertEqual (probability .device , image .device )
184+ torch .testing .assert_close (
185+ weight .float ().sum (dim = - 1 ), torch .ones_like (weight [..., 0 ], dtype = torch .float32 ), rtol = 0.0 , atol = 5e-3
186+ )
187+ torch .testing .assert_close (
188+ probability .float ().sum (dim = - 1 ),
189+ torch .ones_like (probability [..., 0 ], dtype = torch .float32 ),
190+ rtol = 0.0 ,
191+ atol = 5e-3 ,
192+ )
193+
194+ @parameterized .expand ([(torch .float16 ,), (torch .bfloat16 ,)])
195+ def test_module_cast_with_many_bins_remains_finite (self , dtype ):
196+ """Verify module dtype conversion cannot overflow Gaussian parameters.
197+
198+ Args:
199+ dtype: reduced-precision floating-point dtype to test.
200+ """
201+ image = torch .linspace (0.0 , 1.0 , 64 , dtype = dtype ).reshape (1 , 1 , 8 , 8 ).requires_grad_ ()
202+ target = torch .flip (image .detach (), dims = (- 1 ,))
203+ loss = GlobalMutualInformationLoss (kernel_type = "gaussian" , num_bins = 256 ).to (dtype = dtype )
204+
205+ weight , probability = loss .parzen_windowing_gaussian (image )
206+ result = loss (image , target )
207+
208+ self .assertTrue (torch .isfinite (weight ).all ())
209+ self .assertTrue (torch .isfinite (probability ).all ())
210+ self .assertTrue (torch .isfinite (result ))
211+ result .backward ()
212+ self .assertIsNotNone (image .grad )
213+ self .assertTrue (torch .isfinite (image .grad ).all ())
214+
215+ def test_float16_default_dtype_with_many_bins_remains_finite (self ):
216+ """Verify construction under a float16 default keeps Gaussian parameters finite."""
217+ original_dtype = torch .get_default_dtype ()
218+ try :
219+ torch .set_default_dtype (torch .float16 )
220+ image = torch .linspace (0.0 , 1.0 , 64 ).reshape (1 , 1 , 8 , 8 ).requires_grad_ ()
221+ target = torch .flip (image .detach (), dims = (- 1 ,))
222+ loss = GlobalMutualInformationLoss (kernel_type = "gaussian" , num_bins = 256 )
223+
224+ weight , probability = loss .parzen_windowing_gaussian (image )
225+ result = loss (image , target )
226+
227+ self .assertTrue (torch .isfinite (weight ).all ())
228+ self .assertTrue (torch .isfinite (probability ).all ())
229+ self .assertTrue (torch .isfinite (result ))
230+ result .backward ()
231+ self .assertIsNotNone (image .grad )
232+ self .assertTrue (torch .isfinite (image .grad ).all ())
233+ finally :
234+ torch .set_default_dtype (original_dtype )
235+
236+ @parameterized .expand ([(torch .float16 ,), (torch .bfloat16 ,)])
237+ def test_half_precision_nonconstant_images_match_float32 (self , dtype ):
238+ """Verify nonconstant reduced-precision loss tracks float32.
239+
240+ Args:
241+ dtype: reduced-precision floating-point dtype to test.
242+ """
243+ pred_float = torch .linspace (0.0 , 1.0 , 64 ).reshape (1 , 1 , 8 , 8 )
244+ target_float = torch .flip (pred_float , dims = (- 1 ,))
245+ loss = GlobalMutualInformationLoss (kernel_type = "gaussian" )
246+ expected = loss (pred_float , target_float )
247+ pred = pred_float .to (dtype = dtype ).requires_grad_ ()
248+ target = target_float .to (dtype = dtype )
249+
250+ result = loss (pred , target )
251+
252+ self .assertTrue (torch .isfinite (result ))
253+ self .assertEqual (result .dtype , dtype )
254+ torch .testing .assert_close (result .float (), expected , rtol = 1e-2 , atol = 1e-2 )
255+ result .backward ()
256+ self .assertIsNotNone (pred .grad )
257+ self .assertTrue (torch .isfinite (pred .grad ).all ())
184258
185259 @parameterized .expand ([(torch .float16 ,), (torch .bfloat16 ,)])
186260 def test_half_precision_large_constant_volume_is_finite (self , dtype ):
@@ -200,6 +274,37 @@ def test_half_precision_large_constant_volume_is_finite(self, dtype):
200274 self .assertEqual (pred .grad .dtype , pred .dtype )
201275 self .assertEqual (pred .grad .device , pred .device )
202276
277+ def test_cpu_float16_autocast_nonconstant_images_match_float32 (self ):
278+ """Verify nonconstant CPU autocast loss matches float32."""
279+ pred = torch .linspace (0.0 , 1.0 , 64 ).reshape (1 , 1 , 8 , 8 ).requires_grad_ ()
280+ target = torch .flip (pred .detach (), dims = (- 1 ,))
281+ loss = GlobalMutualInformationLoss (kernel_type = "gaussian" )
282+ expected = loss (pred , target ).detach ()
283+
284+ with torch .autocast (device_type = "cpu" , dtype = torch .float16 ):
285+ result = loss (pred , target )
286+
287+ self .assertTrue (torch .isfinite (result ))
288+ self .assertEqual (result .dtype , pred .dtype )
289+ torch .testing .assert_close (result , expected )
290+ result .backward ()
291+ self .assertIsNotNone (pred .grad )
292+ self .assertTrue (torch .isfinite (pred .grad ).all ())
293+
294+ def test_scripted_cpu_float16_autocast_large_volume_is_finite (self ):
295+ """Verify scripted loss avoids float16 histogram overflow under autocast."""
296+ pred = torch .zeros ((1 , 1 , 257 , 257 ), requires_grad = True )
297+ target = torch .zeros_like (pred )
298+ loss = torch .jit .script (GlobalMutualInformationLoss (kernel_type = "gaussian" ))
299+
300+ with torch .autocast (device_type = "cpu" , dtype = torch .float16 ):
301+ result = loss (pred , target )
302+
303+ self .assertTrue (torch .isfinite (result ))
304+ result .backward ()
305+ self .assertIsNotNone (pred .grad )
306+ self .assertTrue (torch .isfinite (pred .grad ).all ())
307+
203308 def test_cpu_float16_autocast_large_volume_is_finite (self ):
204309 """Verify CPU float16 autocast avoids histogram accumulation overflow."""
205310 pred = torch .zeros ((1 , 1 , 48 , 48 , 48 ), requires_grad = True )
@@ -210,6 +315,7 @@ def test_cpu_float16_autocast_large_volume_is_finite(self):
210315 result = loss (pred , target )
211316
212317 self .assertTrue (torch .isfinite (result ))
318+ self .assertEqual (result .dtype , pred .dtype )
213319 result .backward ()
214320 self .assertIsNotNone (pred .grad )
215321 self .assertTrue (torch .isfinite (pred .grad ).all ())
0 commit comments