11import gc
2+ import json
23import os
4+ import shutil
35
46import numpy as np
57import requests
1618from lightx2v .utils .generate_task_id import generate_task_id
1719from lightx2v .utils .global_paras import CALIB
1820from lightx2v .utils .profiler import *
19- from lightx2v .utils .utils import fixed_shape_resize , get_optimal_patched_size_with_sp , isotropic_crop_resize , mux_audio_from_video , save_to_image , save_to_video , wan_vae_to_comfy
21+ from lightx2v .utils .utils import fixed_shape_resize , get_optimal_patched_size_with_sp , is_main_process , isotropic_crop_resize , mux_audio_from_video , save_to_image , save_to_video , wan_vae_to_comfy
2022from lightx2v_platform .base .global_var import AI_DEVICE
2123
2224torch_device_module = getattr (torch , AI_DEVICE )
@@ -88,6 +90,14 @@ def __init__(self, config):
8890 super ().__init__ (config )
8991 self .has_prompt_enhancer = False
9092 self .progress_callback = None
93+ self .reuse_cache_path = self .config .get ("reuse_cache_path" )
94+ if self .enable_reuse and not self .reuse_cache_path :
95+ raise ValueError ("enable_reuse requires reuse_cache_path" )
96+ self .reuse_cache_dir = None
97+ self .reuse_cache_stage_dir = None
98+ self .final_result_path = None
99+ self .previous_result_path = None
100+ self .work_result_path = None
91101 if self .config ["task" ] == "t2v" and self .config .get ("sub_servers" , {}).get ("prompt_enhancer" ) is not None :
92102 self .has_prompt_enhancer = True
93103 if not self .check_sub_servers ("prompt_enhancer" ):
@@ -98,6 +108,116 @@ def __init__(self, config):
98108 self .set_init_device ()
99109 self .init_scheduler ()
100110
111+ def reuse_key (self ):
112+ raise NotImplementedError
113+
114+ def reuse_inputs_path (self , cache_dir ):
115+ rank = dist .get_rank () if dist .is_initialized () else 0
116+ return os .path .join (cache_dir , f"inputs_rank_{ rank :05d} .pt" )
117+
118+ def reuse_input_info (self ):
119+ return {}
120+
121+ def load_reuse_state (self , map_location = AI_DEVICE ):
122+ manifest_path = os .path .join (self .reuse_cache_dir , "manifest.json" )
123+ if not os .path .isfile (manifest_path ):
124+ raise RuntimeError (f"No previous successful { type (self ).__name__ } request is available for reuse" )
125+ with open (manifest_path , encoding = "utf-8" ) as f :
126+ manifest = json .load (f )
127+ if manifest ["reuse_key" ] != self .reuse_key ():
128+ raise ValueError ("Reuse inputs must match the previous successful request" )
129+ self .previous_result_path = manifest ["result_path" ]
130+ return torch .load (self .reuse_inputs_path (self .reuse_cache_dir ), map_location = map_location , weights_only = True )
131+
132+ def load_reused_inputs (self ):
133+ cached = self .load_reuse_state ()
134+ for name , value in cached ["input_info" ].items ():
135+ setattr (self .input_info , name , value )
136+ logger .info ("[Reuse] Loaded the previous request's input encoder output from disk" )
137+ return cached ["inputs" ]
138+
139+ def save_reuse_inputs (self ):
140+ torch .save (
141+ {"inputs" : self .inputs , "input_info" : self .reuse_input_info ()},
142+ self .reuse_inputs_path (self .reuse_cache_stage_dir ),
143+ )
144+
145+ def prepare_reuse_output (self ):
146+ self .reuse_cache_dir = None
147+ self .reuse_cache_stage_dir = None
148+ self .final_result_path = None
149+ self .previous_result_path = None
150+ self .work_result_path = None
151+
152+ output_path = self .input_info .save_result_path
153+ local_output = bool (output_path ) and not output_path .startswith (("http://" , "https://" , "rtmp://" ))
154+ reuse_cache_enabled = self .enable_reuse and local_output and not self .input_info .return_result_tensor
155+ if self .reuse and not reuse_cache_enabled :
156+ raise ValueError (f"{ type (self ).__name__ } reuse requires a local output and return_result_tensor=false" )
157+ if not reuse_cache_enabled :
158+ return
159+
160+ self .final_result_path = os .path .abspath (os .path .expanduser (output_path ))
161+ self .reuse_cache_dir = os .path .abspath (os .path .expanduser (self .reuse_cache_path ))
162+ self .reuse_cache_stage_dir = f"{ self .reuse_cache_dir } .tmp"
163+
164+ def stage_reuse_cache (self ):
165+ if self .reuse_cache_dir is None :
166+ return
167+
168+ if is_main_process ():
169+ shutil .rmtree (self .reuse_cache_stage_dir , ignore_errors = True )
170+ os .makedirs (self .reuse_cache_stage_dir )
171+ if dist .is_initialized ():
172+ dist .barrier ()
173+
174+ if self .reuse :
175+ shutil .copy2 (
176+ self .reuse_inputs_path (self .reuse_cache_dir ),
177+ self .reuse_inputs_path (self .reuse_cache_stage_dir ),
178+ )
179+ else :
180+ self .save_reuse_inputs ()
181+
182+ if dist .is_initialized ():
183+ dist .barrier ()
184+ if is_main_process ():
185+ with open (os .path .join (self .reuse_cache_stage_dir , "manifest.json" ), "w" , encoding = "utf-8" ) as f :
186+ json .dump (
187+ {"reuse_key" : self .reuse_key (), "result_path" : self .final_result_path },
188+ f ,
189+ ensure_ascii = False ,
190+ sort_keys = True ,
191+ )
192+
193+ def commit_reuse_result (self ):
194+ if self .reuse_cache_dir is None or not is_main_process ():
195+ return
196+
197+ cache_backup_dir = f"{ self .reuse_cache_dir } .old"
198+ shutil .rmtree (cache_backup_dir , ignore_errors = True )
199+ cache_backed_up = os .path .isdir (self .reuse_cache_dir )
200+ if cache_backed_up :
201+ os .replace (self .reuse_cache_dir , cache_backup_dir )
202+ try :
203+ os .replace (self .reuse_cache_stage_dir , self .reuse_cache_dir )
204+ if self .work_result_path is not None :
205+ os .replace (self .work_result_path , self .final_result_path )
206+ except Exception :
207+ shutil .rmtree (self .reuse_cache_dir , ignore_errors = True )
208+ if cache_backed_up :
209+ os .replace (cache_backup_dir , self .reuse_cache_dir )
210+ raise
211+ shutil .rmtree (cache_backup_dir , ignore_errors = True )
212+
213+ def discard_reuse_result (self ):
214+ if not is_main_process ():
215+ return
216+ if self .reuse_cache_stage_dir :
217+ shutil .rmtree (self .reuse_cache_stage_dir , ignore_errors = True )
218+ if self .work_result_path and os .path .exists (self .work_result_path ):
219+ os .remove (self .work_result_path )
220+
101221 def warmup (self ):
102222 if not self .config .get ("warmup" , False ):
103223 return
0 commit comments