Skip to content

Commit e4897f1

Browse files
Gasoonjiafacebook-github-bot
authored andcommitted
end2end test for bundled program (#395)
Summary: Pull Request resolved: #395 Update pybinding functions to support bundled program (bp) APIs with method name. Split the end2end test for bp into a single test file. The reasonsfor spliting are: 1. Current overall end2end test does not support multiple methods, but we need to test bp on multiple methods. 2. Bp does not need to be tested on several different kinds of program. 3. Decompose the test for core ET and Bp. Differential Revision: D49336485 fbshipit-source-id: a2f398acfb0c4eb2b43afc860aadfa0c543c7dd5
1 parent 664e91e commit e4897f1

File tree

4 files changed

+180
-109
lines changed

4 files changed

+180
-109
lines changed

bundled_program/tests/TARGETS

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# @noautodeps
2+
13
load("@fbcode_macros//build_defs:python_library.bzl", "python_library")
24
load("@fbcode_macros//build_defs:python_unittest.bzl", "python_unittest")
35

@@ -58,3 +60,33 @@ python_unittest(
5860
"//executorch/extension/pytree:pylib",
5961
],
6062
)
63+
64+
python_unittest(
65+
name = "end2end",
66+
srcs = [
67+
"test_end2end.py",
68+
],
69+
deps = [
70+
":lib",
71+
"//caffe2:torch",
72+
"//executorch/bundled_program:config",
73+
"//executorch/bundled_program:core",
74+
"//executorch/bundled_program/serialize:lib",
75+
"//executorch/exir:dynamic_shape",
76+
"//executorch/exir:lib",
77+
"//executorch/exir:memory",
78+
"//executorch/exir:pass_manager",
79+
"//executorch/exir:print_program",
80+
"//executorch/exir:tensor",
81+
"//executorch/exir/_serialize:lib",
82+
"//executorch/exir/emit:lib",
83+
"//executorch/exir/passes:lib",
84+
"//executorch/exir/tests:control_flow_models",
85+
"//executorch/exir/tests:dynamic_shape_models",
86+
"//executorch/exir/tests:models",
87+
"//executorch/exir/tests:transformer",
88+
"//executorch/extension/pybindings:portable_lib",
89+
"//executorch/extension/pytree:pybindings",
90+
"//executorch/kernels/portable:custom_ops_generated_lib",
91+
],
92+
)

bundled_program/tests/test_end2end.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
# flake8: noqa: F401
8+
import functools
9+
import inspect
10+
import os
11+
import random
12+
import unittest
13+
from typing import Callable, Dict, Optional, Tuple, Type
14+
15+
import executorch.exir as exir
16+
17+
import executorch.exir.control_flow as control_flow
18+
19+
# @manual=//executorch/extension/pytree:pybindings
20+
import executorch.extension.pytree as pytree
21+
22+
import torch
23+
from executorch.bundled_program.config import BundledConfig
24+
25+
from executorch.bundled_program.core import create_bundled_program
26+
from executorch.bundled_program.serialize import (
27+
serialize_from_bundled_program_to_flatbuffer,
28+
)
29+
30+
from executorch.bundled_program.tests.common import get_common_program, SampleModel
31+
32+
kernel_mode = None # either aten mode or lean mode
33+
try:
34+
# pyre-ignore[21]
35+
from executorch.extension.pybindings.portable_lib import (
36+
_load_bundled_program_from_buffer,
37+
_load_for_executorch_from_buffer,
38+
_load_for_executorch_from_bundled_program,
39+
)
40+
41+
kernel_mode = "lean"
42+
except ImportError as e:
43+
print(e)
44+
pass
45+
46+
try:
47+
# pyre-ignore[21]
48+
from executorch.extension.pybindings.aten_lib import (
49+
_load_bundled_program_from_buffer,
50+
_load_for_executorch_from_buffer,
51+
_load_for_executorch_from_bundled_program,
52+
)
53+
54+
assert kernel_mode is None
55+
kernel_mode = "aten"
56+
except ImportError as e:
57+
print(e)
58+
pass
59+
60+
assert kernel_mode is not None
61+
62+
63+
class BundledProgramE2ETest(unittest.TestCase):
64+
def test_sample_model_e2e(self):
65+
program, bundled_config = get_common_program()
66+
eager_model = SampleModel()
67+
68+
bundled_program = create_bundled_program(program, bundled_config)
69+
70+
bundled_program_buffer = serialize_from_bundled_program_to_flatbuffer(
71+
bundled_program
72+
)
73+
74+
executorch_bundled_program = _load_bundled_program_from_buffer(
75+
bundled_program_buffer
76+
)
77+
78+
executorch_module = _load_for_executorch_from_bundled_program(
79+
executorch_bundled_program
80+
)
81+
82+
for method_name in eager_model.method_names:
83+
executorch_module.load_bundled_input(
84+
executorch_bundled_program,
85+
method_name,
86+
0,
87+
)
88+
executorch_module.plan_execute(method_name)
89+
executorch_module.verify_result_with_bundled_expected_output(
90+
executorch_bundled_program,
91+
method_name,
92+
0,
93+
)

