@@ -58,6 +58,12 @@ def set_needs_backward(self, frame_id: int, needs_backward: bool):
5858 if frame_id in self .frames :
5959 self .frames [frame_id ] = (self .frames [frame_id ][0 ], needs_backward )
6060
61+ def remove_graph (self , graph_id : int , frame_id : int ) -> bool :
62+ if frame_id in self .frames and self .frames [frame_id ][0 ] == graph_id :
63+ del self .frames [frame_id ]
64+ return True
65+ return False
66+
6167 def get_graph_order (self ) -> List [Tuple [int , bool ]]:
6268 assert all (isinstance (needs_backward , bool ) for _ , needs_backward in self .frames .values ())
6369 return list (self .frames .values ())
@@ -77,6 +83,66 @@ def clear(self):
7783fwd_real_inputs = []
7884
7985
86+ def cleanup_compiled_backward_state (frame_id = None , owned_frames = None ):
87+ """Release process-global compiled-backward state after completion or failure."""
88+ if frame_id is None :
89+ if owned_frames is None :
90+ frames_needing_bwd .clear ()
91+ else :
92+ frames_needing_bwd .difference_update (owned_frames )
93+ owned_frames .clear ()
94+ else :
95+ frames_needing_bwd .discard (frame_id )
96+ if owned_frames is not None :
97+ owned_frames .discard (frame_id )
98+ if len (frames_needing_bwd ) == 0 :
99+ unpatch_compiled_func ()
100+
101+
102+ def cleanup_backend_state (frame_id , graph_id , owned_frames = None ):
103+ """Release all process-global state owned by a failed graph attempt."""
104+ if graph_order_with_frame_id .remove_graph (graph_id , frame_id ):
105+ frames_partitioned .discard (frame_id )
106+ profiling_results .pop (graph_id , None )
107+ param_manager .pop (graph_id , None )
108+ opt_pass_times [:] = [timing for timing in opt_pass_times if timing [2 ] != graph_id ]
109+ cleanup_compiled_backward_state (frame_id , owned_frames )
110+
111+
112+ def _cleanup_compiled_backward_state_on_error (frame_id , graph_id , owned_frames = None ):
113+
114+ def decorator (fn ):
115+
116+ def wrapped (* args , ** kwargs ):
117+ try :
118+ return fn (* args , ** kwargs )
119+ except Exception :
120+ cleanup_backend_state (frame_id , graph_id , owned_frames )
121+ raise
122+
123+ return wrapped
124+
125+ return decorator
126+
127+
128+ def _cleanup_compiled_backward_backend_state_on_error (owned_frames = None ):
129+
130+ def decorator (fn ):
131+
132+ def wrapped (gm , * args , ** kwargs ):
133+ frame_id = gm .meta ["dynamo_compile_id" ].frame_id
134+ graph_id = id (gm .graph )
135+ try :
136+ return fn (gm , * args , ** kwargs )
137+ except Exception :
138+ cleanup_backend_state (frame_id , graph_id , owned_frames )
139+ raise
140+
141+ return wrapped
142+
143+ return decorator
144+
145+
80146def register_compile_pass (name : str , opt_pass_fn , contract = None ):
81147 from .passes .contract import register_pass_contract
82148 opt_passes [name ] = opt_pass_fn
@@ -97,7 +163,8 @@ def init_schedule(schedule):
97163 remaining_schedule = deque (schedule )
98164
99165
100- def launch_compile_passes (global_steps : int ):
166+ def launch_compile_passes (global_steps : int , owned_frames = None ):
167+ """Advance the pass schedule and discard state owned by the previous compile cycle."""
101168 global next_pass_step , next_passes
102169
103170 if len (remaining_schedule ) > 0 and global_steps == remaining_schedule [0 ][0 ]:
@@ -109,6 +176,8 @@ def launch_compile_passes(global_steps: int):
109176 graph_order_with_frame_id .clear ()
110177 profiling_results .clear ()
111178 param_manager .clear ()
179+ fwd_real_inputs .clear ()
180+ cleanup_compiled_backward_state (owned_frames = owned_frames )
112181 frames_partitioned .clear ()
113182
114183
@@ -201,6 +270,25 @@ def set_example_values_to_symints(real_inputs, param_indices=None):
201270 return tuple (real_inputs_ret )
202271
203272
273+ def _get_fw_real_inputs (local_real_inputs , input_storage : InputStorage , graph_id : int , debug_log : bool = False ):
274+ """Resolve real inputs from the one-shot queue, persistent storage, then legacy state."""
275+ if local_real_inputs :
276+ return local_real_inputs .popleft ()
277+
278+ if input_storage .has_data ():
279+ if debug_log :
280+ log_rank0 (f"Retrieving real inputs from storage for graph_id={ graph_id } " , enable = True )
281+ return input_storage .get ()
282+
283+ if fwd_real_inputs :
284+ return fwd_real_inputs .pop (0 )
285+
286+ raise RuntimeError (f"No real inputs available for graph_id { graph_id } . "
287+ f"Local queue size: { len (local_real_inputs )} , "
288+ f"global queue size: { len (fwd_real_inputs )} , "
289+ f"storage has data: { input_storage .has_data ()} " )
290+
291+
204292def run_opt_passes (opt_passes : List [Callable ],
205293 gm : GraphModule ,
206294 graph_id : int ,
@@ -243,14 +331,18 @@ def run_opt_passes(opt_passes: List[Callable],
243331 get_accelerator ().empty_cache ()
244332
245333
246- def make_backend (backend , compile_config , compile_kwargs = {}):
334+ def make_backend (backend , compile_config , compile_kwargs = {}, owned_frames = None ):
247335
248336 register_custom_ops ()
249337
250338 # Extract values from compile_config
251339 debug_log = compile_config .debug_log
252340 free_activation = compile_config .free_activation and not is_backend_inductor (backend )
253341
342+ if owned_frames is None :
343+ owned_frames = set ()
344+
345+ @_cleanup_compiled_backward_backend_state_on_error (owned_frames )
254346 def backend_fn (gm : GraphModule , real_inputs ):
255347 graph_id = id (gm .graph )
256348
@@ -275,24 +367,23 @@ def backend_fn(gm: GraphModule, real_inputs):
275367 param_indices = [(i , input_val .param_id , input_val .shape ) for i , input_val in enumerate (real_inputs )
276368 if isinstance (input_val , torch .nn .Parameter )]
277369
278- global fwd_real_inputs
279-
280370 # Create an InputStorage instance for this specific graph
281371 # It will be captured by the make_fw_graph closure, eliminating the need for graph ID management
282372 input_storage = InputStorage (keep_int_input_tensors = compile_config .keep_int_input_tensors ,
283373 keep_all_input_tensors = compile_config .keep_all_input_tensors )
284374
285- # Store in both list (for backward compatibility) and storage (for persistence)
375+ # Store in a closure-local queue and storage (for persistence).
286376 # The input_storage keeps tensor metadata to handle cases where
287377 # backend_fn is called once but make_fw_graph is called multiple times
288- fwd_real_inputs . append ( real_inputs )
378+ local_fwd_real_inputs = deque ([ real_inputs ] )
289379 input_storage .put (real_inputs )
290380
291381 global profiling_results
292382 if graph_id not in profiling_results :
293383 profiling_results [graph_id ] = ProfilingResult ()
294384 profiling_results [graph_id ].param_indices = param_indices
295385
386+ @_cleanup_compiled_backward_state_on_error (frame_id , graph_id , owned_frames )
296387 def make_fw_graph (gm , sample_inputs ):
297388 time_start = time .time ()
298389 graph_index = len (graph_order_with_frame_id ) - 1
@@ -305,19 +396,9 @@ def make_fw_graph(gm, sample_inputs):
305396 if len (frames_needing_bwd ) == 0 :
306397 patch_compiled_func ()
307398 frames_needing_bwd .add (frame_id )
399+ owned_frames .add (frame_id )
308400
309- # Try to get real_inputs from the list first, then from storage
310- if fwd_real_inputs :
311- real_inputs = fwd_real_inputs .pop (0 )
312- elif input_storage .has_data ():
313- # Note: input_storage is captured from the enclosing backend_fn scope
314- # Materialize tensors from storage when list is empty
315- log_rank0 (f"Retrieving real inputs from storage for graph_id={ graph_id } " , enable = debug_log )
316- real_inputs = input_storage .get ()
317- else :
318- raise RuntimeError (f"No real inputs available for graph_id { graph_id } . "
319- f"List size: { len (fwd_real_inputs )} , Storage has data: { input_storage .has_data ()} " )
320-
401+ real_inputs = _get_fw_real_inputs (local_fwd_real_inputs , input_storage , graph_id , debug_log = debug_log )
321402 real_inputs = set_example_values_to_symints (real_inputs )
322403
323404 param_manager [graph_id ] = DSGraphParamManager (gm .graph , real_inputs , param_indices )
@@ -342,6 +423,7 @@ def make_fw_graph(gm, sample_inputs):
342423
343424 return gm .graph
344425
426+ @_cleanup_compiled_backward_state_on_error (frame_id , graph_id , owned_frames )
345427 def make_bw_graph (gm , sample_inputs ):
346428 time_start = time .time ()
347429
@@ -385,9 +467,7 @@ def make_bw_graph(gm, sample_inputs):
385467 add_free_activations (graph_id , gm .graph ,
386468 get_activation_node_names (gm .graph , param_nodes_bw , non_param_input_names ))
387469
388- frames_needing_bwd .remove (frame_id )
389- if len (frames_needing_bwd ) == 0 :
390- unpatch_compiled_func ()
470+ cleanup_compiled_backward_state (frame_id , owned_frames )
391471
392472 log_rank0 (
393473 f"Bwd end { graph_index } graph_id={ graph_id } alloc_mem={ get_accelerator ().memory_allocated ()} graph={ gm .graph } " ,
@@ -415,10 +495,15 @@ def compiler_fn(gm, sample_inputs):
415495 partition_fn = partition_fn )
416496 return torch ._dynamo .optimize (** compile_kwargs )(aot_mod )
417497 elif backend == "inductor" :
418- patch_create_aot_dispatcher_function (graph_id , z3_partition , make_fw_graph , make_bw_graph , real_inputs ,
419- param_indices , param_manager , frame_id , frames_partitioned )
420-
421- return torch ._inductor .compile (gm , real_inputs )
498+ restore_aotautograd = patch_create_aot_dispatcher_function (graph_id , z3_partition , make_fw_graph ,
499+ make_bw_graph , real_inputs , param_indices ,
500+ param_manager , frame_id , frames_partitioned )
501+ try :
502+ return torch ._inductor .compile (gm , real_inputs )
503+ finally :
504+ # AotAutograd.__init__ is process-global; never leak this
505+ # graph-specific compiler wiring into a later compilation.
506+ restore_aotautograd ()
422507
423508 raise ValueError (f"Unsupported backend { backend } " )
424509
0 commit comments