Skip to content

Commit 36af221

Browse files
Initial commit of libshortfin. (#119)
This is initial work to create a high level C++ runtime library for serving (with Python bindings). It incorporates learnings from multiple priors (IREE Python Runtime, various samples, Turbine runtime, PJRT runtime) and has several design goals: * High level C++ API with a Python API that is almost a direct mirror. * Opinionated concurrency and threading model (i.e. can be used free-threaded but designed to work with controlled isolate like thread loops). * Explicit system setup which can support a variety of bespoke configurations. * Safe/low-code APIs for common operations with escape hatches for esoteric/advanced usage. * Robust multi-device (both heterogenous and homegenous) from the get-go from a single process. * Designed to use free-threaded (gil-lis) Python for high concurrency situations. * Minimal deps (i.e. can interop with things like torch, numpy, etc but has enough bolts to build a complete solution without them). * Compatibility and preference for async/await style programming (with direct support for C++ co-routines and Python asyncio). * Opinionated representation of the local topology, especially focused on managing execution on directly bus-connected devices (while being scalable to remote use as an enhancement). --------- Signed-off-by: Stella Laurenzo <[email protected]>
1 parent 8046365 commit 36af221

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3979
-1
lines changed

.pre-commit-config.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,7 @@ repos:
1212
rev: 22.10.0
1313
hooks:
1414
- id: black
15+
- repo: https://github.com/pre-commit/mirrors-clang-format
16+
rev: 'v18.1.4'
17+
hooks:
18+
- id: clang-format

libshortfin/.clang-format

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright 2019 The IREE Authors
2+
#
3+
# Licensed under the Apache License v2.0 with LLVM Exceptions.
4+
# See https://llvm.org/LICENSE.txt for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
7+
# Follow IREE runtime style, based on Google.
8+
BasedOnStyle: Google

libshortfin/CMakeLists.txt

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2024 Advanced Micro Devices, Inc
2+
#
3+
# Licensed under the Apache License v2.0 with LLVM Exceptions. See
4+
# https://llvm.org/LICENSE.txt for license information. SPDX-License-Identifier:
5+
# Apache-2.0 WITH LLVM-exception
6+
7+
cmake_minimum_required(VERSION 3.28)
8+
9+
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR)
10+
message(
11+
FATAL_ERROR
12+
"Do not build in-source. Please remove CMakeCache.txt and the CMakeFiles/ directory. Then build out-of-source."
13+
)
14+
endif()
15+
16+
project(
17+
"libshortfin"
18+
VERSION 0.9
19+
LANGUAGES C CXX)
20+
21+
set(CMAKE_C_STANDARD 11)
22+
set(CMAKE_CXX_STANDARD 20)
23+
# https://discourse.cmake.org/t/cmake-3-28-cmake-cxx-compiler-clang-scan-deps-notfound-not-found/9244/3
24+
set(CMAKE_CXX_SCAN_FOR_MODULES 0)
25+
26+
option(SHORTFIN_BUILD_PYTHON_BINDINGS "Builds Python Bindings" OFF)
27+
28+
# Includes.
29+
list(APPEND CMAKE_MODULE_PATH
30+
${CMAKE_CURRENT_LIST_DIR}/build_tools/cmake/
31+
)
32+
include(shortfin_library)
33+
34+
# Dependencies.
35+
find_package(spdlog)
36+
find_package(xtensor)
37+
find_package(IREERuntime)
38+
39+
add_subdirectory(src)
40+
41+
if(SHORTFIN_BUILD_PYTHON_BINDINGS)
42+
find_package(Python 3.8 COMPONENTS Interpreter Development.Module REQUIRED)
43+
add_subdirectory(bindings/python)
44+
set(SHORTFIN_PYTHON_CPP_PREBUILT "TRUE") # See setup.py.
45+
configure_file(setup.py setup.py @ONLY)
46+
configure_file(pyproject.toml pyproject.toml COPYONLY)
47+
endif()