extension/pybindings/pybindings.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,14 @@ class Module final {
175175
return result;
176176
}
177177

178+
Method& get_method(const std::string& method_name) {
179+
if (methods_.count(method_name) == 0) {
180+
THROW_IF_ERROR(
181+
Error(), "no such method in program: %s", method_name.c_str());
182+
}
183+
return *methods_[method_name].get();
184+
}
185+
178186
private:
179187
/// A wrapper/util class for executorch memory allocations/manager.
180188
class Memory {
@@ -449,6 +457,48 @@ struct PyModule final {
449457
return run_method("forward", inputs);
450458
}
451459

460+
void load_bundled_input(
461+
PyBundledModule& m,
462+
const string method_name,
463+
size_t testset_idx) {
464+
const void* bundled_program_ptr = m.get_bundled_program_ptr();
465+
Error status = util::LoadBundledInput(
466+
module_->get_method(method_name),
467+
bundled_program_ptr,
468+
&m.get_bundled_input_allocator(),
469+
method_name.c_str(),
470+
testset_idx);
471+
ET_CHECK_MSG(
472+
status == Error::Ok,
473+
"LoadBundledInput failed with status %" PRIu32,
474+
status);
475+
}
476+
477+
void verify_result_with_bundled_expected_output(
478+
PyBundledModule& m,
479+
const string method_name,
480+
size_t testset_idx) {
481+
const void* bundled_program_ptr = m.get_bundled_program_ptr();
482+
Error status = util::VerifyResultWithBundledExpectedOutput(
483+
module_->get_method(method_name),
484+
bundled_program_ptr,
485+
&m.get_bundled_input_allocator(),
486+
method_name.c_str(),
487+
testset_idx);
488+
ET_CHECK_MSG(
489+
status == Error::Ok,
490+
"Result verification failed with status %" PRIu32,
491+
status);
492+
}
493+
494+
void plan_execute(const string method_name) {
495+
auto status = module_->get_method(method_name).execute();
496+
THROW_IF_ERROR(
497+
status,
498+
"executing execution plan for method 'forward' failed with error: 0x%" PRIx32,
499+
status);
500+
}
501+
452502
private:
453503
std::unique_ptr<Module> module_;
454504
};
@@ -485,6 +535,11 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
485535
m.def("_reset_profile_results", []() { EXECUTORCH_RESET_PROFILE_RESULTS(); });
486536

487537
py::class_<PyModule>(m, "ExecutorchModule")
538+
.def("load_bundled_input", &PyModule::load_bundled_input)
539+
.def(
540+
"verify_result_with_bundled_expected_output",
541+
&PyModule::verify_result_with_bundled_expected_output)
542+
.def("plan_execute", &PyModule::plan_execute)
488543
.def("run_method", &PyModule::run_method)
489544
.def("forward", &PyModule::forward);
490545

test/end2end/test_end2end.py

Lines changed: 0 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,7 @@
2020
# @manual=//executorch/extension/pytree:pybindings
2121
import executorch.extension.pytree as pytree
2222
import torch
23-
from executorch.bundled_program.config import BundledConfig
2423

