Skip to content

Commit aeef64d

Browse files
committed
[ExecuTorch][#10447] Extend PyBundledModule with extension.BundledModule
# Context This issue is a step of #9638. In #9638, we want to have `extension.Module` as the single source of implementation in `pybindings`, which means that `pybindings.PyModule` should use `extension.Module` rather than its own `pybindings.Module`. # Proposal Now that we have `extension.BundledModule` ready, we want to test it out by having our existing `PyBundledModule` to extend it, and let `verify_result_with_bundled_expected_output` to use it, so that we can test out the whole thing with https://github.com/pytorch/executorch/blob/fb45e19055a92d2a91a4d4b7008e135232cbb14b/devtools/bundled_program/test/test_end2end.py ghstack-source-id: 289817714 ghstack-source-id: 289817714 exported-using-ghexport Differential Revision: [D76751313](https://our.internmc.facebook.com/intern/diff/D76751313/) ghstack-source-id: 290750933 Pull Request resolved: #11737
1 parent 097d7d4 commit aeef64d

File tree

6 files changed

+104
-122
lines changed

6 files changed

+104
-122
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,14 @@ if(EXECUTORCH_BUILD_PYBIND)
583583
torch
584584
)
585585

586+
if(EXECUTORCH_BUILD_EXTENSION_MODULE)
587+
if(CMAKE_TOOLCHAIN_IOS OR CMAKE_TOOLCHAIN_ANDROID OR APPLE)
588+
list(APPEND _dep_libs extension_module_static)
589+
else()
590+
list(APPEND _dep_libs extension_module)
591+
endif()
592+
endif()
593+
586594
if(EXECUTORCH_BUILD_TESTS)
587595
list(APPEND _dep_libs test_backend_compiler_lib)
588596
endif()

devtools/bundled_program/test/test_end2end.py

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,7 @@
55
# LICENSE file in the root directory of this source tree.
66

77
# flake8: noqa: F401
8-
import functools
9-
import inspect
10-
import os
11-
import random
128
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
239

2410
from executorch.devtools.bundled_program.core import BundledProgram
2511
from executorch.devtools.bundled_program.serialize import (
@@ -35,8 +21,6 @@
3521
try:
3622
from executorch.extension.pybindings.portable_lib import (
3723
_load_bundled_program_from_buffer,
38-
_load_for_executorch_from_buffer,
39-
_load_for_executorch_from_bundled_program,
4024
)
4125

4226
kernel_mode = "lean"
@@ -47,8 +31,6 @@
4731
try:
4832
from executorch.extension.pybindings.aten_lib import ( # @manual=//executorch/extension/pybindings:aten_lib
4933
_load_bundled_program_from_buffer,
50-
_load_for_executorch_from_buffer,
51-
_load_for_executorch_from_bundled_program,
5234
)
5335

5436
assert kernel_mode is None
@@ -75,19 +57,8 @@ def test_sample_model_e2e(self):
7557
bundled_program_buffer
7658
)
7759

78-
executorch_module = _load_for_executorch_from_bundled_program(
79-
executorch_bundled_program
80-
)
81-
8260
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,
61+
executorch_bundled_program.verify_result_with_bundled_expected_output(
9162
method_name,
9263
0,
9364
)

extension/module/targets.bzl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,14 @@ def define_common_targets():
5353
"//executorch/extension/module:module" + aten_suffix,
5454
],
5555
)
56+
57+
runtime.cxx_library(
58+
name = "module_wrapper" + aten_suffix,
59+
visibility = [
60+
"@EXECUTORCH_CLIENTS",
61+
],
62+
exported_deps = [
63+
"//executorch/extension/module:module" + aten_suffix,
64+
"//executorch/extension/module:bundled_module" + aten_suffix,
65+
],
66+
)

