Skip to content

Commit fb5eedc

Browse files
authored
Revert "Optimize CUDA export host memory usage" (pytorch#21698)
Reverts pytorch#21617
1 parent 730b77a commit fb5eedc

3 files changed

Lines changed: 3 additions & 233 deletions

File tree

‎backends/cuda/cuda_backend.py‎

Lines changed: 2 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66

77

88
import contextlib
9-
import copy
10-
import ctypes
11-
import functools
12-
import gc
139
import logging
1410
import os
1511
import shutil
@@ -29,7 +25,6 @@
2925
from executorch.backends.cuda.triton.replacement_pass import (
3026
ReplaceEdgeOpWithTritonOpPass,
3127
)
32-
from executorch.exir._serialize._cord import FileBackedData
3328
from executorch.exir._warnings import experimental
3429
from executorch.exir.backend.backend_details import BackendDetails
3530
from executorch.exir.backend.compile_spec_schema import CompileSpec
@@ -65,14 +60,6 @@ def _is_cpu_clone_active() -> bool:
6560
return getattr(_CPU_CLONE_GUARD, "active", False)
6661

6762

68-
def _trim_host_memory() -> None:
69-
gc.collect()
70-
try:
71-
ctypes.CDLL(None).malloc_trim(0)
72-
except AttributeError:
73-
pass
74-
75-
7663
def _full_zeros_preserving_strides(x: torch.Tensor, device) -> torch.Tensor:
7764
"""Allocate a zero-filled tensor matching ``x``'s size/stride/dtype on ``device``.
7865
@@ -95,29 +82,18 @@ def _is_emptied(x) -> bool:
9582
)
9683

9784

98-
def _tensor_properties_for_low_memory(tensor, original):
99-
if _is_emptied(tensor):
100-
return None
101-
return original(tensor)
102-
103-
10485
@contextlib.contextmanager
10586
def _compile_time_cpu_clones(target_device: torch.device):
10687
"""Force AOTI's mutated-buffer clones onto CPU while preserving the
10788
serialized constants' target device."""
108-
from torch._inductor import (
109-
codecache as _codecache,
110-
compile_fx as _cfx,
111-
graph as _graph,
112-
)
89+
from torch._inductor import compile_fx as _cfx, graph as _graph
11390
from torch._inductor.codegen.cpp_wrapper_cpu import CppWrapperCpu as _Cpp
11491
from torch._inductor.graph import GraphLowering as _GL
11592

11693
orig_clone = _cfx.clone_preserve_strides
11794
orig_codegen_device = _Cpp.codegen_device
11895
orig_get_const = _GL.get_original_value_of_constant
11996
orig_is_same = _graph.is_same_tensor
120-
orig_tensor_properties = _codecache.TensorProperties
12197

12298
def _is_same_skip_emptied(data, value):
12399
# KV buffers freed via resize_(0) all have data_ptr 0, so the stock
@@ -176,9 +152,6 @@ def _codegen_device_target_aware(self, device):
176152
_Cpp.codegen_device = _codegen_device_target_aware
177153
_GL.get_original_value_of_constant = _get_const_synthesize_zeros
178154
_graph.is_same_tensor = _is_same_skip_emptied
179-
_codecache.TensorProperties = functools.partial(
180-
_tensor_properties_for_low_memory, original=orig_tensor_properties
181-
)
182155
prev_active = getattr(_CPU_CLONE_GUARD, "active", False)
183156
_CPU_CLONE_GUARD.active = True
184157
try:
@@ -189,7 +162,6 @@ def _codegen_device_target_aware(self, device):
189162
_Cpp.codegen_device = orig_codegen_device
190163
_GL.get_original_value_of_constant = orig_get_const
191164
_graph.is_same_tensor = orig_is_same
192-
_codecache.TensorProperties = orig_tensor_properties
193165

194166

195167
def _is_kv_buffer(name, v) -> bool:
@@ -298,38 +270,6 @@ def _on_off_compile_spec_value(spec: CompileSpec) -> bool:
298270
return value == "ON"
299271

300272

301-
def _write_aoti_weights_blob(weights, blob_path: str) -> None:
302-
_trim_host_memory()
303-
tensors = [tensor for tensor, _ in weights.values()]
304-
all_cuda = all(tensor.is_cuda for tensor in tensors)
305-
chunk_size = 8 * 1024 * 1024
306-
307-
with open(blob_path, "wb") as output:
308-
for tensor in tensors:
309-
if tensor.is_mkldnn:
310-
raise RuntimeError("MKLDNN constants are not supported by CUDA AOTI")
311-
storage = tensor.untyped_storage()
312-
nbytes = storage.nbytes()
313-
if nbytes and tensor.is_cuda:
314-
byte_tensor = torch.empty(
315-
0, dtype=torch.uint8, device=tensor.device
316-
).set_(storage, 0, (nbytes,), (1,))
317-
for offset in range(0, nbytes, chunk_size):
318-
cpu_chunk = byte_tensor[offset : offset + chunk_size].cpu()
319-
output.write(memoryview(cpu_chunk.numpy()))
320-
del byte_tensor, cpu_chunk
321-
elif nbytes:
322-
raw_array = (ctypes.c_ubyte * nbytes).from_address(storage.data_ptr())
323-
raw_view = memoryview(raw_array).cast("B")
324-
for offset in range(0, nbytes, chunk_size):
325-
output.write(raw_view[offset : offset + chunk_size])
326-
del raw_view, raw_array
327-
if not all_cuda and (padding := (-nbytes) % 64):
328-
output.write(bytes(padding))
329-
del storage
330-
_trim_host_memory()
331-
332-
333273
@final
334274
@experimental(
335275
"This API and all of cuda backend related functionality are experimental."
@@ -444,56 +384,6 @@ def save_data_externally(cls) -> bool:
444384
"""
445385
return True
446386

447-
@classmethod
448-
def load_weights_blob(
449-
cls, blob_path: str, compile_specs: List[CompileSpec]
450-
) -> tuple[Any, str]:
451-
if not cls._is_low_memory_mode(compile_specs):
452-
return super().load_weights_blob(blob_path, compile_specs)
453-
blob_data = FileBackedData.move_from(blob_path)
454-
return blob_data, blob_data.sha256().hex()
455-
456-
@classmethod
457-
def materialize_weights_blob(
458-
cls, paths: Any, compile_specs: List[CompileSpec]
459-
) -> Any:
460-
if not cls._is_low_memory_mode(compile_specs) or not isinstance(paths, list):
461-
return paths
462-
463-
from torch.export.pt2_archive._package_weights import Weights
464-
465-
weights = [path for path in paths if isinstance(path, Weights)]
466-
if not weights:
467-
return paths
468-
if len(weights) != 1:
469-
raise RuntimeError(
470-
f"Expected one CUDA AOTI weights output, got {len(weights)}"
471-
)
472-
473-
so_path = next(
474-
path
475-
for path in paths
476-
if isinstance(path, str) and path.endswith(".wrapper.so")
477-
)
478-
blob_path = os.path.splitext(so_path)[0] + "_weights.blob"
479-
_write_aoti_weights_blob(weights[0], blob_path)
480-
return [path for path in paths if not isinstance(path, Weights)] + [blob_path]
481-
482-
@classmethod
483-
def copy_exported_program_for_preprocess(
484-
cls, edge_program, compile_specs: List[CompileSpec]
485-
):
486-
if not cls._is_low_memory_mode(compile_specs):
487-
return copy.deepcopy(edge_program)
488-
489-
tensor_memo = {
490-
id(tensor): tensor
491-
for values in (edge_program.state_dict, edge_program.constants)
492-
for tensor in values.values()
493-
if isinstance(tensor, torch.Tensor)
494-
}
495-
return copy.deepcopy(edge_program, tensor_memo)
496-
497387
@classmethod
498388
def get_supported_fallback_kernels(cls) -> Dict[str, Any]:
499389
return {
@@ -569,9 +459,7 @@ def get_aoti_compile_options(
569459
"aot_inductor.package": True,
570460
"aot_inductor.package_constants_in_so": False,
571461
# Store weight constants on disk in a binary blob
572-
"aot_inductor.package_constants_on_disk_format": cls._weights_format(
573-
compile_specs
574-
),
462+
"aot_inductor.package_constants_on_disk_format": "binary_blob",
575463
# Enable maximum automatic tuning for optimal performance
576464
"max_autotune": True,
577465
# Use TRITON for GEMM (General Matrix Multiply) operations tuning only to avoid using operators in libtorch
@@ -706,7 +594,6 @@ def _combined():
706594
stack.enter_context(
707595
_compile_time_cpu_clones(torch.device(cls.get_device_name()))
708596
)
709-
_trim_host_memory()
710597
yield
711598

712599
return _combined()
@@ -719,14 +606,6 @@ def _is_low_memory_mode(compile_specs: List[CompileSpec]) -> bool:
719606
return spec.value.decode("utf-8").upper() == "ON"
720607
return False
721608

722-
@classmethod
723-
def _weights_format(cls, compile_specs: List[CompileSpec]) -> str:
724-
return (
725-
"pickle_weights"
726-
if cls._is_low_memory_mode(compile_specs)
727-
else "binary_blob"
728-
)
729-
730609
@classmethod
731610
def move_program_to_device(
732611
cls,

‎backends/cuda/tests/test_cuda_export.py‎

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
import os
8-
import tempfile
97
import unittest
108
from typing import Tuple
119

@@ -132,23 +130,9 @@ def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
132130
inputs = (torch.randn(3, 4), torch.randn(3, 4))
133131

134132
# Test export
135-
edge_program_manager = self._export_to_cuda_with_lower(
136-
module,
137-
inputs,
138-
[
139-
CudaBackend.generate_method_name_compile_spec("forward"),
140-
CompileSpec("low_memory_mode", b"ON"),
141-
],
142-
)
133+
edge_program_manager = self._export_to_cuda_with_lower(module, inputs)
143134
self.assertIsNotNone(edge_program_manager, "Simple add operation export failed")
144135

145-
et_program = edge_program_manager.to_executorch()
146-
with tempfile.TemporaryDirectory() as output_dir:
147-
et_program.write_tensor_data_to_file(output_dir)
148-
ptd_path = os.path.join(output_dir, "aoti_cuda_blob.ptd")
149-
self.assertTrue(os.path.isfile(ptd_path))
150-
self.assertGreater(os.path.getsize(ptd_path), 0)
151-
152136
def test_conv2d(self):
153137
"""Test CUDA export for 2D convolution."""
154138

‎backends/cuda/tests/test_cuda_partitioner.py‎

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,112 +4,19 @@
44
# This source code is licensed under the BSD-style license found in the
55
# LICENSE file in the root directory of this source tree.
66

7-
import hashlib
87
import operator
9-
import os
10-
import tempfile
118
import unittest
129
from typing import Tuple
1310

1411
import torch
15-
from executorch.backends.cuda.cuda_backend import CudaBackend
1612
from executorch.backends.cuda.cuda_partitioner import CudaPartitioner
17-
from executorch.exir._serialize._cord import FileBackedData
18-
from executorch.exir.backend.compile_spec_schema import CompileSpec
1913
from executorch.exir.backend.partitioner import PartitionResult
2014
from executorch.exir.delegate import executorch_call_delegate
2115
from torch._export.utils import is_buffer, is_lifted_tensor_constant, is_param
2216
from torch.export import export
23-
from torch.export.pt2_archive._package_weights import TensorProperties, Weights
2417
from torch.fx.passes.utils.fuser_utils import validate_partition
2518

2619

27-
class TestCudaWeightsBlob(unittest.TestCase):
28-
def test_low_memory_blob_stays_file_backed(self) -> None:
29-
data = b"cuda weights"
30-
with tempfile.TemporaryDirectory() as directory:
31-
path = os.path.join(directory, "weights.blob")
32-
with open(path, "wb") as f:
33-
f.write(data)
34-
35-
blob, digest = CudaBackend.load_weights_blob(
36-
path, [CompileSpec("low_memory_mode", b"ON")]
37-
)
38-
39-
self.assertIsInstance(blob, FileBackedData)
40-
self.assertEqual(hashlib.sha256(data).hexdigest(), digest)
41-
self.assertEqual(data, blob.to_bytes())
42-
self.assertFalse(os.path.exists(path))
43-
44-
def test_default_blob_behavior_is_unchanged(self) -> None:
45-
data = b"cuda weights"
46-
with tempfile.TemporaryDirectory() as directory:
47-
path = os.path.join(directory, "weights.blob")
48-
with open(path, "wb") as f:
49-
f.write(data)
50-
51-
blob, digest = CudaBackend.load_weights_blob(path, [])
52-
53-
self.assertIsInstance(blob, bytes)
54-
self.assertEqual(data, blob)
55-
self.assertEqual(hashlib.sha256(data).hexdigest(), digest)
56-
self.assertFalse(os.path.exists(path))
57-
58-
def test_low_memory_weights_are_streamed_in_binary_blob_format(self) -> None:
59-
first = torch.tensor([1, 2, 3], dtype=torch.int16)
60-
second = torch.tensor([4, 5], dtype=torch.int32)
61-
weights = Weights(
62-
{
63-
"first": (first, TensorProperties(first)),
64-
"second": (second, TensorProperties(second)),
65-
}
66-
)
67-
68-
with tempfile.TemporaryDirectory() as directory:
69-
so_path = os.path.join(directory, "model.wrapper.so")
70-
paths = CudaBackend.materialize_weights_blob(
71-
[so_path, weights], [CompileSpec("low_memory_mode", b"ON")]
72-
)
73-
blob_path = os.path.join(directory, "model.wrapper_weights.blob")
74-
75-
self.assertEqual([so_path, blob_path], paths)
76-
with open(blob_path, "rb") as blob:
77-
data = blob.read()
78-
expected = (
79-
bytes(first.untyped_storage())
80-
+ bytes(58)
81-
+ bytes(second.untyped_storage())
82-
+ bytes(56)
83-
)
84-
self.assertEqual(expected, data)
85-
86-
def test_low_memory_program_copy_shares_tensor_storage(self) -> None:
87-
module = torch.nn.Linear(4, 3)
88-
program = export(module, (torch.randn(2, 4),), strict=True)
89-
90-
copied = CudaBackend.copy_exported_program_for_preprocess(
91-
program, [CompileSpec("low_memory_mode", b"ON")]
92-
)
93-
94-
self.assertIsNot(program, copied)
95-
self.assertIsNot(program.graph_module, copied.graph_module)
96-
self.assertIs(program.state_dict["weight"], copied.state_dict["weight"])
97-
copied._state_dict["weight"] = torch.nn.Parameter(torch.zeros(3, 4))
98-
self.assertFalse(torch.count_nonzero(program.state_dict["weight"]) == 0)
99-
100-
def test_default_program_copy_has_independent_tensor_storage(self) -> None:
101-
module = torch.nn.Linear(4, 3)
102-
program = export(module, (torch.randn(2, 4),), strict=True)
103-
104-
copied = CudaBackend.copy_exported_program_for_preprocess(program, [])
105-
106-
self.assertIsNot(program.state_dict["weight"], copied.state_dict["weight"])
107-
self.assertNotEqual(
108-
program.state_dict["weight"].data_ptr(),
109-
copied.state_dict["weight"].data_ptr(),
110-
)
111-
112-
11320
class TestCudaPartitioner(unittest.TestCase):
11421
"""
11522
Test CUDA partitioner functionality.

0 commit comments

Comments
 (0)