Skip to content

Commit 4fcd903

Browse files
cccclaifacebook-github-bot
authored andcommitted
update the on device delegate compatibility (#4060)
Summary: Pull Request resolved: #4060 As discussed offline, mainly two changes in this diff - add a unit test in the demo backend that will throw `DelegateInvalidCompatibility` when getting a wrong version - Add doc block for the compatibility error report during init Reviewed By: mergennachin, kirklandsign Differential Revision: D58986515 fbshipit-source-id: 0b1da8a770fe564485ceb3a28c3d13831ee37528
1 parent 59e706a commit 4fcd903

File tree

8 files changed

+121
-8
lines changed

8 files changed

+121
-8
lines changed

exir/backend/test/TARGETS

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,3 +318,22 @@ python_unittest(
318318
"//executorch/exir/backend/canonical_partitioners:duplicate_constant_node_pass",
319319
],
320320
)
321+
322+
python_unittest(
323+
name = "test_compatibility",
324+
srcs = [
325+
"test_compatibility.py",
326+
],
327+
preload_deps = [
328+
"//executorch/runtime/executor/test:test_backend_compiler_lib",
329+
],
330+
deps = [
331+
":backend_with_compiler_demo",
332+
"//caffe2:torch",
333+
"//executorch/exir:lib",
334+
"//executorch/exir/_serialize:lib",
335+
"//executorch/exir/backend:backend_api",
336+
"//executorch/exir/backend:compile_spec_schema",
337+
"//executorch/extension/pybindings:portable_lib", # @manual
338+
],
339+
)

exir/backend/test/backend_with_compiler_demo.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def preprocess(
8282
) -> PreprocessResult:
8383
processed_bytes = ""
8484
number_of_instruction = 0
85+
version = "0"
8586
debug_handle_map = {}
8687
match_ops = [
8788
exir_ops.edge.aten.sin.default,
@@ -129,7 +130,12 @@ def preprocess(
129130
debug_handle_map[new_debug_id] = (original_debug_id,)
130131
return PreprocessResult(
131132
processed_bytes=bytes(
132-
str(number_of_instruction) + "#" + processed_bytes, encoding="utf8"
133+
str(number_of_instruction)
134+
+ "version:"
135+
+ version
136+
+ "#"
137+
+ processed_bytes,
138+
encoding="utf8",
133139
),
134140
debug_handle_map=debug_handle_map,
135141
)

exir/backend/test/test_backends.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def forward(self, x):
194194
program=program,
195195
delegate=program.execution_plan[0].delegates[0],
196196
expected_id=BackendWithCompilerDemo.__name__,
197-
expected_processed=b"1#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
197+
expected_processed=b"1version:0#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
198198
)
199199

200200
# Check the delegate instruction
@@ -414,7 +414,7 @@ def forward(self, x):
414414
program=program,
415415
delegate=program.execution_plan[0].delegates[0],
416416
expected_id=BackendWithCompilerDemo.__name__,
417-
expected_processed=b"1#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
417+
expected_processed=b"1version:0#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
418418
)
419419

420420
# Check the delegate instruction

exir/backend/test/test_backends_lifted.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def forward(self, x):
210210
program=program,
211211
delegate=program.execution_plan[0].delegates[0],
212212
expected_id=BackendWithCompilerDemo.__name__,
213-
expected_processed=b"1#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
213+
expected_processed=b"1version:0#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
214214
)
215215

216216
# Check the delegate instruction
@@ -414,7 +414,7 @@ def forward(self, x):
414414
program=program,
415415
delegate=program.execution_plan[0].delegates[0],
416416
expected_id=BackendWithCompilerDemo.__name__,
417-
expected_processed=b"1#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
417+
expected_processed=b"1version:0#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
418418
)
419419