extension/pybindings/README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ CMAKE_ARGS="-DEXECUTORCH_BUILD_MPS=ON" ./install_executorch.sh
2727
- `_reset_profile_results()`: Reset profile results.
2828
## Classes
2929
### ExecuTorchModule
30-
- `load_bundled_input()`: Load bundled input.
31-
- `verify_result_with_bundled_expected_output(bundle: str, method_name: str, testset_idx: int, rtol: float = 1e-5, atol: float = 1e-8)`: Verify result with bundled expected output.
3230
- `plan_execute()`: Plan and execute.
3331
- `run_method()`: Run method.
3432
- `forward()`: Forward. This takes a pytree-flattend PyTorch-tensor-based input.
@@ -37,5 +35,6 @@ CMAKE_ARGS="-DEXECUTORCH_BUILD_MPS=ON" ./install_executorch.sh
3735
- `__call__()`: Call method.
3836
### BundledModule
3937
This class is currently empty and serves as a placeholder for future methods and attributes.
38+
- `verify_result_with_bundled_expected_output(method_name: str, testset_idx: int, rtol: float = 1e-5, atol: float = 1e-8)`: Verify result with bundled expected output.
4039
## Note
4140
All functions and methods are guarded by a call guard that redirects `cout` and `cerr` to the Python environment.

extension/pybindings/pybindings.cpp

Lines changed: 81 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include <executorch/extension/data_loader/buffer_data_loader.h>
2424
#include <executorch/extension/data_loader/mmap_data_loader.h>
2525
#include <executorch/extension/memory_allocator/malloc_memory_allocator.h>
26+
#include <executorch/extension/module/bundled_module.h>
2627
#include <executorch/extension/threadpool/threadpool.h>
2728
#include <executorch/runtime/backend/interface.h>
2829
#include <executorch/runtime/core/data_loader.h>
@@ -425,13 +426,54 @@ inline std::unique_ptr<Module> load_module_from_file(
425426
program_verification);
426427
}
427428

429+
inline py::list get_outputs_as_py_list(
430+
const std::vector<EValue>& outputs,
431+
bool clone_outputs = true) {
432+
const auto outputs_size = outputs.size();
433+
py::list list(outputs_size);
434+
for (size_t i = 0; i < outputs_size; ++i) {
435+
auto& v = outputs[i];
436+
if (Tag::None == v.tag) {
437+
list[i] = py::none();
438+
} else if (Tag::Int == v.tag) {
439+
list[i] = py::cast(v.toInt());
440+
} else if (Tag::Double == v.tag) {
441+
list[i] = py::cast(v.toDouble());
442+
} else if (Tag::Bool == v.tag) {
443+
list[i] = py::cast(v.toBool());
444+
} else if (Tag::String == v.tag) {
445+
list[i] = py::cast(std::string(v.toString().data()));
446+
} else if (Tag::Tensor == v.tag) {
447+
#ifdef USE_ATEN_LIB
448+
// Clone so the outputs in python do not share a lifetime with the
449+
// module object
450+
if (clone_outputs) {
451+
list[i] = py::cast(v.toTensor().clone());
452+
} else {
453+
list[i] = py::cast(v.toTensor());
454+
}
455+
#else
456+
if (clone_outputs) {
457+
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()).clone());
458+
} else {
459+
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()));
460+
}
461+
#endif
462+
} else {
463+
ET_ASSERT_UNREACHABLE_MSG("Invalid model output type");
464+
}
465+
}
466+
return list;
467+
}
468+
428469
static constexpr size_t kDEFAULT_BUNDLED_INPUT_POOL_SIZE = 16 * 1024U;
429470

