Skip to content

Commit 176bca9

Browse files
d4l3kfacebook-github-bot
authored andcommitted
multipy: beginnings of pybind support (#277)
Summary: This adds pybindings for multipy/runtime that are available at `multipy.runtime`. This creates a new `multipy_pybind.so` file in build/ with a bit of glue code from Python to load it correctly. Once landed this should be available in all normal deploy installs This requires a few tricks: * adds `-fPIC` to the multipy library so it can be included from a shared library * modify custom loader to load symbols from the current library first (equivalent to RTLD_DEEPBIND) so local python symbols are preferred over the global python * explicitly loads `libtorch.so` using RTLD_GLOBAL so libinterpreter.so has the symbols available Current limitations: * Obj must be destroyed/GCed before the InterpreterSession is destroyed. If left up to GC in most cases it will crash the program. * No pickle interface for smartly transferring models between interpreters Other cleanups: * remove gtest from interpreter * remove pytorch submodule dependency Pull Request resolved: #277 Test Plan: Added test_pybind.py to the CI Reviewed By: PaliC Differential Revision: D42006173 Pulled By: d4l3k fbshipit-source-id: 4b1f6e69a33255da173d6736d2cf77a5fa53753c
1 parent 16023e4 commit 176bca9

File tree

13 files changed

+182
-13
lines changed

13 files changed

+182
-13
lines changed

.github/workflows/docs-build.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ jobs:
1717
architecture: x64
1818
- name: Checkout MultiPy
1919
uses: actions/checkout@v2
20+
with:
21+
submodules: true
2022
- name: Install Dependencies
2123
run: |
2224
set -eux

.github/workflows/install_test.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ jobs:
3030

3131
- name: Checkout MultiPy
3232
uses: actions/checkout@v2
33+
with:
34+
submodules: true
3335

3436
- name: Install runtime build dependencies
3537
run: |

.github/workflows/runtime_tests.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,14 @@ jobs:
3232
DOCKER_BUILDKIT: 1
3333
run: nvidia-docker build -t multipy --progress=plain --build-arg PYTHON_3_MINOR_VERSION=${{ matrix.python3-minor-version }} --build-arg BUILD_CUDA_TESTS=1 .
3434

35-
- name: Test
35+
- name: C++ Tests
3636
run: |
3737
docker run --rm multipy bash -c "if [[ ${{ matrix.python3-minor-version }} -lt 8 ]]; then source ~/venvs/multipy/bin/activate; fi && multipy/runtime/build/test_deploy"
3838
nvidia-docker run --rm multipy bash -c "if [[ ${{ matrix.python3-minor-version }} -lt 8 ]]; then source ~/venvs/multipy/bin/activate; fi && multipy/runtime/build/test_deploy_gpu"
3939
40+
- name: Pybind Tests
41+
run: |
42+
docker run --rm multipy bash -c "if [[ ${{ matrix.python3-minor-version }} -lt 8 ]]; then source ~/venvs/multipy/bin/activate; fi && python multipy/runtime/test_pybind.py"
4043
4144
- name: Examples
4245
run: |

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,3 @@
44
[submodule "multipy/runtime/third-party/pybind11"]
55
path = multipy/runtime/third-party/pybind11
66
url = https://github.com/pybind/pybind11.git
7-
[submodule "multipy/runtime/third-party/pytorch"]
8-
path = multipy/runtime/third-party/pytorch
9-
url = https://github.com/pytorch/pytorch.git

multipy/runtime/CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ message(STATUS "CMAKE_BUILD_TYPE - ${CMAKE_BUILD_TYPE}" )
2121
set(DEPLOY_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
2222
get_filename_component(MULTIPY_DIR ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
2323

24-
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fabi-version=11 -fno-lto")
24+
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fabi-version=11 -fno-lto -fPIC")
2525
include(${DEPLOY_DIR}/utils.cmake)
2626

2727
add_subdirectory(interpreter)
@@ -171,3 +171,10 @@ install (
171171

172172
install(TARGETS torch_deploy DESTINATION dist/multipy/runtime/lib)
173173
install(TARGETS torch_deployinterpreter DESTINATION dist/multipy/runtime/lib)
174+
175+
add_subdirectory(third-party/pybind11)
176+
pybind11_add_module(multipy_pybind pybind_init.cpp)
177+
target_include_directories(multipy_pybind PRIVATE ${CMAKE_SOURCE_DIR}/../..)
178+
target_link_libraries(multipy_pybind
179+
PUBLIC "-Wl,--no-as-needed -rdynamic" dl torch_deploy_interface c10 torch_cpu torch_python
180+
)

multipy/runtime/__init__.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Meta Platforms, Inc. and affiliates.
3+
# All rights reserved.
4+
#
5+
# This source code is licensed under the BSD-style license found in the
6+
# LICENSE file in the root directory of this source tree.
7+
8+
"""
9+
This contains a pybinded interface to MultiPy's subinterpreters. This enables
10+
running child interpreters from an existing python process without having any
11+
shared GIL.
12+
13+
WARNING: This is currently a prototype and is subject to change at any point.
14+
15+
Current limitations:
16+
* Obj must be destroyed/GCed before the InterpreterSession is destroyed. If left
17+
up to GC in most cases it will crash the program.
18+
* No pickle interface for smartly transferring models between interpreters
19+
20+
See test_pybind.py for examples on how to use.
21+
"""
22+
23+
import ctypes
24+
import os
25+
import os.path
26+
27+
import torch
28+
29+
# We need to load libtorch into the global symbol space so the subinterpreters
30+
# can use it.
31+
torch_dir = os.path.dirname(torch.__file__)
32+
libtorch_path = os.path.join(torch_dir, "lib", "libtorch.so")
33+
ctypes.CDLL(libtorch_path, mode=ctypes.RTLD_GLOBAL)
34+
35+
from multipy.runtime.build.multipy_pybind import InterpreterManager # noqa: F401

multipy/runtime/elf_file.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,7 @@ inline bool exists(const char* name) {
7474

7575
multipy::optional<Section> searchForSection(const char* name) {
7676
{
77-
std::string execPath;
78-
std::ifstream("/proc/self/cmdline") >> execPath;
79-
ElfFile elfFile(execPath.c_str());
77+
ElfFile elfFile("/proc/self/exe");
8078
auto section = elfFile.findSection(name);
8179
if (section) {
8280
return section;

multipy/runtime/interpreter/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ set(INTERPRETER_LIB_SOURCES
6060
${INTERPRETER_DIR}/builtin_registry.cpp
6161
${INTERPRETER_DIR}/import_find_sharedfuncptr.cpp
6262
${INTERPRETER_DIR}/plugin_registry.cpp
63+
${INTERPRETER_DIR}/../loader.cpp
6364
${LINKER_SCRIPT}
6465
)
6566
add_library(torch_deployinterpreter SHARED ${INTERPRETER_LIB_SOURCES} ${LINKER_SCRIPT})
@@ -80,5 +81,4 @@ target_include_directories(torch_deployinterpreter BEFORE PUBLIC ${Python3_INCLU
8081

8182
target_link_libraries(torch_deployinterpreter PRIVATE fmt::fmt-header-only)
8283
target_link_libraries(torch_deployinterpreter PRIVATE torch_python)
83-
target_link_libraries(torch_deployinterpreter PRIVATE gtest)
8484
target_link_libraries(torch_deployinterpreter PRIVATE multipy_torch)

multipy/runtime/loader.cpp

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1024,16 +1024,17 @@ struct __attribute__((visibility("hidden"))) CustomLibraryImpl
10241024
}
10251025
}
10261026

1027+
// search in this binary first -- equivalent to RTLD_DEEPBIND behavior
1028+
auto r = sym(sym_name, version);
1029+
if (r) {
1030+
return r;
1031+
}
10271032
for (const auto& sys_lib : symbol_search_path_) {
10281033
auto r = sys_lib->sym(sym_name, version);
10291034
if (r) {
10301035
return r;
10311036
}
10321037
}
1033-
auto r = sym(sym_name, version);
1034-
if (r) {
1035-
return r;
1036-
}
10371038
if (ELF64_ST_BIND(sym_st.st_info) != STB_WEAK) {
10381039
if (!version) {
10391040
version = "nullptr";

multipy/runtime/pybind_init.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include <multipy/runtime/deploy.h>
2+
#include <pybind11/pybind11.h>
3+
#include <torch/csrc/jit/python/pybind_utils.h>
4+
#include <torch/csrc/lazy/core/debug_util.h>
5+
6+
namespace py = pybind11;
7+
8+
using namespace torch::deploy;
9+
10+
namespace {
11+
at::IValue detachIValue(at::IValue&& iv) {
12+
if (iv.isTensor()) {
13+
// detach tensors to avoid cross interpreter autograd state
14+
return std::move(iv).toTensor().detach();
15+
}
16+
return iv;
17+
}
18+
at::IValue toIValue(const py::handle& obj) {
19+
return detachIValue(torch::jit::toTypeInferredIValue(obj));
20+
}
21+
} // namespace
22+
23+
PYBIND11_MODULE(multipy_pybind, m) {
24+
m.doc() = "multipy python bindings";
25+
26+
py::class_<InterpreterManager>(m, "InterpreterManager")
27+
.def(py::init<size_t>())
28+
.def("acquire_one", &InterpreterManager::acquireOne)
29+
.def(
30+
"__len__",
31+
[](InterpreterManager& self) -> int {
32+
return self.allInstances().size();
33+
})
34+
.def(
35+
"__getitem__",
36+
[](InterpreterManager& self, int i) -> InterpreterSession {
37+
return self.allInstances().at(i).acquireSession();
38+
});
39+
40+
py::class_<Interpreter>(m, "Interpreter")
41+
.def("acquire_session", &Interpreter::acquireSession);
42+
43+
py::class_<InterpreterSession>(m, "InterpreterSession")
44+
.def("global_", &InterpreterSession::global);
45+
46+
py::class_<Obj>(m, "Obj")
47+
.def(
48+
"__call__",
49+
[](Obj& self, py::args args, const py::kwargs& kwargs) -> Obj {
50+
std::vector<at::IValue> iargs;
51+
std::unordered_map<std::string, at::IValue> ikwargs;
52+
53+
for (auto& arg : args) {
54+
iargs.emplace_back(toIValue(arg));
55+
}
56+
for (auto& arg : kwargs) {
57+
ikwargs.emplace(
58+
arg.first.cast<std::string>(), toIValue(arg.second));
59+
}
60+
61+
return self.callKwargs(iargs, ikwargs);
62+
})
63+
.def(
64+
"__getattr__",
65+
[](Obj& self, std::string attr) -> Obj {
66+
return self.attr(attr.c_str());
67+
})
68+
.def("deref", [](Obj& self) -> py::object {
69+
return ::torch::jit::toPyObject(detachIValue(self.toIValue()));
70+
});
71+
}

0 commit comments

Comments
 (0)