Skip to content

Commit 5b3da18

Browse files
authored
Avoid deep-copying the model during source transform (pytorch#21581)
Differential Revision: D114773308 Pull Request resolved: pytorch#21581
1 parent 0bbb6f3 commit 5b3da18

4 files changed

Lines changed: 38 additions & 4 deletions

File tree

‎export/export.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,10 @@ def _build_stages(self, stages: List[StageType]) -> Dict[StageType, Stage]:
300300
stage = None
301301
for stage_type in stages or self._get_default_pipeline():
302302
if stage_type == StageType.SOURCE_TRANSFORM:
303-
stage = SourceTransformStage(self._quant_recipe)
303+
stage = SourceTransformStage(
304+
self._quant_recipe,
305+
in_place=self._export_recipe.source_transform_in_place,
306+
)
304307
elif stage_type == StageType.QUANTIZE:
305308
stage = QuantizeStage(self._quant_recipe)
306309
elif stage_type == StageType.TORCH_EXPORT:

‎export/recipe.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ class ExportRecipe:
154154
quantization_recipe: Optional quantization recipe for model quantization
155155
aten_transform_passes: Optional list of functions to apply transformation passes to the program before edge lowering.
156156
These callables are invoked to modify and return the transformed program.
157+
source_transform_in_place: Skip the defensive deepcopy in the SOURCE_TRANSFORM
158+
stage and mutate the caller's model. Necessary for models
159+
large enough that a second copy will not fit in memory.
157160
lowering_recipe: Optional lowering recipe for model lowering and partitioning
158161
executorch_backend_config: Optional backend configuration for ExecuTorch
159162
pipeline_stages: Optional list of stages to execute, defaults to a standard pipeline.
@@ -166,6 +169,7 @@ class ExportRecipe:
166169
aten_transform_passes: Optional[
167170
List[Callable[[str, ExportedProgram], ExportedProgram]]
168171
] = None
172+
source_transform_in_place: bool = False
169173
lowering_recipe: Optional[LoweringRecipe] = None
170174
# pyre-ignore[11]: Type not defined
171175
executorch_backend_config: Optional[ExecutorchBackendConfig] = None

‎export/stages.py‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,13 @@ class SourceTransformStage(Stage):
315315
Optional stage: Source transform stage: Apply source transformations to the model.
316316
"""
317317

318-
def __init__(self, quantization_recipe: Optional[QuantizationRecipe]) -> None:
318+
def __init__(
319+
self,
320+
quantization_recipe: Optional[QuantizationRecipe],
321+
in_place: bool = False,
322+
) -> None:
319323
self._quantization_recipe = quantization_recipe
324+
self._in_place = in_place
320325
self._transformed_models: Dict[str, nn.Module] = {}
321326

322327
@property
@@ -347,8 +352,11 @@ def run(self, artifact: PipelineArtifact) -> None:
347352

348353
assert isinstance(artifact.data, dict)
349354

350-
# Store the original models
351-
self._transformed_models = copy.deepcopy(artifact.data)
355+
# A second copy of the model is not affordable for every caller, so
356+
# large models can opt out and have their own model mutated instead.
357+
self._transformed_models = (
358+
artifact.data if self._in_place else copy.deepcopy(artifact.data)
359+
)
352360

353361
# Apply torchao quantize_ to each model
354362
for _, model in self._transformed_models.items():

‎export/tests/test_export_stages.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,25 @@ def test_run_with_ao_quantization_configs(
321321
# verify the result model is NOT the same object as the original
322322
self.assertIsNot(result_artifact.data["forward"], self.model)
323323

324+
@patch("executorch.export.stages.quantize_")
325+
@patch("executorch.export.stages.unwrap_tensor_subclass")
326+
def test_run_in_place_does_not_copy_the_model(
327+
self, mock_unwrap: Mock, mock_quantize: Mock
328+
) -> None:
329+
from torchao.core.config import AOBaseConfig
330+
331+
mock_recipe = Mock(spec=QuantizationRecipe)
332+
mock_recipe.ao_quantization_configs = [
333+
AOQuantizationConfig(ao_base_config=Mock(spec=AOBaseConfig))
334+
]
335+
336+
stage = SourceTransformStage(mock_recipe, in_place=True)
337+
stage.run(PipelineArtifact(data=self.models_dict, context={}))
338+
339+
# The caller's own model is quantized rather than a copy of it.
340+
self.assertIs(mock_quantize.call_args[0][0], self.model)
341+
self.assertIs(stage.get_artifacts().data["forward"], self.model)
342+
324343

325344
class TestQuantizeStage(unittest.TestCase):
326345
def setUp(self) -> None:

0 commit comments

Comments
 (0)