Skip to content

Commit 98c1ec9

Browse files
cccclaifacebook-github-bot
authored andcommitted
Emit lowered module (#89)
Summary: Pull Request resolved: #89 It has been a pending task for a while, as a follow up on https://fb.workplace.com/groups/536346827621174/permalink/665126474743208/ we want the lowered backend module to be **runnerable**, **emittable**, and **retracable**. This diff makes the lowered backend module emittable without the need to composite with other modules It will the easiest the flow for backend developer to try lower one op to a backend via delegate. Reviewed By: angelayi Differential Revision: D47803806 fbshipit-source-id: aae31442591e497976359413a088cb2460107627
1 parent da993fd commit 98c1ec9

File tree

6 files changed

+431
-34
lines changed

6 files changed

+431
-34
lines changed

exir/TARGETS

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,14 @@ python_library(
120120
deps = [
121121
":delegate",
122122
":graph_module",
123-
":lib",
123+
":schema",
124124
":tracer",
125125
"//caffe2:torch",
126126
"//executorch/exir/backend:compile_spec_schema",
127+
"//executorch/exir/emit:lib",
128+
"//executorch/exir/passes:memory_planning_pass",
129+
"//executorch/exir/passes:spec_prop_pass",
130+
"//executorch/exir/serialize:lib",
127131
],
128132
)
129133

exir/backend/test/TARGETS

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ python_unittest(
145145
],
146146
)
147147

