88
99"""
1010
11- from contextlib import nullcontext
1211from datetime import datetime
1312from numcodecs import blosc
1413from torch .optim .lr_scheduler import CosineAnnealingLR
@@ -71,6 +70,7 @@ def __init__(
7170 max_epochs = 400 ,
7271 model = None ,
7372 use_amp = True ,
73+ use_amp_validation = False ,
7474 checkpoint_weights = None ,
7575 fg_weight = 20.0 ,
7676 num_workers = None ,
@@ -96,7 +96,12 @@ def __init__(
9696 model : None or nn.Module, optional
9797 Model to be trained on the given datasets. Default is None.
9898 use_amp : bool, optional
99- Indication of whether to use mixed precision. Default is True.
99+ Whether to use CUDA float16 autocast and gradient scaling during
100+ training. Default is True.
101+ use_amp_validation : bool, optional
102+ Whether to use CUDA float16 autocast during validation and
103+ checkpoint selection. Default is False so validation matches FP32
104+ production inference.
100105 val_every : int, optional
101106 Run validation (and checkpoint selection) every this many epochs;
102107 the final epoch is always validated. The count-space metrics are
@@ -120,6 +125,8 @@ def __init__(
120125 self .prefetch = prefetch
121126 self .val_every = max (1 , int (val_every ))
122127 self .seed = seed
128+ self .use_amp = bool (use_amp )
129+ self .use_amp_validation = bool (use_amp_validation )
123130
124131 self .codec = blosc .Blosc (cname = "zstd" , clevel = 5 , shuffle = blosc .SHUFFLE )
125132 self .criterion = SignalPreservingLoss (fg_weight = fg_weight )
@@ -134,15 +141,10 @@ def __init__(
134141 self .scheduler = CosineAnnealingLR (self .optimizer , T_max = max_epochs )
135142 self .writer = SummaryWriter (log_dir = log_dir )
136143
137- if use_amp :
138- self .autocast = torch .autocast (device_type = "cuda" , dtype = torch .float16 )
139- else :
140- self .autocast = nullcontext ()
141-
142144 # Scale the loss before backward so small float16 gradients do not
143145 # underflow (and are unscaled before the step). Disabled => no-op, so
144146 # the same code path is correct with and without AMP.
145- self .scaler = torch .amp .GradScaler ("cuda" , enabled = use_amp )
147+ self .scaler = torch .amp .GradScaler ("cuda" , enabled = self . use_amp )
146148
147149 # --- Core Routines ---
148150 def run (self , train_dataset , val_dataset ):
@@ -232,7 +234,9 @@ def train_step(self, train_dataloader, epoch):
232234 self .model .train ()
233235 for x , y , fg_mask in train_dataloader :
234236 # Forward pass
235- hat_y , loss = self .forward_pass (x , y , fg_mask )
237+ hat_y , loss = self .forward_pass (
238+ x , y , fg_mask , use_amp = self .use_amp
239+ )
236240
237241 # Backward pass (loss-scaled for AMP stability)
238242 self .optimizer .zero_grad ()
@@ -276,7 +280,9 @@ def validate_step(self, val_dataloader, epoch):
276280 self .model .eval ()
277281 for x , y , raw , fg_mask in val_dataloader :
278282 # Run model
279- hat_y , loss = self .forward_pass (x , y , fg_mask )
283+ hat_y , loss = self .forward_pass (
284+ x , y , fg_mask , use_amp = self .use_amp_validation
285+ )
280286
281287 # Evaluate result
282288 losses .append (loss .detach ().cpu ())
@@ -313,7 +319,7 @@ def validate_step(self, val_dataloader, epoch):
313319 self .save_model (epoch , score )
314320 return loss , cratio , is_best
315321
316- def forward_pass (self , x , y , fg_mask ):
322+ def forward_pass (self , x , y , fg_mask , use_amp = None ):
317323 """
318324 Performs a forward pass through the model and computes loss.
319325
@@ -325,6 +331,9 @@ def forward_pass(self, x, y, fg_mask):
325331 Target tensor with shape (B, C, D, H, W).
326332 fg_mask : torch.Tensor
327333 Foreground mask (0/1) with shape (B, C, D, H, W).
334+ use_amp : bool, optional
335+ Whether to autocast this forward pass to float16. Defaults to the
336+ training AMP setting.
328337
329338 Returns
330339 -------
@@ -333,7 +342,13 @@ def forward_pass(self, x, y, fg_mask):
333342 loss : torch.Tensor
334343 Computed loss value.
335344 """
336- with self .autocast :
345+ if use_amp is None :
346+ use_amp = self .use_amp
347+ with torch .autocast (
348+ device_type = torch .device (self .device ).type ,
349+ dtype = torch .float16 ,
350+ enabled = bool (use_amp ),
351+ ):
337352 x = x .to (self .device )
338353 y = y .to (self .device )
339354 fg_mask = fg_mask .to (self .device )
@@ -437,6 +452,8 @@ def save_config(self, config):
437452 "prefetch" : self .prefetch ,
438453 "val_every" : self .val_every ,
439454 "seed" : self .seed ,
455+ "use_amp" : self .use_amp ,
456+ "use_amp_validation" : self .use_amp_validation ,
440457 "fg_weight" : getattr (self .criterion , "fg_weight" , None ),
441458 "checkpoint_weights" : self .checkpoint_weights ,
442459 "lr" : self .optimizer .param_groups [0 ]["lr" ],
0 commit comments