11import os
2- import pathlib
3- from typing import Any , Dict , Optional , Union
2+ from typing import Dict , Optional
43import weakref
54from mpi4py .MPI import Comm , MIN
65import httomo
98from httomo .runner .loader import LoaderInterface
109from httomo .runner .method_wrapper import GpuTimeInfo , MethodWrapper
1110from httomo .runner .methods_repository_interface import MethodRepository
12- from httomo .utils import catchtime , xp
11+ from httomo .utils import catchtime , xp , gpu_enabled
1312
1413import h5py
15- import numpy as np
1614
1715
1816class SaveIntermediateFilesWrapper (GenericMethodWrapper ):
@@ -30,6 +28,7 @@ def __init__(
3028 output_mapping : Dict [str , str ] = {},
3129 out_dir : Optional [os .PathLike ] = None ,
3230 prev_method : Optional [MethodWrapper ] = None ,
31+ next_method_is_cpu : bool = False ,
3332 loader : Optional [LoaderInterface ] = None ,
3433 ** kwargs ,
3534 ):
@@ -45,6 +44,7 @@ def __init__(
4544 assert loader is not None
4645 self ._loader = loader
4746 assert prev_method is not None
47+ self ._next_method_is_cpu = next_method_is_cpu
4848
4949 filename = f"{ prev_method .task_id } -{ prev_method .package_name } -{ prev_method .method_name } "
5050 is_saving_recon = prev_method .module_path .endswith (".algorithm" )
@@ -62,21 +62,34 @@ def __init__(
6262 # make sure file gets closed properly
6363 weakref .finalize (self , self ._file .close )
6464
65+ def _transfer_data (self , block : T ) -> T :
66+ if block .is_cpu :
67+ return block
68+ if self ._next_method_is_cpu :
69+ # convert the whole (GPU) block to CPU if the next method is CPU
70+ self ._gpu_time_info = GpuTimeInfo ()
71+ with catchtime () as t :
72+ block .to_cpu ()
73+ self ._gpu_time_info .device2host = t .elapsed
74+ return block
75+ return block
76+
6577 def execute (self , block : T ) -> T :
66- # we overwrite the whole execute method here, as we do not need any of the helper
67- # methods from the Generic Wrapper
68- # What we know:
69- # - we do not transfer the dataset as a whole to CPU - only the data and angles locally
70- # (and never back)
71- # - the user does not insert this method - it's automatic - so no config params are
72- # relevant
73- # - we return just the input as it is
74- self ._gpu_time_info = GpuTimeInfo ()
75- with catchtime () as t :
76- data = (
77- block .data_unpadded if block .is_cpu else xp .asnumpy (block .data_unpadded )
78- )
79- self ._gpu_time_info .device2host += t .elapsed
78+ # we overwrite the most of the execute method here
79+ # we transfer the data to CPU only if the next method is CPU, otherwise we keep it on GPU
80+ # in case if save_intermediate is the last method we also keep the data on GPU
81+ block = self ._transfer_data (block )
82+
83+ if self ._next_method_is_cpu or not gpu_enabled :
84+ data = block .data_unpadded
85+ else :
86+ # Transfer data to CPU while the main block stays on GPU
87+ self ._gpu_time_info = GpuTimeInfo ()
88+
89+ with catchtime () as t :
90+ data = xp .asnumpy (block .data_unpadded )
91+
92+ self ._gpu_time_info .device2host += t .elapsed
8093
8194 MIN_BLOCK_LEN_PARAM = "minimum_block_length"
8295 if block .chunk_index_unpadded [block .slicing_dim ] == 0 and self .comm .size > 1 :
0 commit comments