420420
# Check the delegate instruction
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
#
4+
# This source code is licensed under the BSD-style license found in the
5+
# LICENSE file in the root directory of this source tree.
6+
7+
import unittest
8+
9+
import torch
10+
from executorch.exir import to_edge
11+
from executorch.exir._serialize import _serialize_pte_binary
12+
from executorch.exir.backend.backend_api import to_backend
13+
from executorch.exir.backend.compile_spec_schema import CompileSpec
14+
from executorch.exir.backend.test.backend_with_compiler_demo import (
15+
BackendWithCompilerDemo,
16+
)
17+
18+
from executorch.extension.pybindings.portable_lib import (
19+
_load_for_executorch_from_buffer, # @manual
20+
)
21+
from torch.export import export
22+
23+
24+
class TestCompatibility(unittest.TestCase):
25+
def test_compatibility_in_runtime(self):
26+
class SinModule(torch.nn.Module):
27+
def __init__(self):
28+
super().__init__()
29+
30+
def forward(self, x):
31+
return torch.sin(x)
32+
33+
sin_module = SinModule()
34+
model_inputs = (torch.ones(1),)
35+
edgeir_m = to_edge(export(sin_module, model_inputs))
36+
max_value = model_inputs[0].shape[0]
37+
compile_specs = [CompileSpec("max_value", bytes([max_value]))]
38+
lowered_sin_module = to_backend(
39+
BackendWithCompilerDemo.__name__, edgeir_m.exported_program(), compile_specs
40+
)
41+
buff = lowered_sin_module.buffer()
42+
43+
# The demo backend works well
44+
executorch_module = _load_for_executorch_from_buffer(buff)
45+
model_inputs = torch.ones(1)
46+
_ = executorch_module.forward([model_inputs])
47+
48+
prog = lowered_sin_module.program()
49+
# Rewrite the delegate version number from 0 to 1.
50+
prog.backend_delegate_data[0].data = bytes(
51+
"1version:1#op:demo::aten.sin.default, numel:1, dtype:torch.float32<debug_handle>1#",
52+
encoding="utf8",
53+
)
54+
55+
# Generate the .pte file with the wrong version.
56+
buff = bytes(
57+
_serialize_pte_binary(
58+
program=prog,
59+
)
60+
)
61+
62+
# Throw runtime error with error code 0x30, meaning delegate is incompatible.
63+
with self.assertRaisesRegex(
64+
RuntimeError,
65+
"loading method forward failed with error 0x30",
66+
):
67+
executorch_module = _load_for_executorch_from_buffer(buff)

pytest.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ addopts =
4141
--ignore=exir/backend/test/demos
4242
--ignore=exir/backend/test/test_backends.py
4343
--ignore=exir/backend/test/test_backends_lifted.py
44+
--ignore=exir/backend/test/test_compatibility.py
4445
--ignore=exir/backend/test/test_lowered_backend_module.py
4546
--ignore=exir/backend/test/test_partitioner.py
4647
--ignore=exir/tests/test_common.py

runtime/backend/interface.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,11 @@ class PyTorchBackendInterface {
7272
* implemented by the delegate. This handle is passed to `execute()` and
7373
* `destroy()`, and the memory it points to is owned by the backend.
7474
* Typically points to a backend-private class/struct.
75-
* @returns On error, a value other than Error:Ok.
75+
* @returns On error, returns an error code other than Error::Ok. If the
76+
* compiled unit (the preprocessed result from ahead of time) is not
77+
* compatible with the current backend runtime, return the error code
78+
* Error::DelegateInvalidCompatibility. Other backend delegate
79+
* specific error codes can be found in error.h.
7680
*/
7781
__ET_NODISCARD virtual Result<DelegateHandle*> init(
7882
BackendInitContext& context,

runtime/executor/test/test_backend_compiler_lib.cpp

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,25 @@ class BackendWithCompiler final : public PyTorchBackendInterface {
109109
const char* kSignLiteral = "#";
110110
// The first number is the number of total instruction
111111
const char* start = static_cast<const char*>(processed->data());
112-
char* instruction_number_end =
112+
113+
const char* kVersion = "version:";
114+
const long int kRuntimeVersion = 0;
115+
char* version_start =
116+
const_cast<char*>(strstr(start, kVersion)) + strlen(kVersion);
117+
char* version_end;
118+
char* instruction_set_start =
113119
const_cast<char*>(strstr(start, kSignLiteral));
120+
121+
long int version = strtol(version_start, &version_end, 10);
122+
ET_CHECK_OR_RETURN_ERROR(
123+
version == kRuntimeVersion,
124+
DelegateInvalidCompatibility,
125+
"The version of BackendWithCompiler runtime is %ld, but received an incompatible version %ld instead.",
126+
kRuntimeVersion,
127+
version);
128+
char* instruction_number_end;
114129
long int instruction_number = strtol(start, &instruction_number_end, 10);
130+
115131
ET_CHECK_OR_RETURN_ERROR(
116132
instruction_number >= 0,
117133
InvalidArgument,
@@ -124,7 +140,7 @@ class BackendWithCompiler final : public PyTorchBackendInterface {
124140
runtime_allocator, DemoOp, instruction_number);
125141
op_list->numops = static_cast<size_t>(instruction_number);
126142

127-
parse_delegate(instruction_number_end + 1, kSignLiteral, op_list->ops);
143+
parse_delegate(instruction_set_start + 1, kSignLiteral, op_list->ops);
128144

129145
// Can't call `processed->Free()` because op_list points into it.
130146

0 commit comments

Comments
 (0)