25-
from executorch.bundled_program.core import create_bundled_program
26-
from executorch.bundled_program.serialize import (
27-
serialize_from_bundled_program_to_flatbuffer,
28-
)
2924
from executorch.exir import (
3025
CaptureConfig,
3126
EdgeCompileConfig,
@@ -486,7 +481,6 @@ def maketest(
486481
allow_non_contiguous_tensor: bool = False,
487482
method: str = "forward",
488483
dynamic_memory_planning_mode: DynamicMemoryPlanningMode = DynamicMemoryPlanningMode.UPPER_BOUND,
489-
bundled_io=False,
490484
capture_config=None,
491485
verify_graph: Optional[Callable] = None,
492486
) -> Callable[[unittest.TestCase], None]:
@@ -510,9 +504,6 @@ def maketest(
510504
program only contains contiguous tensors.
511505
method: The name of the module_cls method to trace.
512506
dynamic_memory_planning_mode: The dynamic memory planning mode to use.
513-
bundled_io: If true, will wrap the given program into bundled program
514-
format, run and compared the program with bundled input and expected
515-
output.
516507
517508
Returns:
518509
A TestCase method that tests the provided module class and method.
@@ -587,54 +578,6 @@ def wrapper(self: unittest.TestCase) -> None:
587578
print(f"actual result: {actual}")
588579
self.assertTrue(is_close)
589580

590-
if bundled_io:
591-
print("Being verified by Bundled Program")
592-
expected_outputs_list = [
593-
[[module.eager_module(*x)] for x in inputs_list],
594-
]
595-
bundled_config = BundledConfig(
596-
[
597-
method,
598-
],
599-
[
600-
inputs_list,
601-
],
602-
expected_outputs_list,
603-
)
604-
bundled_program = create_bundled_program(
605-
module.executorch_program.program, bundled_config
606-
)
607-
bundled_program_buffer = serialize_from_bundled_program_to_flatbuffer(
608-
bundled_program
609-
)
610-
611-
# pyre-fixme[16]: Module `executorch.extension.pybindings` has no attribute `portable`.
612-
executorch_bundled_program = _load_bundled_program_from_buffer(
613-
bundled_program_buffer
614-
)
615-
616-
# pyre-fixme[16]: Module `executorch.extension.pybindings` has no attribute `portable`.
617-
executorch_module = _load_for_executorch_from_bundled_program(
618-
executorch_bundled_program
619-
)
620-
621-
default_execution_plan_id = 0
622-
623-
# TODO(T144329357): check bundled attachment correctness
624-
# No load_bundled_input() method
625-
# for testset_idx in range(niter):
626-
# executorch_module.load_bundled_input(
627-
# executorch_bundled_program,
628-
# default_execution_plan_id,
629-
# testset_idx,
630-
# )
631-
# executorch_module.plan_execute()
632-
# executorch_module.verify_result_with_bundled_expected_output(
633-
# executorch_bundled_program,
634-
# default_execution_plan_id,
635-
# testset_idx,
636-
# )
637-
638581
return wrapper
639582

640583

@@ -823,55 +766,3 @@ def test_batch_norm(self):
823766
# run this on aten mode.
824767
run_executor=is_aten_mode,
825768
)(self)
826-
827-
828-
class BundledProgramE2ETest(unittest.TestCase):
829-
# Using all models supporting executor running in this test.
830-
831-
def test_mem_planning_toy_model_bundled_program(self):
832-
maketest(ToyModelForMemPlanning, bundled_io=True)(self)
833-
834-
def test_executorch_forward_bundled_program(self):
835-
maketest(ModuleAdd, bundled_io=True)(self)
836-
837-
@skipUnless(RUN_SKIPPED, "TODO(larryliu0820) Fix this in both fbcode and oss")
838-
def test_containers_bundled_program(self):
839-
maketest(ModuleContainers, do_tree_flatten=True, bundled_io=True)(self)
840-
841-
# Failed to produce a graph during tracing w/ dynamo because there are no torch ops
842-
# test_return_input_bundled_program = maketest(
843-
# ModuleReturnInput, do_tree_flatten=True, bundled_io=True
844-
# )
845-
846-
# can not run this on the executor because missing the following ops:
847-
# aten::select_copy.int_out, aten::eq.Scalar_out
848-
# TODO(gasoonjia) re-enable these tests.
849-
# test_ifelse_bundled_program = maketest(ModuleIfElse, bundled_io=True)
850-
# test_ifelse_with_bool_bundled_program = maketest(ModuleIfElseWithBool, bundled_io=True)
851-
852-
# fail to trace with functionalization enabled
853-
# Fail with error: Missing out variants: {'aten::select', 'aten::_shape_as_tensor', 'aten::tensor_split'}
854-
# TODO(gasoonjia) re-enable these tests.
855-
# test_while_bundled_program = maketest(ModuleWhile, bundled_io=True)
856-
857-
# test_while_if_bundled_program = maketest(ModuleWhileIf, bundled_io=True)
858-
# test_if_while_bundled_program = maketest(ModuleIfWhile, bundled_io=True)
859-
860-
def test_input_dynamic_shape_bundled_program(self):
861-
maketest(
862-
ModuleInputDynamicShape,
863-
run_graph_module=False,
864-
bundled_io=True,
865-
capture_config=exir.CaptureConfig(
866-
enable_dynamic_shape=True,
867-
),
868-
)(self)
869-
870-
@skip("revisit when unbacked symint is ready")
871-
def test_intermediate_dynamic_shape_bundled_program(self):
872-
maketest(
873-
ModuleIntermediateDynamicShape,
874-
run_graph_module=False,
875-
allow_non_contiguous_tensor=True,
876-
bundled_io=True,
877-
)(self)

0 commit comments

Comments
 (0)