libshortfin/README.md

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# libshortfin - SHARK C++ inference library
2+
3+
## Dev Builds
4+
5+
Library dependencies:
6+
7+
* [spdlog](https://github.com/gabime/spdlog)
8+
* [xtensor](https://github.com/xtensor-stack/xtensor)
9+
* [iree runtime](https://github.com/iree-org/iree)
10+
11+
On recent Ubuntu, the primary dependencies can be satisfied via:
12+
13+
```
14+
apt install libspdlog-dev libxtensor-dev
15+
```
16+
17+
CMake must be told how to find the IREE runtime, either from a distribution
18+
tarball, or local build/install dir. For a local build directory, pass:
19+
20+
```
21+
# Assumes that the iree-build directory is adjacent to this repo.
22+
-DCMAKE_PREFIX_PATH=$(pwd)/../../iree-build/lib/cmake/IREE
23+
```
24+
25+
One liner recommended CMake command (note that CMAKE_LINKER_TYPE requires
26+
cmake>=3.29):
27+
28+
```
29+
cmake -GNinja -S. -Bbuild \
30+
-DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \
31+
-DCMAKE_LINKER_TYPE=LLD \
32+
-DCMAKE_PREFIX_PATH=$(pwd)/../../iree-build/lib/cmake/IREE
33+
```
34+
35+
## Building Python Bindings
36+
37+
If using a Python based development flow, there are two options:
38+
39+
1. `pip install -v .` to build and install the library (TODO: Not yet implemented).
40+
2. Build with cmake and `-DSHORTFIN_BUILD_PYTHON_BINDINGS=ON` and then
41+
from the `build/` directory, run `pip install -v -e .` to create an
42+
editable install that will update as you build the C++ project.
43+
44+
If predominantly developing with a C++ based flow, the second option is
45+
recommended. Your python install should track any source file changes or
46+
builds without further interaction. Re-installing will be necessary if package
47+
structure changes significantly.
48+
49+
## Running Tests
50+
51+
The project uses a combination of ctest for native C++ tests and pytest. Much
52+
of the functionality is only tested via the Python tests, using the
53+
`_shortfin.lib` internal implementation directly. In order to run these tests,
54+
you must have installed the Python package as per the above steps.
55+
56+
Which style of test is used is pragmatic and geared at achieving good test
57+
coverage with a minimum of duplication. Since it is often much more expensive
58+
to build native tests of complicated flows, many things are only tested via
59+
Python. This does not preclude having other language bindings later, but it
60+
does mean that the C++ core of the library must always be built with the
61+
Python bindings to test the most behavior. Given the target of the project,
62+
this is not considered to be a significant issue.
63+
64+
# Production Library Building
65+
66+
In order to build a production library, additional build steps are typically
67+
recommended:
68+
69+
* Compile all deps with the same compiler/linker for LTO compatibility
70+
* Provide library dependencies manually and compile them with LTO
71+
* Compile dependencies with `-fvisibility=hidden`
72+
* Enable LTO builds of libshortfin
73+
* Set flags to enable symbol versioning
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Copyright 2024 Advanced Micro Devices, Inc
2+
#
3+
# Licensed under the Apache License v2.0 with LLVM Exceptions. See
4+
# https://llvm.org/LICENSE.txt for license information. SPDX-License-Identifier:
5+
# Apache-2.0 WITH LLVM-exception
6+
7+
# libshortfin publishes multiple python packages: - _shortfin: Trampoline
8+
# __init__.py which looks at environment variables to load an appropriate native
9+
# library. - _shortfin_default.lib: Native library as a default, uninstrumented
10+
# build. - _shortfin_tracing.lib: Native library with tracing enabled (TODO). -
11+
# Others.
12+
13+
# Detect the installed nanobind package and import it into CMake
14+
execute_process(
15+
COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir
16+
OUTPUT_STRIP_TRAILING_WHITESPACE
17+
OUTPUT_VARIABLE nanobind_ROOT)
18+
find_package(nanobind CONFIG REQUIRED)
19+
20+
nanobind_add_module(shortfin_python_extension NB_STATIC LTO
21+
array_binding.cc
22+
lib_ext.cc
23+
)
24+
25+
set_target_properties(shortfin_python_extension
26+
PROPERTIES OUTPUT_NAME "_shortfin_default/lib")
27+
28+
target_link_libraries(shortfin_python_extension
29+
# TODO: This should be configurable as to whether we link to the static
30+
# or dynamic version.
31+
PRIVATE shortfin
32+
)
33+
34+
nanobind_add_stub(
35+
shortfin_python_extension_stub
36+
MODULE _shortfin_default.lib
37+
OUTPUT _shortfin_default/lib.pyi
38+
DEPENDS shortfin_python_extension
39+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Copyright 2024 Advanced Micro Devices, Inc
2+
#
3+
# Licensed under the Apache License v2.0 with LLVM Exceptions.
4+
# See https://llvm.org/LICENSE.txt for license information.
5+
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
7+
# The proper way to import this package is via:
8+
# from _shortfin import lib as sfl
9+
10+
# TODO: Use environment variables to determine which built variant to
11+
# import.
12+
from _shortfin_default import lib
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Copyright 2024 Advanced Micro Devices, Inc
2+
//
3+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
7+
#include "./lib_ext.h"
8+
#include "./utils.h"
9+
#include "shortfin/array/api.h"
10+
11+
using namespace shortfin::array;
12+
13+
namespace shortfin::python {
14+
15+
void BindArray(py::module_ &m) {
16+
py::class_<DType>(m, "DType")
17+
.def_prop_ro("is_boolean", &DType::is_boolean)
18+
.def_prop_ro("is_integer", &DType::is_integer)
19+
.def_prop_ro("is_float", &DType::is_float)
20+
.def_prop_ro("is_complex", &DType::is_complex)
21+
.def_prop_ro("bit_count", &DType::bit_count)
22+
.def_prop_ro("is_byte_aligned", &DType::is_byte_aligned)
23+
.def_prop_ro("dense_byte_count", &DType::dense_byte_count)
24+
.def("is_integer_bitwidth", &DType::is_integer_bitwidth)
25+
.def(py::self == py::self)
26+
.def("__repr__", &DType::name);
27+
28+
m.attr("opaque8") = DType::opaque8();
29+
m.attr("opaque16") = DType::opaque16();
30+
m.attr("opaque32") = DType::opaque32();
31+
m.attr("opaque64") = DType::opaque64();
32+
m.attr("bool8") = DType::bool8();
33+
m.attr("int4") = DType::int4();
34+
m.attr("sint4") = DType::sint4();
35+
m.attr("uint4") = DType::uint4();
36+
m.attr("int8") = DType::int8();
37+
m.attr("sint8") = DType::sint8();
38+
m.attr("uint8") = DType::uint8();
39+
m.attr("int16") = DType::int16();
40+
m.attr("sint16") = DType::sint16();
41+
m.attr("uint16") = DType::uint16();
42+
m.attr("int32") = DType::int32();
43+
m.attr("sint32") = DType::sint32();
44+
m.attr("uint32") = DType::uint32();
45+
m.attr("int64") = DType::int64();
46+
m.attr("sint64") = DType::sint64();
47+
m.attr("uint64") = DType::uint64();
48+
m.attr("float16") = DType::float16();
49+
m.attr("float32") = DType::float32();
50+
m.attr("float64") = DType::float64();
51+
m.attr("bfloat16") = DType::bfloat16();
52+
m.attr("complex64") = DType::complex64();
53+
m.attr("complex128") = DType::complex128();
54+
55+
py::class_<storage>(m, "storage")
56+
.def_static(
57+
"allocate_host",
58+
[](local::ScopedDevice &device, iree_device_size_t allocation_size) {
59+
return storage::AllocateHost(device, allocation_size);
60+
},
61+
py::arg("device"), py::arg("allocation_size"), py::keep_alive<0, 1>())
62+
.def_static(
63+
"allocate_device",
64+
[](local::ScopedDevice &device, iree_device_size_t allocation_size) {
65+
return storage::AllocateDevice(device, allocation_size);
66+
},
67+
py::arg("device"), py::arg("allocation_size"), py::keep_alive<0, 1>())
68+
.def("fill",
69+
[](storage &self, py::handle buffer) {
70+
Py_buffer py_view;
71+
int flags = PyBUF_FORMAT | PyBUF_ND; // C-Contiguous ND.
72+
if (PyObject_GetBuffer(buffer.ptr(), &py_view, flags) != 0) {
73+
throw py::python_error();
74+
}
75+
PyBufferReleaser py_view_releaser(py_view);
76+
self.Fill(py_view.buf, py_view.len);
77+
})
78+
.def("__repr__", &storage::to_s);
79+
80+
py::class_<base_array>(m, "base_array")
81+
.def_prop_ro("dtype", &base_array::dtype)
82+
.def_prop_ro("shape", &base_array::shape);
83+
py::class_<device_array, base_array>(m, "device_array")
84+
.def("__init__", [](py::args, py::kwargs) {})
85+
.def_static("__new__",
86+
[](py::handle py_type, class storage storage,
87+
std::span<const size_t> shape, DType dtype) {
88+
return custom_new_keep_alive<device_array>(
89+
py_type, /*keep_alive=*/storage.scope(), storage, shape,
90+
dtype);
91+
})
92+
.def_static("__new__",
93+
[](py::handle py_type, local::ScopedDevice &device,
94+
std::span<const size_t> shape, DType dtype) {
95+
return custom_new_keep_alive<device_array>(
96+
py_type, /*keep_alive=*/device.scope(),
97+
device_array::allocate(device, shape, dtype));
98+
})
99+
.def_prop_ro("device", &device_array::device,
100+
py::rv_policy::reference_internal)
101+
.def_prop_ro("storage", &device_array::storage,
102+
py::rv_policy::reference_internal)
103+
.def("__repr__", &device_array::to_s);
104+
py::class_<host_array, base_array>(m, "host_array")
105+
.def("__init__", [](py::args, py::kwargs) {})
106+
.def_static("__new__",
107+
[](py::handle py_type, class storage storage,
108+
std::span<const size_t> shape, DType dtype) {
109+
return custom_new_keep_alive<host_array>(
110+
py_type, /*keep_alive=*/storage.scope(), storage, shape,
111+
dtype);
112+
})
113+
.def_static("__new__",
114+
[](py::handle py_type, local::ScopedDevice &device,
115+
std::span<const size_t> shape, DType dtype) {
116+
return custom_new_keep_alive<host_array>(
117+
py_type, /*keep_alive=*/device.scope(),
118+
host_array::allocate(device, shape, dtype));
119+
})
120+
.def_static("__new__",
121+
[](py::handle py_type, device_array &device_array) {
122+
return custom_new_keep_alive<host_array>(
123+
py_type, /*keep_alive=*/device_array.device().scope(),
124+
host_array::for_transfer(device_array));
125+
})
126+
.def_prop_ro("device", &host_array::device,
127+
py::rv_policy::reference_internal)
128+
.def_prop_ro("storage", &host_array::storage,
129+
py::rv_policy::reference_internal)
130+
.def("__repr__", &host_array::to_s);
131+
}
132+
133+
} // namespace shortfin::python

0 commit comments

Comments
 (0)