Skip to content

Commit f20d3f6

Browse files
fix(losses): register buffers in GlobalMutualInformationLoss
When kernel_type="gaussian", `preterm` and `bin_centers` were stored as plain tensor attributes via simple assignment. This means they are not registered in PyTorch's module buffer system, so calling `loss.to("cuda")` or `loss.cuda()` does not move these tensors to the target device. Each forward pass had to call `.to(img)` to patch the device mismatch at runtime, which is both redundant and misleading. Use `register_buffer(..., persistent=False)` so that both tensors are properly tracked by the module and automatically move with `.to()` / `.cuda()` / `.cpu()` calls, consistent with the pattern already used by `LocalNormalizedCrossCorrelationLoss`. The `.to(img)` calls in `parzen_windowing_gaussian` are retained for dtype coercion (e.g. float16 inference). Adds `TestGlobalMutualInformationLossBuffers` to verify buffer registration and that b-spline mode does not create gaussian buffers. Closes #8819 Signed-off-by: Oleksandr Sanin <alexaaander.sanin@gmail.com>
1 parent 27a03ef commit f20d3f6

2 files changed

Lines changed: 27 additions & 2 deletions

File tree

monai/losses/image_dissimilarity.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,11 @@ def __init__(
233233
self.kernel_type = look_up_option(kernel_type, ["gaussian", "b-spline"])
234234
self.num_bins = num_bins
235235
self.kernel_type = kernel_type
236+
self.preterm: torch.Tensor
237+
self.bin_centers: torch.Tensor
236238
if self.kernel_type == "gaussian":
237-
self.preterm = 1 / (2 * sigma**2)
238-
self.bin_centers = bin_centers[None, None, ...]
239+
self.register_buffer("preterm", 1 / (2 * sigma**2), persistent=False)
240+
self.register_buffer("bin_centers", bin_centers[None, None, ...], persistent=False)
239241
self.smooth_nr = float(smooth_nr)
240242
self.smooth_dr = float(smooth_dr)
241243

tests/losses/image_dissimilarity/test_global_mutual_information_loss.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,28 @@ def test_ill_opts(self, num_bins, reduction, expected_exception, expected_messag
145145
GlobalMutualInformationLoss(num_bins=num_bins, reduction=reduction)(pred, target)
146146

147147

148+
class TestGlobalMutualInformationLossBuffers(unittest.TestCase):
149+
def test_gaussian_kernel_registers_buffers(self):
150+
loss = GlobalMutualInformationLoss(kernel_type="gaussian")
151+
# preterm and bin_centers must be registered buffers so .to() moves them
152+
self.assertIn("preterm", loss._buffers)
153+
self.assertIn("bin_centers", loss._buffers)
154+
self.assertFalse(loss.preterm.requires_grad)
155+
self.assertFalse(loss.bin_centers.requires_grad)
156+
self.assertEqual(loss.bin_centers.ndim, 3)
157+
158+
def test_bspline_kernel_has_no_gaussian_buffers(self):
159+
loss = GlobalMutualInformationLoss(kernel_type="b-spline")
160+
self.assertNotIn("preterm", loss._buffers)
161+
self.assertNotIn("bin_centers", loss._buffers)
162+
163+
def test_gaussian_kernel_forward_correct(self):
164+
pred = torch.rand(2, 1, 8, 8, dtype=torch.float32)
165+
target = torch.rand(2, 1, 8, 8, dtype=torch.float32)
166+
loss = GlobalMutualInformationLoss(kernel_type="gaussian")
167+
result = loss(pred, target)
168+
self.assertEqual(result.shape, torch.Size([]))
169+
170+
148171
if __name__ == "__main__":
149172
unittest.main()

0 commit comments

Comments
 (0)