Skip to content

Commit 0da9ca3

Browse files
authored
Implement direct serialization of .bpte:s (#20217)
This patch is heavily inspired by #17333 Similarly to the old serialization of pte:s, bundled program serialization went through a program -> string json -> flatbuffer path, which is heavily inefficient. As in the PR above, generate python flatbuffer bindings from the fbs schema and use them to cut out the json step. Checked in flatbuffer bindings and fbs schema coherence is enforced by the validate_flatbuffer_gen job. e6586533af Testing: Tested by devtools/bundled_program/test/test_end2end.py, Additionally, did the following benchmark locally: /usr/bin/time -v -- aot_arm_compiler.py --delegate --bundleio -t vgf [args] Time metric: Elapsed (wall clock) time Memory metric: Maximum resident set size ``` Model Baseline (s / MiB) With-patch (s / MiB) Speed-up Relative memory same.bpte bpte size (MiB) --------- ------------------- --------------------- -------- --------------- -------- --------- mv2 31.36 / 1349.7 22.37 / 845.5 1.40x 0.63x (-37.4%) yes 13.91 resnet50 128.57 / 10380.9 28.24 / 2003.4 4.55x 0.19x (-80.7%) yes 97.99 w2l 730.23 / 12102.9 15.85 / 2376.2 46.07x 0.20x (-80.4%) yes 131.45 deit_tiny 87.57 / 2669.5 70.23 / 1037.6 1.25x 0.39x (-61.1%) yes 22.88 ``` cc @Gasoonjia @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Erik Lundell <erik.lundell@arm.com>
1 parent b2d84b7 commit 0da9ca3

21 files changed

Lines changed: 1713 additions & 7 deletions

File tree

.flake8

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ exclude =
7575
./configurations,
7676
./docs,
7777
./exir/_serialize/generated/executorch_flatbuffer,
78+
./devtools/bundled_program/serialize/generated,
7879
./third_party,
7980
*.pyi
8081

.github/workflows/validate_flatbuffer_gen.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ on:
55
pull_request:
66
paths:
77
- "schema/**"
8-
- "exir/_serialize/generated/executorch_flatbuffer/**"
8+
- "devtools/bundled_program/schema/**"
9+
- "exir/_serialize/generated/**"
10+
- "devtools/bundled_program/serialize/generated/**"
911

1012
jobs:
1113
exir-flatbuffer:
@@ -33,3 +35,15 @@ jobs:
3335
echo "Please run 'python exir/_serialize/generate_program.py' to regenerate the files and commit the changes."
3436
exit 1
3537
fi
38+
39+
- name: Generate bundled program flatbuffer Python
40+
run: python devtools/bundled_program/serialize/generate_bundled_program.py
41+
42+
- name: Validate bundled_program_flatbuffer is unchanged
43+
run: |
44+
git add -A devtools/bundled_program/serialize/generated
45+
if ! git diff --cached --quiet -- devtools/bundled_program/serialize/generated; then
46+
echo "Error: bundled_program_flatbuffer has uncommitted changes."
47+
echo "Please run 'python devtools/bundled_program/serialize/generate_bundled_program.py' to regenerate the files and commit the changes."
48+
exit 1
49+
fi

.lintrunner.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ exclude_patterns = [
99
'.github/scripts/**',
1010
'exir/serde/**',
1111
'exir/_serialize/generated/executorch_flatbuffer/**',
12+
'devtools/bundled_program/serialize/generated/**',
1213
]
1314
command = [
1415
'python',
@@ -41,6 +42,7 @@ exclude_patterns = [
4142
'**/third-party/**',
4243
'exir/serde/**',
4344
'exir/_serialize/generated/executorch_flatbuffer/**',
45+
'devtools/bundled_program/serialize/generated/**',
4446
]
4547
command = [
4648
'python',

backends/arm/scripts/pre-push

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ for COMMIT in ${COMMITS}; do
177177
for committed_file in "${license_files[@]}"; do
178178
# Skip files with certain extensions
179179
case "$committed_file" in
180-
*.md|*.md.in|*.json|*.yml|*.yaml|*.cmake|*.patch|.gitignore|*.bzl|BUCK|*/BUCK|TARGETS|*/TARGETS)
180+
*.md|*.md.in|*.json|*.yml|*.yaml|*.cmake|*.patch|.gitignore|*.bzl|BUCK|*/BUCK|TARGETS|*/TARGETS|*/generated/*)
181181
echo -e "${INFO} Skipping license check for ${committed_file} (excluded extension)"
182182
continue
183183
;;

devtools/bundled_program/schema/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@ and other useful info together for verifying the correctness of ExecuTorch progr
44

55
## Rules to ensure forward/backward compatibility
66
Please check the rules in [here](../../../schema/README.md) for more info.
7+
8+
9+
## Regenerating generated code
10+
11+
Schema changes require regenerating the Python bindings in
12+
`devtools/bundled_program/serialize/generated` and committing the updated files. From the repo root:
13+
14+
```sh
15+
python devtools/bundled_program/serialize/generate_bundled_program.py
16+
```

devtools/bundled_program/serialize/BUCK

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fbcode_target(_kind = runtime.python_library,
99
name = "lib",
1010
srcs = [
1111
"__init__.py",
12-
],
12+
] + glob(["generated/**/*.py"]),
1313
resources = {
1414
"//executorch/devtools/bundled_program/schema:bundled_program_schema.fbs": "bundled_program_schema.fbs",
1515
"//executorch/devtools/bundled_program/schema:scalar_type.fbs": "scalar_type.fbs",
@@ -19,6 +19,7 @@ fbcode_target(_kind = runtime.python_library,
1919
# Please ask before changing this.
2020
visibility = ["PUBLIC"],
2121
deps = [
22+
"fbsource//third-party/pypi/flatbuffers:flatbuffers",
2223
"fbsource//third-party/pypi/setuptools:setuptools",
2324
"//executorch/devtools/bundled_program/schema:bundled_program_schema_py",
2425
"//executorch/exir/_serialize:lib",

devtools/bundled_program/serialize/__init__.py

Lines changed: 180 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Copyright (c) Meta Platforms, Inc. and affiliates.
22
# All rights reserved.
3-
# Copyright 2025 Arm Limited and/or its affiliates.
3+
# Copyright 2025-2026 Arm Limited and/or its affiliates.
44
#
55
# This source code is licensed under the BSD-style license found in the
66
# LICENSE file in the root directory of this source tree.
@@ -9,23 +9,62 @@
99

1010
# TODO(T138924864): Refactor to unify the serialization for bundled program and executorch program.
1111

12+
import functools
1213
import importlib.resources as _resources
1314
import json
1415
import os
16+
import re
1517
import tempfile
18+
from typing import Any
1619

1720
import executorch.devtools.bundled_program.schema as bp_schema
1821

1922
import executorch.devtools.bundled_program.serialize as serialization_package
23+
24+
import flatbuffers # pyre-ignore[21]
2025
from executorch.devtools.bundled_program.core import BundledProgram
26+
from executorch.devtools.bundled_program.serialize.generated.bundled_program_flatbuffer import (
27+
Bool as _Bool,
28+
BundledMethodTestCase as _BundledMethodTestCase,
29+
BundledMethodTestSuite as _BundledMethodTestSuite,
30+
BundledProgram as _BundledProgram,
31+
Double as _Double,
32+
Int as _Int,
33+
Tensor as _Tensor,
34+
Value as _Value,
35+
ValueUnion as _ValueUnion,
36+
)
2137
from executorch.exir._serialize._dataclass import _DataclassEncoder, _json_to_dataclass
2238
from executorch.exir._serialize._flatbuffer import _flatc_compile, _flatc_decompile
39+
from executorch.exir._serialize._flatbuffer_program import (
40+
_coerce_bytes,
41+
_create_aligned_byte_vector,
42+
)
2343

2444
# The prefix of schema files used for bundled program
2545
BUNDLED_PROGRAM_SCHEMA_NAME = "bundled_program_schema"
2646
SCALAR_TYPE_SCHEMA_NAME = "scalar_type"
2747

2848

49+
@functools.lru_cache(maxsize=1)
50+
def _bundled_program_file_identifier() -> bytes:
51+
schema = _resources.read_binary(
52+
serialization_package, f"{BUNDLED_PROGRAM_SCHEMA_NAME}.fbs"
53+
)
54+
match = re.search(rb'file_identifier\s+"([^"]+)"', schema)
55+
if match is None:
56+
raise ValueError(
57+
f"Missing file_identifier in {BUNDLED_PROGRAM_SCHEMA_NAME}.fbs"
58+
)
59+
file_identifier = match.group(1)
60+
if len(file_identifier) != 4:
61+
raise ValueError(
62+
f"Invalid file_identifier length {len(file_identifier)} "
63+
f"in {BUNDLED_PROGRAM_SCHEMA_NAME}.fbs"
64+
)
65+
return file_identifier
66+
67+
2968
def write_schema(d: str, schema_name: str) -> None:
3069
schema_path = os.path.join(d, "{}.fbs".format(schema_name))
3170
with open(schema_path, "wb") as schema_file:
@@ -78,6 +117,145 @@ def convert_from_flatbuffer(program_flatbuffer: bytes) -> bytes:
78117
return output_file.read()
79118

80119

120+
def _pack_tensor(self: Any, builder: Any) -> int:
121+
if self.sizes is not None:
122+
_Tensor.TensorStartSizesVector(builder, len(self.sizes))
123+
for i in reversed(range(len(self.sizes))):
124+
builder.PrependInt32(self.sizes[i])
125+
sizes = builder.EndVector()
126+
if self.data is not None:
127+
data = _create_aligned_byte_vector(builder, _coerce_bytes(self.data), 16)
128+
if self.dimOrder is not None:
129+
dim_order = _create_aligned_byte_vector(
130+
builder, _coerce_bytes(self.dimOrder), 1
131+
)
132+
133+
_Tensor.TensorStart(builder)
134+
_Tensor.TensorAddScalarType(builder, self.scalarType)
135+
if self.sizes is not None:
136+
_Tensor.TensorAddSizes(builder, sizes)
137+
if self.data is not None:
138+
_Tensor.TensorAddData(builder, data)
139+
if self.dimOrder is not None:
140+
_Tensor.TensorAddDimOrder(builder, dim_order)
141+
return _Tensor.TensorEnd(builder)
142+
143+
144+
def _pack_bundled_program(self: Any, builder: Any) -> int:
145+
if self.methodTestSuites is not None:
146+
method_test_suites_list = [
147+
method_test_suite.Pack(builder)
148+
for method_test_suite in self.methodTestSuites
149+
]
150+
_BundledProgram.BundledProgramStartMethodTestSuitesVector(
151+
builder, len(self.methodTestSuites)
152+
)
153+
for i in reversed(range(len(self.methodTestSuites))):
154+
builder.PrependUOffsetTRelative(method_test_suites_list[i])
155+
method_test_suites = builder.EndVector()
156+
if self.program is not None:
157+
program = _create_aligned_byte_vector(builder, _coerce_bytes(self.program), 32)
158+
159+
_BundledProgram.BundledProgramStart(builder)
160+
_BundledProgram.BundledProgramAddVersion(builder, self.version)
161+
if self.methodTestSuites is not None:
162+
_BundledProgram.BundledProgramAddMethodTestSuites(builder, method_test_suites)
163+
if self.program is not None:
164+
_BundledProgram.BundledProgramAddProgram(builder, program)
165+
return _BundledProgram.BundledProgramEnd(builder)
166+
167+
168+
@functools.lru_cache(maxsize=1)
169+
def _install_fast_packers() -> None:
170+
_Tensor.TensorT.Pack = _pack_tensor
171+
_BundledProgram.BundledProgramT.Pack = _pack_bundled_program
172+
173+
174+
def _convert_tensor(val: bp_schema.Tensor) -> Any:
175+
result = _Tensor.TensorT()
176+
result.scalarType = int(val.scalar_type)
177+
result.sizes = list(val.sizes)
178+
result.data = _coerce_bytes(val.data)
179+
result.dimOrder = _coerce_bytes(val.dim_order)
180+
return result
181+
182+
183+
def _convert_int(val: bp_schema.Int) -> Any:
184+
result = _Int.IntT()
185+
result.intVal = val.int_val
186+
return result
187+
188+
189+
def _convert_bool(val: bp_schema.Bool) -> Any:
190+
result = _Bool.BoolT()
191+
result.boolVal = val.bool_val
192+
return result
193+
194+
195+
def _convert_double(val: bp_schema.Double) -> Any:
196+
result = _Double.DoubleT()
197+
result.doubleVal = val.double_val
198+
return result
199+
200+
201+
def _convert_value_union(val: bp_schema.ValueUnion) -> tuple[int, Any]:
202+
if isinstance(val, bp_schema.Tensor):
203+
return _ValueUnion.ValueUnion.Tensor, _convert_tensor(val)
204+
if isinstance(val, bp_schema.Int):
205+
return _ValueUnion.ValueUnion.Int, _convert_int(val)
206+
if isinstance(val, bp_schema.Bool):
207+
return _ValueUnion.ValueUnion.Bool, _convert_bool(val)
208+
if isinstance(val, bp_schema.Double):
209+
return _ValueUnion.ValueUnion.Double, _convert_double(val)
210+
return _ValueUnion.ValueUnion.NONE, None
211+
212+
213+
def _convert_value(val: bp_schema.Value) -> Any:
214+
result = _Value.ValueT()
215+
result.valType, result.val = _convert_value_union(val.val)
216+
return result
217+
218+
219+
def _convert_method_test_case(val: bp_schema.BundledMethodTestCase) -> Any:
220+
result = _BundledMethodTestCase.BundledMethodTestCaseT()
221+
result.inputs = [_convert_value(value) for value in val.inputs]
222+
result.expectedOutputs = [_convert_value(value) for value in val.expected_outputs]
223+
return result
224+
225+
226+
def _convert_method_test_suite(val: bp_schema.BundledMethodTestSuite) -> Any:
227+
result = _BundledMethodTestSuite.BundledMethodTestSuiteT()
228+
result.methodName = val.method_name
229+
result.testCases = [
230+
_convert_method_test_case(test_case) for test_case in val.test_cases
231+
]
232+
return result
233+
234+
235+
def _convert_bundled_program(val: bp_schema.BundledProgram) -> Any:
236+
result = _BundledProgram.BundledProgramT()
237+
result.version = val.version
238+
result.methodTestSuites = [
239+
_convert_method_test_suite(suite) for suite in val.method_test_suites
240+
]
241+
result.program = _coerce_bytes(val.program)
242+
return result
243+
244+
245+
def _bundled_program_schema_to_flatbuffer(
246+
bundled_program: bp_schema.BundledProgram,
247+
) -> bytes:
248+
_install_fast_packers()
249+
bundled_program_t = _convert_bundled_program(bundled_program)
250+
builder = flatbuffers.Builder()
251+
bundled_program_offset = bundled_program_t.Pack(builder)
252+
builder.Finish(
253+
bundled_program_offset,
254+
file_identifier=_bundled_program_file_identifier(),
255+
)
256+
return bytes(builder.Output())
257+
258+
81259
# from bundled program to flatbuffer
82260
def serialize_from_bundled_program_to_flatbuffer(
83261
bundled_program: BundledProgram,
@@ -94,9 +272,7 @@ def serialize_from_bundled_program_to_flatbuffer(
94272

95273
bundled_program_in_schema = bundled_program.serialize_to_schema()
96274

97-
return convert_to_flatbuffer(
98-
serialize_from_bundled_program_to_json(bundled_program_in_schema)
99-
)
275+
return _bundled_program_schema_to_flatbuffer(bundled_program_in_schema)
100276

101277

102278
# From flatbuffer to bundled program in schema.

0 commit comments

Comments
 (0)