148+
python_unittest(
149+
name = "test_lowered_backend_module",
150+
srcs = [
151+
"test_lowered_backend_module.py",
152+
],
153+
supports_static_listing = True,
154+
deps = [
155+
"fbsource//third-party/pypi/hypothesis:hypothesis",
156+
":backend_with_compiler_demo",
157+
":qnn_backend_demo",
158+
"//caffe2:torch",
159+
"//executorch/exir:lib",
160+
"//executorch/exir:schema",
161+
"//executorch/exir/backend:backend_api",
162+
"//executorch/exir/backend:compile_spec_schema",
163+
"//executorch/exir/tests:models",
164+
"//executorch/extension/pybindings:portable", # @manual
165+
"//executorch/kernels/portable:custom_ops_generated_lib",
166+
"//executorch/kernels/quantized:custom_ops_generated_lib",
167+
"//executorch/runtime/executor/test:test_backend_compiler_lib",
168+
],
169+
)
170+
148171
python_unittest(
149172
name = "test_graph_partition",
150173
srcs = [

exir/backend/test/test_backends.py

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -115,31 +115,6 @@ def check_backend_delegate(
115115
program.backend_delegate_data[processed.index].data, expected_processed
116116
)
117117

118-
def test_simple(self):
119-
class SinModule(torch.nn.Module):
120-
def __init__(self):
121-
super().__init__()
122-
123-
def forward(self, x):
124-
return torch.sin(x)
125-
126-
sin_module = SinModule()
127-
model_inputs = (torch.ones(1),)
128-
expected_res = sin_module(*model_inputs)
129-
edgeir_m = exir.capture(
130-
sin_module, model_inputs, exir.CaptureConfig()
131-
).to_edge()
132-
133-
lowered_sin_module = to_backend(
134-
"BackendWithCompilerDemo", edgeir_m.exported_program, []
135-
)
136-
new_res = lowered_sin_module(*model_inputs)
137-
138-
self.assertTrue(torch.allclose(new_res, expected_res))
139-
140-
# TODO(tkaruturi): emitting single LoweredBackendModule
141-
# program = exir.capture(graph_module).to_edge().to_exectorch().program
142-
143118
@vary_segments
144119
def test_backend_with_compiler(self, extract_segments: bool):
145120
class SinModule(torch.nn.Module):
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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 executorch.exir.tests.models as models
10+
11+
import torch
12+
from executorch import exir
13+
from executorch.exir.backend.backend_api import to_backend
14+
from executorch.exir.backend.compile_spec_schema import CompileSpec
15+
from executorch.exir.backend.test.backend_with_compiler_demo import (
16+
BackendWithCompilerDemo,
17+
)
18+
from executorch.exir.backend.test.qnn_backend_demo import QnnBackend
19+
from executorch.exir.schema import DelegateCall, Program
20+
21+
from executorch.extension.pybindings.portable import ( # @manual
22+
_load_for_executorch_from_buffer,
23+
)
24+
from hypothesis import given, settings, strategies as st
25+
26+
27+
class TestBackendAPI(unittest.TestCase):
28+
def validate_lowered_module_program(self, program: Program) -> None:
29+
"""
30+
For any program emitted from lowered_backend_module, we expect only one delegate call
31+
"""
32+
# there should only be one instruction
33+
self.assertEqual(
34+
len(program.execution_plan[0].chains[0].instructions),
35+
1,
36+
)
37+
38+
# the only instruction should be a delegate call
39+
self.assertTrue(
40+
isinstance(
41+
program.execution_plan[0].chains[0].instructions[0].instr_args,
42+
DelegateCall,
43+
)
44+
)
45+
46+
def get_program_from_wrapped_module(
47+
self, lowered_module, example_inputs, capture_config, edge_compile_config
48+
):
49+
class WrappedModule(torch.nn.Module):
50+
def __init__(self):
51+
super().__init__()
52+
self.one_module = lowered_module
53+
54+
def forward(self, *args):
55+
return self.one_module(*args)
56+
57+
return (
58+
exir.capture(WrappedModule(), example_inputs, capture_config)
59+
.to_edge(edge_compile_config)
60+
.to_executorch()
61+
.program
62+
)
63+
64+
@given(
65+
unlift=st.booleans(), # verify both lifted and unlifted graph
66+
)
67+
@settings(deadline=500000)
68+
def test_emit_lowered_backend_module_end_to_end(self, unlift):
69+
class SinModule(torch.nn.Module):
70+
def __init__(self):
71+
super().__init__()
72+
73+
def forward(self, x):
74+
return torch.sin(x)
75+
76+
sin_module = SinModule()
77+
model_inputs = (torch.ones(1),)
78+
expected_res = sin_module(*model_inputs)
79+
edgeir_m = exir.capture(
80+
sin_module,
81+
model_inputs,
82+
exir.CaptureConfig(pt2_mode=True, enable_aot=True, _unlift=unlift),
83+
).to_edge(exir.EdgeCompileConfig(_check_ir_validity=False, _use_edge_ops=True))
84+
max_value = model_inputs[0].shape[0]
85+
compile_specs = [CompileSpec("max_value", bytes([max_value]))]
86+
lowered_sin_module = to_backend(
87+
BackendWithCompilerDemo.__name__, edgeir_m.exported_program, compile_specs
88+
)
89+
90+
new_res = lowered_sin_module(*model_inputs)
91+
92+
self.assertTrue(torch.allclose(new_res[0], expected_res))
93+
program = lowered_sin_module.program()
94+
self.validate_lowered_module_program(program)
95+
buff = lowered_sin_module.buffer()
96+
97+
executorch_module = _load_for_executorch_from_buffer(buff)
98+
model_inputs = torch.ones(1)
99+
model_outputs = executorch_module.forward([model_inputs])
100+
self.assertEqual(
101+
model_inputs,
102+
torch.ones(1),
103+
)
104+
expected_res = 0.8333 * torch.ones(1)
105+
106+
self.assertTrue(
107+
torch.allclose(model_outputs[0], expected_res, atol=1e-03, rtol=1e-03)
108+
)
109+
110+
@given(
111+
unlift=st.booleans(), # verify both lifted and unlifted graph
112+
)
113+
@settings(deadline=500000)
114+
def test_emit_lowered_backend_module(self, unlift):
115+
module_list = [
116+
models.Emformer(),
117+
models.Repeat(),
118+
models.ElementwiseAdd(),
119+
models.MLP(),
120+
models.ModelWithUnusedArg(),
121+
]
122+
123+
capture_config = (
124+
exir.CaptureConfig(enable_aot=True) if unlift else exir.CaptureConfig()
125+
)
126+
127+
edge_compile_config = exir.EdgeCompileConfig(
128+
_check_ir_validity=False, _use_edge_ops=True
129+
)
130+
131+
for model in module_list:
132+
model_inputs = model.get_random_inputs()
133+
134+
edgeir_m = exir.capture(model, model_inputs, capture_config).to_edge(
135+
edge_compile_config
136+
)
137+
lowered_model = to_backend(
138+
QnnBackend.__name__, edgeir_m.exported_program, []
139+
)
140+
program = lowered_model.program()
141+
reference_program = self.get_program_from_wrapped_module(
142+
lowered_model, model_inputs, capture_config, edge_compile_config
143+
)
144+
145+
# Check program is fairly equal to the reference program
146+
self.assertEqual(
147+
len(program.execution_plan[0].chains[0].instructions),
148+
len(reference_program.execution_plan[0].chains[0].instructions),
149+
)
150+
151+
self.assertEqual(
152+
len(program.execution_plan[0].values),
153+
len(reference_program.execution_plan[0].values),
154+
)
155+
156+
self.assertEqual(
157+
len(program.execution_plan[0].inputs),
158+
len(reference_program.execution_plan[0].inputs),
159+
)
160+
161+
self.assertEqual(
162+
len(program.execution_plan[0].outputs),
163+
len(reference_program.execution_plan[0].outputs),
164+
)
165+
166+
# Ensure we can get the buffer
167+
_ = lowered_model.buffer()
168+
self.validate_lowered_module_program(program)
169+
170+
@given(
171+
unlift=st.booleans(), # verify both lifted and unlifted graph
172+
)
173+
@settings(deadline=500000)
174+
def test_emit_nested_lowered_backend_module(self, unlift):
175+
module_list = [
176+
models.Emformer(),
177+
models.Repeat(),
178+
models.ElementwiseAdd(),
179+
models.MLP(),
180+
models.ModelWithUnusedArg(),
181+
]
182+
183+
capture_config = (
184+
exir.CaptureConfig(enable_aot=True) if unlift else exir.CaptureConfig()
185+
)
186+
187+
edge_compile_config = exir.EdgeCompileConfig(
188+
_check_ir_validity=False, _use_edge_ops=True
189+
)
190+
191+
for model in module_list:
192+
model_inputs = model.get_random_inputs()
193+
194+
edgeir_m = exir.capture(model, model_inputs, capture_config).to_edge(
195+
edge_compile_config
196+
)
197+
lowered_module = to_backend(
198+
QnnBackend.__name__, edgeir_m.exported_program, []
199+
)
200+
201+
# This module will include one operator and two delegate call
202+
class WrappedModule(torch.nn.Module):
203+
def __init__(self, lowered_module):
204+
super().__init__()
205+
self.one_module = lowered_module
206+
207+
def forward(self, *args):
208+
return self.one_module(*args)
209+
210+
wrapped_module = WrappedModule(lowered_module)
211+
wrapped_module_edge = exir.capture(
212+
wrapped_module, model_inputs, capture_config
213+
).to_edge(edge_compile_config)
214+
215+
nested_lowered_model = to_backend(
216+
QnnBackend.__name__, wrapped_module_edge.exported_program, []
217+
)
218+
219+
program = nested_lowered_model.program()
220+
self.validate_lowered_module_program(program)

0 commit comments

Comments
 (0)