Skip to content

Commit e2bcb9d

Browse files
authored
Serialize a program without copying its delegate blobs (pytorch#24458)
### What is wrong today `to_executorch()` serializes the program right away. Before it moves each delegate blob out of the program and into a segment, `serialize_pte_binary` deep-copies the program, so that the caller's program does not change. `copy.deepcopy` shares an object whose type is exactly `bytes`. But it copies an object of any other bytes type, for example a subclass of `bytes`. The Core ML backend returns such a type for models over 1 GiB: it maps the model files into memory instead of reading them. So for these models, `to_executorch()` briefly held 2 more copies of the delegate blob, and the serialized program kept 1 of them for as long as the `ExecutorchProgramManager` lived. With a 4 GiB delegate blob of a `bytes` subclass, peak memory in `to_executorch()` grew by 8 GiB, and 4 GiB stayed in use after it returned. ### What this change does It seeds the `copy.deepcopy` memo with the delegate blobs, so the copied program shares them instead of copying them. The blobs are immutable, so sharing them is safe. The rest of the program is still copied, and the output does not change. ### What was tested - New test `test_serialize_shares_delegate_blobs` serializes a program whose delegate blob is a `bytes` subclass, and checks that the output holds that same object. It fails without this change. - The `exir/_serialize` tests pass, and the other `exir` tests give the same results with and without this change. - A test backend whose `preprocess` returns a 4 GiB blob, on Linux x86_64. With a `bytes` subclass, the extra peak memory in `to_executorch()` went from 8 GiB to none, and the `.pte` file is byte for byte the same as before. With plain `bytes`, nothing changed.
1 parent 59541e1 commit e2bcb9d

2 files changed

Lines changed: 29 additions & 4 deletions

File tree

‎exir/_serialize/_program.py‎

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -413,10 +413,17 @@ def serialize_pte_binary(
413413
if constant_tensor_alignment is None:
414414
constant_tensor_alignment = ALIGNMENT
415415

416-
# Don't modify the original program.
417-
# TODO(T144120904): Could avoid yet more huge copies with a more shallow
418-
# copy, reusing the actual data blobs.
419-
program = copy.deepcopy(pte_file.program)
416+
# Don't modify the original program, but share its delegate blobs rather
417+
# than copy them: deepcopy shares only exact bytes, and Core ML returns
418+
# another bytes type for models over 1 GiB. The memo lives only for this
419+
# call, because deepcopy fills it with every object it copies.
420+
program = copy.deepcopy(
421+
pte_file.program,
422+
memo={
423+
id(entry.data): entry.data
424+
for entry in pte_file.program.backend_delegate_data
425+
},
426+
)
420427

421428
# Store extracted segment data, with any buffer-specific alignment.
422429
# This may be constant data, delegate data or named data.

‎exir/_serialize/test/test_program.py‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,24 @@ def test_deserialize_restores_segments_from_a_view_of_the_input(self) -> None:
777777
self.assertIsInstance(restored, bytes)
778778
self.assertEqual(restored, blob)
779779

780+
def test_serialize_shares_delegate_blobs(self) -> None:
781+
# Not exactly bytes, like the blob Core ML returns for models over
782+
# 1 GiB, which deepcopy would copy.
783+
class Blob(bytes):
784+
pass
785+
786+
program = get_test_program()
787+
blob = Blob(self.gen_blob_data(SEGMENT_ALIGNMENT, b"\x10\x11\x01"))
788+
add_delegate_data(program, program.execution_plan[0], [blob])
789+
790+
pte_data = serialize_pte_binary(
791+
PTEFile(program=program),
792+
extract_delegate_segments=True,
793+
segment_alignment=SEGMENT_ALIGNMENT,
794+
)
795+
796+
self.assertTrue(any(buffer is blob for buffer in pte_data._buffers))
797+
780798
def test_no_constants(self) -> None:
781799
program = get_test_program()
782800
# Insert placeholder for non-const tensors.

0 commit comments

Comments
 (0)