99
1010import logging
1111import time
12- from typing import Any , Callable , Dict , List , Optional , Sequence , Tuple , Union
12+ from typing import Any , Callable , Dict , List , Optional , Sequence , Set , Tuple , Union
1313
1414import torch
1515from executorch .exir import EdgeProgramManager
@@ -235,6 +235,7 @@ def __init__(
235235 }
236236
237237 self ._stage_to_artifacts : Dict [StageType , PipelineArtifact ] = {}
238+ self ._released_stages : Set [StageType ] = set ()
238239
239240 def _detect_model_type (
240241 self , model : Union [nn .Module , GraphModule , ExportedProgram , Dict ]
@@ -452,8 +453,11 @@ def _run_pipeline(self) -> None:
452453 # After validation: a rejected pipeline must not destroy the previous
453454 # run. In place, since get_stage_artifacts() hands out this dict.
454455 self ._stage_to_artifacts .clear ()
456+ self ._released_stages .clear ()
455457
456458 current_artifact = PipelineArtifact (data = self ._model , context = self ._run_context )
459+ release = self ._export_recipe .release_intermediate_artifacts
460+ previous_stage_type = None
457461
458462 # Execute stages from registry in the order specified by pipeline_stages
459463 for stage_type in self ._pipeline_stages :
@@ -473,6 +477,30 @@ def _run_pipeline(self) -> None:
473477
474478 self ._stage_to_artifacts [stage_type ] = current_artifact
475479
480+ # The stage that just ran has consumed its input, so the previous
481+ # stage's output is now unreachable except through the two
482+ # references we hold ourselves.
483+ if release and previous_stage_type is not None :
484+ self ._release_stage (previous_stage_type )
485+ previous_stage_type = stage_type
486+
487+ def _release_stage (self , stage_type : StageType ) -> None :
488+ self ._stage_to_artifacts .pop (stage_type , None )
489+ stage = self ._stage_registry .get (stage_type )
490+ if stage is not None :
491+ stage .release_artifact ()
492+ self ._released_stages .add (stage_type )
493+ logging .info (f"Released artifact for stage: { stage_type } " )
494+
495+ def _raise_if_released (self , * stage_types : StageType ) -> None :
496+ released = [s for s in stage_types if s in self ._released_stages ]
497+ if released :
498+ names = ", " .join (str (s ) for s in released )
499+ raise RuntimeError (
500+ f"Artifact for { names } was released to free memory. Disable "
501+ "release_intermediate_artifacts on the recipe to retain it."
502+ )
503+
476504 def export (self ) -> None :
477505 """
478506 Execute the full export process.
@@ -503,6 +531,7 @@ def get_exported_program(self, method_name: str = "forward") -> ExportedProgram:
503531 RuntimeError: If torch export stage has not been run
504532 KeyError: If the method name is not found in exported programs
505533 """
534+ self ._raise_if_released (StageType .TORCH_EXPORT )
506535 artifact = self ._stage_to_artifacts .get (StageType .TORCH_EXPORT )
507536 if artifact is None or artifact .data is None :
508537 raise RuntimeError (
@@ -535,17 +564,19 @@ def get_edge_program_manager(self) -> "EdgeProgramManager":
535564 RuntimeError: If no edge stage has been run
536565 """
537566 # Check stages in order of preference
538- for stage_type in [
567+ edge_stages = [
539568 StageType .TO_EDGE_TRANSFORM_AND_LOWER ,
540569 StageType .TO_BACKEND ,
541570 StageType .EDGE_PROGRAM_MANAGER_TRANSFORM ,
542571 StageType .TO_EDGE ,
543- ]:
572+ ]
573+ for stage_type in edge_stages :
544574 artifact = self ._stage_to_artifacts .get (stage_type )
545575 if artifact is not None and artifact .data is not None :
546576 logging .info (f"Returning edge program manager from stage { stage_type } " )
547577 return artifact .data
548578
579+ self ._raise_if_released (* edge_stages )
549580 raise RuntimeError (
550581 "Edge program manager is not available. "
551582 "Run one of the edge stages first: TO_EDGE_TRANSFORM_AND_LOWER, TO_EDGE, EDGE_PROGRAM_MANAGER_TRANSFORM, or TO_BACKEND."
@@ -681,13 +712,16 @@ def print_delegation_info(self) -> None:
681712 & {StageType .TO_EDGE_TRANSFORM_AND_LOWER , StageType .TO_BACKEND }
682713 )
683714 if not lowering_stage :
684- RuntimeError (
715+ raise RuntimeError (
685716 "No delegation info available, atleast one of the lowering stages should be present"
686717 )
687718
688719 stage_artifact = self ._stage_to_artifacts .get (lowering_stage [0 ])
689720 if stage_artifact is None :
690- RuntimeError ("No delegation info available, run the lowering stage first" )
721+ self ._raise_if_released (lowering_stage [0 ])
722+ raise RuntimeError (
723+ "No delegation info available, run the lowering stage first"
724+ )
691725
692726 delegation_info_by_method = stage_artifact .get_context (
693727 "delegation_info_by_method" , None
0 commit comments