@@ -396,7 +396,7 @@ def _save_metrics_to_file(self, metrics_dict: Dict[str, Any]):
396396 def _compute_test_metrics (self , decoded_predictions : np .ndarray , labels : torch .Tensor , volume_name : str = None ):
397397 """Update configured torchmetrics using decoded predictions and print per-volume metrics."""
398398 pred_tensor = torch .from_numpy (decoded_predictions ).float ().to (self .device )
399- labels_tensor = labels .float ()
399+ labels_tensor = labels .float (). to ( pred_tensor . device )
400400
401401 # Remove batch and channel dimensions
402402 pred_tensor = pred_tensor .squeeze ()
@@ -770,13 +770,57 @@ def on_test_start(self):
770770 # Keep in training mode (e.g., for Monte Carlo Dropout uncertainty estimation)
771771 self .train ()
772772
773+ sliding_cfg = getattr (getattr (self .cfg , "inference" , None ), "sliding_window" , None )
774+ if bool (getattr (sliding_cfg , "keep_input_on_cpu" , False )):
775+ print (
776+ " Sliding-window CPU input mode enabled: keeping test image tensors on CPU "
777+ "and letting MONAI move window batches to the configured sw_device."
778+ )
779+
773780 def on_test_end (self ):
774781 """Called at the end of testing."""
775782 # Note: Metrics are logged in test_step with on_epoch=True,
776783 # which automatically computes and logs them at the end of testing.
777784 # Logging here causes a Lightning warning since self.log() is not allowed in on_test_end.
778785 pass
779786
787+ def transfer_batch_to_device (self , batch : Any , device : torch .device , dataloader_idx : int ) -> Any :
788+ """Keep large test/predict input volumes on CPU for MONAI sliding-window inference."""
789+ sliding_cfg = getattr (getattr (self .cfg , "inference" , None ), "sliding_window" , None )
790+ keep_input_on_cpu = bool (getattr (sliding_cfg , "keep_input_on_cpu" , False ))
791+
792+ trainer = getattr (self , "_trainer" , None )
793+ is_test_or_predict = bool (
794+ getattr (trainer , "testing" , False ) or getattr (trainer , "predicting" , False )
795+ )
796+
797+ preserve_cpu_input = keep_input_on_cpu and is_test_or_predict and isinstance (batch , dict )
798+ cpu_image = None
799+ cpu_label = None
800+ cpu_mask = None
801+ if preserve_cpu_input :
802+ image = batch .get ("image" )
803+ label = batch .get ("label" )
804+ mask = batch .get ("mask" )
805+ if torch .is_tensor (image ):
806+ cpu_image = image
807+ if torch .is_tensor (label ):
808+ cpu_label = label
809+ if torch .is_tensor (mask ):
810+ cpu_mask = mask
811+
812+ moved_batch = super ().transfer_batch_to_device (batch , device , dataloader_idx )
813+
814+ if preserve_cpu_input and isinstance (moved_batch , dict ):
815+ if cpu_image is not None :
816+ moved_batch ["image" ] = cpu_image
817+ if cpu_label is not None :
818+ moved_batch ["label" ] = cpu_label
819+ if cpu_mask is not None :
820+ moved_batch ["mask" ] = cpu_mask
821+
822+ return moved_batch
823+
780824 def test_step (self , batch : Dict [str , torch .Tensor ], batch_idx : int ) -> STEP_OUTPUT :
781825 """
782826 Test step with optional sliding-window inference and metrics computation.
@@ -885,6 +929,7 @@ def test_step(self, batch: Dict[str, torch.Tensor], batch_idx: int) -> STEP_OUTP
885929 print (f"INFERENCE PLAN: { volume_name } " )
886930 print (f"{ '=' * 70 } " )
887931 print (f"Input shape: { tuple (images .shape )} " )
932+ print (f"Input device: { images .device } " )
888933
889934 # Extract sliding window parameters if available
890935 if hasattr (self .cfg , 'inference' ) and hasattr (self .cfg .inference , 'sliding_window' ):
0 commit comments