Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python binding of Triton server C API #265

Merged
merged 10 commits into from
Sep 19, 2023
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -415,3 +415,7 @@ export(
)

export(PACKAGE TritonCore)

if(NOT TRITON_CORE_HEADERS_ONLY)
add_subdirectory(python python)
endif()
69 changes: 69 additions & 0 deletions python/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Copyright 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of NVIDIA CORPORATION nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
cmake_minimum_required(VERSION 3.18)

add_subdirectory(tritonserver)

file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/TRITON_VERSION ${TRITON_VERSION})
configure_file(../LICENSE LICENSE.txt COPYONLY)
configure_file(setup.py setup.py @ONLY)
file(COPY test/test_binding.py DESTINATION ./test/.)

set(WHEEL_DEPENDS
${CMAKE_CURRENT_BINARY_DIR}/TRITON_VERSION
${CMAKE_CURRENT_BINARY_DIR}/LICENSE.txt
${CMAKE_CURRENT_BINARY_DIR}/setup.py
${CMAKE_CURRENT_BINARY_DIR}/tritonserver
python-bindings
)

set(wheel_stamp_file "stamp.whl")

add_custom_command(
OUTPUT "${wheel_stamp_file}"
COMMAND python3
ARGS
"${CMAKE_CURRENT_SOURCE_DIR}/build_wheel.py"
--dest-dir "${CMAKE_CURRENT_BINARY_DIR}/generic"
--binding-path $<TARGET_FILE:python-bindings>
DEPENDS ${WHEEL_DEPENDS}
)

add_custom_target(
generic-server-wheel ALL
DEPENDS
"${wheel_stamp_file}"
)

install(
CODE "file(GLOB _Wheel \"${CMAKE_CURRENT_BINARY_DIR}/generic/triton*.whl\")"
CODE "file(INSTALL \${_Wheel} DESTINATION \"${CMAKE_INSTALL_PREFIX}/python\")"
)

# Test
install(
CODE "file(INSTALL ${CMAKE_CURRENT_BINARY_DIR}/test/test_binding.py DESTINATION \"${CMAKE_INSTALL_PREFIX}/python\")"
)
124 changes: 124 additions & 0 deletions python/build_wheel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
# Copyright 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of NVIDIA CORPORATION nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import argparse
import os
import pathlib
import re
import shutil
import subprocess
import sys
from distutils.dir_util import copy_tree
from tempfile import mkstemp


def fail_if(p, msg):
if p:
print("error: {}".format(msg), file=sys.stderr)
sys.exit(1)


def mkdir(path):
pathlib.Path(path).mkdir(parents=True, exist_ok=True)


def touch(path):
pathlib.Path(path).touch()


def cpdir(src, dest):
copy_tree(src, dest, preserve_symlinks=1)


def sed(pattern, replace, source, dest=None):
fin = open(source, "r")
if dest:
fout = open(dest, "w")
else:
fd, name = mkstemp()
fout = open(name, "w")

for line in fin:
out = re.sub(pattern, replace, line)
fout.write(out)

fin.close()
fout.close()
if not dest:
shutil.copyfile(name, source)


if __name__ == "__main__":
parser = argparse.ArgumentParser()

parser.add_argument(
"--dest-dir", type=str, required=True, help="Destination directory."
)
parser.add_argument(
"--binding-path", type=str, required=True, help="Path to Triton Python binding."
)

FLAGS = parser.parse_args()

FLAGS.triton_version = None
with open("TRITON_VERSION", "r") as vfile:
FLAGS.triton_version = vfile.readline().strip()

FLAGS.whl_dir = os.path.join(FLAGS.dest_dir, "wheel")

print("=== Building in: {}".format(os.getcwd()))
print("=== Using builddir: {}".format(FLAGS.whl_dir))
print("Adding package files")

mkdir(os.path.join(FLAGS.whl_dir, "tritonserver"))
shutil.copy("tritonserver/__init__.py", os.path.join(FLAGS.whl_dir, "tritonserver"))

cpdir("tritonserver/_c", os.path.join(FLAGS.whl_dir, "tritonserver", "_c"))
PYBIND_LIB = os.path.basename(FLAGS.binding_path)
shutil.copyfile(
FLAGS.binding_path,
os.path.join(FLAGS.whl_dir, "tritonserver", "_c", PYBIND_LIB),
)

shutil.copyfile("LICENSE.txt", os.path.join(FLAGS.whl_dir, "LICENSE.txt"))
shutil.copyfile("setup.py", os.path.join(FLAGS.whl_dir, "setup.py"))

os.chdir(FLAGS.whl_dir)
print("=== Building wheel")
args = ["python3", "setup.py", "bdist_wheel"]

wenv = os.environ.copy()
wenv["VERSION"] = FLAGS.triton_version
wenv["TRITON_PYBIND"] = PYBIND_LIB
p = subprocess.Popen(args, env=wenv)
p.wait()
fail_if(p.returncode != 0, "setup.py failed")

cpdir("dist", FLAGS.dest_dir)

print("=== Output wheel file is in: {}".format(FLAGS.dest_dir))
touch(os.path.join(FLAGS.dest_dir, "stamp.whl"))
98 changes: 98 additions & 0 deletions python/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
# Copyright 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of NVIDIA CORPORATION nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import os
import sys
from itertools import chain

from setuptools import find_packages, setup

if "--plat-name" in sys.argv:
PLATFORM_FLAG = sys.argv[sys.argv.index("--plat-name") + 1]
else:
PLATFORM_FLAG = "any"

if "VERSION" not in os.environ:
raise Exception("envvar VERSION must be specified")

VERSION = os.environ["VERSION"]

try:
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel

class bdist_wheel(_bdist_wheel):
def finalize_options(self):
_bdist_wheel.finalize_options(self)
self.root_is_pure = False

def get_tag(self):
pyver, abi, plat = "py3", "none", PLATFORM_FLAG
return pyver, abi, plat

except ImportError:
bdist_wheel = None

this_directory = os.path.abspath(os.path.dirname(__file__))

data_files = [
("", ["LICENSE.txt"]),
]
platform_package_data = [os.environ["TRITON_PYBIND"]]

setup(
name="tritonserver",
version=VERSION,
author="NVIDIA Inc.",
author_email="[email protected]",
description="Python API of the Triton In-Process Server",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Python bindings for TritonServer C-API"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this describes the Python package tritonserver? Python binding is meant to be lower level and not directly exposed.

license="BSD",
url="https://developer.nvidia.com/nvidia-triton-inference-server",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Intended Audience :: Information Technology",
"Topic :: Scientific/Engineering",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Software Development :: Libraries",
"Topic :: Utilities",
"License :: OSI Approved :: BSD License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Environment :: Console",
"Natural Language :: English",
"Operating System :: OS Independent",
],
packages=find_packages(),
package_data={
"": platform_package_data,
},
zip_safe=False,
cmdclass={"bdist_wheel": bdist_wheel},
data_files=data_files,
)
Loading