430-
struct PyBundledModule final {
471+
struct PyBundledModule : public BundledModule {
431472
explicit PyBundledModule(
432473
const py::bytes& buffer,
433474
uint32_t bundled_input_pool_size)
434-
: bundled_program_ptr_(buffer),
475+
: BundledModule(buffer.cast<std::string_view>().data()),
476+
bundled_program_ptr_(buffer),
435477
program_ptr_(static_cast<const void*>(
436478
bundled_program_flatbuffer::GetBundledProgram(
437479
get_bundled_program_ptr())
@@ -460,6 +502,33 @@ struct PyBundledModule final {
460502
return program_len_;
461503
}
462504

505+
py::list verify_result_with_bundled_expected_output(
506+
const std::string& method_name,
507+
size_t testset_idx,
508+
double rtol = 1e-5,
509+
double atol = 1e-8) {
510+
// Execute the method
511+
auto result = BundledModule::execute(method_name, testset_idx);
512+
if (!result.ok()) {
513+
THROW_IF_ERROR(
514+
result.error(),
515+
"Method execution failed with status 0x%" PRIx32,
516+
static_cast<uint32_t>(result.error()));
517+
}
518+
519+
// Convert outputs to py::list
520+
const auto& outputs = result.get();
521+
py::list py_outputs = get_outputs_as_py_list(outputs);
522+
523+
Error status = BundledModule::verify_method_outputs(
524+
method_name, testset_idx, rtol, atol);
525+
THROW_IF_ERROR(
526+
status,
527+
"Result verification failed with status %" PRIu32,
528+
static_cast<uint32_t>(status));
529+
return py_outputs;
530+
}
531+
463532
private:
464533
// Store the bytes object instead of a raw pointer so that this module will
465534
// keep the bytes alive.
@@ -816,43 +885,6 @@ struct PyModule final {
816885
}
817886
}
818887

819-
void load_bundled_input(
820-
PyBundledModule& m,
821-
const std::string method_name,
822-
size_t testset_idx) {
823-
const void* bundled_program_ptr = m.get_bundled_program_ptr();
824-
Error status = executorch::BUNDLED_PROGRAM_NAMESPACE::load_bundled_input(
825-
module_->get_method(method_name), bundled_program_ptr, testset_idx);
826-
THROW_IF_ERROR(
827-
status,
828-
"load_bundled_input failed with status 0x%" PRIx32,
829-
static_cast<uint32_t>(status));
830-
}
831-
832-
py::list verify_result_with_bundled_expected_output(
833-
PyBundledModule& m,
834-
const std::string method_name,
835-
size_t testset_idx,
836-
double rtol = 1e-5,
837-
double atol = 1e-8) {
838-
const void* bundled_program_ptr = m.get_bundled_program_ptr();
839-
auto& method = module_->get_method(method_name);
840-
Error status = executorch::BUNDLED_PROGRAM_NAMESPACE::load_bundled_input(
841-
method, bundled_program_ptr, testset_idx);
842-
THROW_IF_ERROR(
843-
status,
844-
"load_bundled_input failed with status 0x%" PRIx32,
845-
static_cast<uint32_t>(status));
846-
py::list outputs = plan_execute(method_name);
847-
status = executorch::BUNDLED_PROGRAM_NAMESPACE::verify_method_outputs(
848-
method, bundled_program_ptr, testset_idx, rtol, atol);
849-
THROW_IF_ERROR(
850-
status,
851-
"Result verification failed with status %" PRIu32,
852-
static_cast<uint32_t>(status));
853-
return outputs;
854-
}
855-
856888
py::list plan_execute(
857889
const std::string method_name,
858890
bool clone_outputs = true) {
@@ -875,46 +907,6 @@ struct PyModule final {
875907
return get_outputs_as_py_list(outputs, clone_outputs);
876908
}
877909

878-
py::list get_outputs_as_py_list(
879-
const std::vector<EValue>& outputs,
880-
bool clone_outputs = true) {
881-
const auto outputs_size = outputs.size();
882-
py::list list(outputs_size);
883-
for (size_t i = 0; i < outputs_size; ++i) {
884-
auto& v = outputs[i];
885-
if (Tag::None == v.tag) {
886-
list[i] = py::none();
887-
} else if (Tag::Int == v.tag) {
888-
list[i] = py::cast(v.toInt());
889-
} else if (Tag::Double == v.tag) {
890-
list[i] = py::cast(v.toDouble());
891-
} else if (Tag::Bool == v.tag) {
892-
list[i] = py::cast(v.toBool());
893-
} else if (Tag::String == v.tag) {
894-
list[i] = py::cast(std::string(v.toString().data()));
895-
} else if (Tag::Tensor == v.tag) {
896-
#ifdef USE_ATEN_LIB
897-
// Clone so the outputs in python do not share a lifetime with the
898-
// module object
899-
if (clone_outputs) {
900-
list[i] = py::cast(v.toTensor().clone());
901-
} else {
902-
list[i] = py::cast(v.toTensor());
903-
}
904-
#else
905-
if (clone_outputs) {
906-
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()).clone());
907-
} else {
908-
list[i] = py::cast(alias_attensor_to_etensor(v.toTensor()));
909-
}
910-
#endif
911-
} else {
912-
ET_ASSERT_UNREACHABLE_MSG("Invalid model output type");
913-
}
914-
}
915-
return list;
916-
}
917-
918910
std::unique_ptr<PyMethodMeta> method_meta(const std::string method_name) {
919911
auto& method = module_->get_method(method_name);
920912
return std::make_unique<PyMethodMeta>(module_, method.method_meta());
@@ -1074,16 +1066,6 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
10741066
call_guard);
10751067

10761068
py::class_<PyModule>(m, "ExecuTorchModule")
1077-
.def("load_bundled_input", &PyModule::load_bundled_input, call_guard)
1078-
.def(
1079-
"verify_result_with_bundled_expected_output",
1080-
&PyModule::verify_result_with_bundled_expected_output,
1081-
py::arg("bundle"),
1082-
py::arg("method_name"),
1083-
py::arg("testset_idx"),
1084-
py::arg("rtol") = 1e-5,
1085-
py::arg("atol") = 1e-8,
1086-
call_guard)
10871069
.def(
10881070
"plan_execute",
10891071
&PyModule::plan_execute,
@@ -1129,7 +1111,16 @@ PYBIND11_MODULE(EXECUTORCH_PYTHON_MODULE_NAME, m) {
11291111
py::arg("clone_outputs") = true,
11301112
call_guard);
11311113

1132-
py::class_<PyBundledModule>(m, "BundledModule");
1114+
py::class_<PyBundledModule>(m, "BundledModule")
1115+
.def(
1116+
"verify_result_with_bundled_expected_output",
1117+
&PyBundledModule::verify_result_with_bundled_expected_output,
1118+
py::arg("method_name"),
1119+
py::arg("testset_idx"),
1120+
py::arg("rtol") = 1e-5,
1121+
py::arg("atol") = 1e-8,
1122+
call_guard);
1123+
11331124
py::class_<PyTensorInfo>(m, "TensorInfo")
11341125
.def("sizes", &PyTensorInfo::sizes, call_guard)
11351126
.def("dtype", &PyTensorInfo::dtype, call_guard)

shim_et/xplat/executorch/extension/pybindings/pybindings.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ PORTABLE_MODULE_DEPS = [
1616
"//executorch/extension/data_loader:buffer_data_loader",
1717
"//executorch/extension/data_loader:mmap_data_loader",
1818
"//executorch/extension/memory_allocator:malloc_memory_allocator",
19+
"//executorch/extension/module:module_wrapper",
1920
"//executorch/runtime/executor/test:test_backend_compiler_lib",
2021
"//executorch/devtools/etdump:etdump_flatcc",
2122
] + get_all_cpu_backend_targets()
@@ -28,6 +29,7 @@ ATEN_MODULE_DEPS = [
2829
"//executorch/extension/data_loader:buffer_data_loader",
2930
"//executorch/extension/data_loader:mmap_data_loader",
3031
"//executorch/extension/memory_allocator:malloc_memory_allocator",
32+
"//executorch/extension/module:module_wrapper_aten",
3133
"//executorch/devtools/bundled_program:runtime_aten",
3234
"//executorch/runtime/executor/test:test_backend_compiler_lib_aten",
3335
"//executorch/devtools/etdump:etdump_flatcc",

0 commit comments

Comments
 (0)