diff --git a/.azure/gpu-tests.yml b/.azure/gpu-tests.yml index 4fb390300..b29668710 100644 --- a/.azure/gpu-tests.yml +++ b/.azure/gpu-tests.yml @@ -45,6 +45,10 @@ jobs: echo "##vso[task.setvariable variable=TORCH_URL]https://download.pytorch.org/whl/cu${CUDA_version_mm}/torch_stable.html" displayName: "set Env. vars" + - bash: | + pip install uv + displayName: "Install uv" + - bash: | whoami && id lspci | egrep 'VGA|3D' @@ -53,44 +57,43 @@ jobs: echo $CUDA_VISIBLE_DEVICES echo $TORCH_URL python --version - pip --version - pip cache dir - pip list + uv --version + uv cache dir + uv pip list displayName: "Image info & NVIDIA" - bash: | - pip install . -U --prefer-binary \ - -r ./_requirements/test.txt --find-links=${TORCH_URL} + uv sync --all-extras --dev displayName: "Install environment" - bash: | - pip list + uv pip list python -c "import torch ; mgpu = torch.cuda.device_count() ; assert mgpu >= 2, f'found GPUs: {mgpu}'" displayName: "Sanity check" - bash: | - pip install -q py-tree - py-tree /var/tmp/torch + uv pip install -q py-tree + uv run py-tree /var/tmp/torch displayName: "Show caches" - bash: | - coverage run --source litserve -m pytest src tests -v + uv run coverage run --source litserve -m pytest src tests -v displayName: "Testing" - bash: | - python -m coverage report - python -m coverage xml - python -m codecov --token=$(CODECOV_TOKEN) --name="GPU-coverage" \ + uv run coverage report + uv run coverage xml + uv run codecov --token=$(CODECOV_TOKEN) --name="GPU-coverage" \ --commit=$(Build.SourceVersion) --flags=gpu,unittest --env=linux,azure ls -l displayName: "Statistics" - bash: | - pip install torch torchvision -U -q --find-links=${TORCH_URL} -r _requirements/perf.txt - export PYTHONPATH=$PWD && python tests/parity_fastapi/main.py + uv pip install torch torchvision -U -q --find-links=${TORCH_URL} + export PYTHONPATH=$PWD && uv run tests/parity_fastapi/main.py displayName: "Run FastAPI parity tests" - bash: | - pip install gpustat wget -U -q + uv pip install gpustat wget -U -q bash tests/perf_test/bert/run_test.sh displayName: "Run GPU perf test" diff --git a/.github/workflows/ci-minimal-dependency-check.yml b/.github/workflows/ci-minimal-dependency-check.yml index b869ad3ba..9db21e1fe 100644 --- a/.github/workflows/ci-minimal-dependency-check.yml +++ b/.github/workflows/ci-minimal-dependency-check.yml @@ -18,16 +18,18 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv and setup python + uses: astral-sh/setup-uv@v6 with: - python-version: "3.11" + activate-environment: true + python-version: "3.12" + enable-cache: true - name: Install LitServe run: | - pip --version - pip install . psutil -U -q - pip list + uv sync --no-dev + uv pip install . psutil -U -q + uv pip list - name: Tests run: python tests/minimal_run.py diff --git a/.github/workflows/ci-parity.yml b/.github/workflows/ci-parity.yml index 0a1ce27f5..a71a845b1 100644 --- a/.github/workflows/ci-parity.yml +++ b/.github/workflows/ci-parity.yml @@ -18,16 +18,19 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python - uses: actions/setup-python@v5 + - name: Install uv and setup python + uses: astral-sh/setup-uv@v6 with: - python-version: "3.11" + activate-environment: true + python-version: "3.12" + enable-cache: true - name: Install LitServe run: | - pip --version - pip install . torchvision jsonargparse uvloop tenacity -U -q -r _requirements/test.txt -U -q - pip list + uv sync --all-extras --dev + uv pip compile pyproject.toml -o requirements.txt + uv pip install torchvision jsonargparse uvloop tenacity -U -q + uv pip list - name: Parity test run: export PYTHONPATH=$PWD && python tests/parity_fastapi/main.py diff --git a/.github/workflows/ci-testing.yml b/.github/workflows/ci-testing.yml index dfd24358d..3e0de0480 100644 --- a/.github/workflows/ci-testing.yml +++ b/.github/workflows/ci-testing.yml @@ -21,7 +21,7 @@ jobs: include: - { os: "macos-latest", python-version: "3.12" } - { os: "windows-latest", python-version: "3.11" } - - { os: "ubuntu-22.04", python-version: "3.9", requires: "oldest" } + #- { os: "ubuntu-22.04", python-version: "3.9", requires: "oldest" } # fixme timeout-minutes: 35 env: @@ -29,28 +29,36 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + - name: Install uv and setup python + uses: astral-sh/setup-uv@v6 with: + activate-environment: true python-version: ${{ matrix.python-version }} - cache: "pip" + enable-cache: true - - name: Set min. dependencies - if: matrix.requires == 'oldest' + - name: Set up dependencies run: | - pip install 'lightning-utilities[cli]' - python -m lightning_utilities.cli requirements set-oldest --req_files='["requirements.txt", "_requirements/test.txt"]' + uv pip compile pyproject.toml -o requirements.txt + cat requirements.txt - - name: Install package & dependencies + # TODO: FIX THIS TO SUPPORT PYPROJECT.TOML + # - name: Set min. dependencies + # if: matrix.requires == 'oldest' + # run: | + # uv pip install 'lightning-utilities[cli]' + # uv run python -m lightning_utilities.cli requirements set-oldest --req_files='["requirements.txt"]' + # cat requirements.txt + + - name: Install package run: | - pip --version - pip install -e '.[test]' -U -q --find-links $TORCH_URL - pip list + uv pip list + uv sync --all-extras --dev + uv pip list - name: Tests timeout-minutes: 15 run: | - python -m pytest --cov=litserve src/ tests/ -v -s --durations=100 + pytest --cov=litserve src/ tests/ -v -s --durations=100 - name: Statistics run: | diff --git a/.gitignore b/.gitignore index 008c1d465..a41e8a81c 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ venv.bak/ lightning_logs/ MNIST .DS_Store +uv.lock diff --git a/_requirements/test.txt b/_requirements/test.txt deleted file mode 100644 index a3e15eab1..000000000 --- a/_requirements/test.txt +++ /dev/null @@ -1,18 +0,0 @@ -httpx>=0.27.0 -coverage[toml] >=7.5.3 -pytest >=8.0 -pytest-cov -mypy ==1.16.1 -pytest-asyncio -asgi-lifespan -python-multipart -psutil -requests -lightning >2.0.0 -torch >2.0.0 -transformers -openai>=1.12.0 -pillow -numpy <3.0 -pytest-retry>=1.6.3 -fastmcp>=2.9.2; python_version >= '3.10' diff --git a/pyproject.toml b/pyproject.toml index dc1c7a55c..99e297e56 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,12 +2,63 @@ license_file = "LICENSE" description-file = "README.md" -[build-system] -requires = [ - "setuptools", - "wheel", +[project] +name = "litserve" +dynamic = ["version"] +description = "Lightweight AI server." +readme = "README.md" +license = {text = "Apache-2.0"} +authors = [ + {name = "Lightning-AI et al.", email = "community@lightning.ai"} +] +requires-python = ">=3.9" +keywords = ["deep learning", "pytorch", "AI"] +classifiers = [ + "Environment :: Console", + "Natural Language :: English", + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Scientific/Engineering :: Information Analysis", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] + +dependencies = [ + "fastapi>=0.100", + "pyzmq>=22.0.0", + "uvicorn[standard]>=0.29.0", ] +[project.urls] +Homepage = "https://github.com/Lightning-AI/litserve" +"Bug Tracker" = "https://github.com/Lightning-AI/litserve/issues" +Documentation = "https://lightning-ai.github.io/litserve/" +"Source Code" = "https://github.com/Lightning-AI/litserve" +Download = "https://github.com/Lightning-AI/litserve" + +[project.scripts] +litserve = "litserve.__main__:main" +lightning = "litserve.cli:main" + + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.version] +path = "src/litserve/__about__.py" +pattern = "__version__ = \"(?P.+)\"" + + +[tool.hatch.metadata] +allow-direct-references = true [tool.check-manifest] ignore = [ @@ -16,7 +67,6 @@ ignore = [ ".github/*" ] - [tool.pytest.ini_options] norecursedirs = [ ".git", @@ -128,3 +178,30 @@ convention = "google" [tool.ruff.lint.mccabe] # Unlike Flake8, default to a complexity level of 10. max-complexity = 10 + +[dependency-groups] +dev = [ + "asgi-lifespan>=2.1.0", + "coverage[toml]>=7.5.3", + "fastmcp>=2.9.2 ; python_full_version >= '3.10'", + "httpx>=0.27.0", + "lightning>2.0.0", + "mypy==1.16.1", + "numpy<3.0", + "openai>=1.12.0", + "pillow>=11.3.0", + "psutil>=7.0.0", + "pytest>=8.0", + "pytest-asyncio>=1.0.0", + "pytest-cov>=6.2.1", + "pytest-retry>=1.6.3", + "python-multipart>=0.0.20", + "requests>=2.32.4", + "torch>2.0.0", + "transformers>=4.53.0", + "uvloop>=0.21.0 ; sys_platform != 'win32'", + "tenacity>=9.1.2", + "jsonargparse", + "rich", + "torchvision>=0.22.1", +] diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index c2b750867..000000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -fastapi >=0.100 -uvicorn[standard] >=0.29.0 -pyzmq >=22.0.0 diff --git a/setup.py b/setup.py deleted file mode 100755 index f84ae0e1b..000000000 --- a/setup.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env python -# Copyright The Lightning AI team. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import glob -import os -from importlib.util import module_from_spec, spec_from_file_location -from pathlib import Path - -from pkg_resources import parse_requirements -from setuptools import find_packages, setup - -_PATH_ROOT = os.path.dirname(__file__) -_PATH_SOURCE = os.path.join(_PATH_ROOT, "src") -_PATH_REQUIRES = os.path.join(_PATH_ROOT, "_requirements") - - -def _load_py_module(fname, pkg="litserve"): - spec = spec_from_file_location(os.path.join(pkg, fname), os.path.join(_PATH_SOURCE, pkg, fname)) - py = module_from_spec(spec) - spec.loader.exec_module(py) - return py - - -def _load_requirements(path_dir: str = _PATH_ROOT, file_name: str = "requirements.txt") -> list: - reqs = parse_requirements(open(os.path.join(path_dir, file_name)).readlines()) - return list(map(str, reqs)) - - -about = _load_py_module("__about__.py") -with open(os.path.join(_PATH_ROOT, "README.md"), encoding="utf-8") as fopen: - readme = fopen.read() - - -def _prepare_extras(requirements_dir: str = _PATH_REQUIRES, skip_files: tuple = ("devel.txt", "docs.txt")) -> dict: - # https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-extras - # Define package extras. These are only installed if you specify them. - # From remote, use like `pip install pytorch-lightning[dev, docs]` - # From local copy of repo, use like `pip install ".[dev, docs]"` - req_files = [Path(p) for p in glob.glob(os.path.join(requirements_dir, "*.txt"))] - extras = { - p.stem: _load_requirements(file_name=p.name, path_dir=str(p.parent)) - for p in req_files - if p.name not in skip_files - } - # todo: eventually add some custom aggregations such as `develop` - extras = {name: sorted(set(reqs)) for name, reqs in extras.items()} - print("The extras are: ", extras) - return extras - - -# https://packaging.python.org/discussions/install-requires-vs-requirements / -# keep the meta-data here for simplicity in reading this file... it's not obvious -# what happens and to non-engineers they won't know to look in init ... -# the goal of the project is simplicity for researchers, don't want to add too much -# engineer specific practices -setup( - name="litserve", - version=about.__version__, - description=about.__docs__, - author=about.__author__, - author_email=about.__author_email__, - url=about.__homepage__, - download_url="https://github.com/Lightning-AI/litserve", - license=about.__license__, - packages=find_packages(where="src"), - package_dir={"": "src"}, - long_description=readme, - long_description_content_type="text/markdown", - include_package_data=True, - zip_safe=False, - keywords=["deep learning", "pytorch", "AI"], - python_requires=">=3.8", - setup_requires=["wheel"], - install_requires=_load_requirements(), - extras_require=_prepare_extras(), - project_urls={ - "Bug Tracker": "https://github.com/Lightning-AI/litserve/issues", - "Documentation": "https://lightning-ai.github.io/litserve/", - "Source Code": "https://github.com/Lightning-AI/litserve", - }, - classifiers=[ - "Environment :: Console", - "Natural Language :: English", - # How mature is this project? Common values are - # 3 - Alpha, 4 - Beta, 5 - Production/Stable - "Development Status :: 3 - Alpha", - # Indicate who your project is intended for - "Intended Audience :: Developers", - "Topic :: Scientific/Engineering :: Artificial Intelligence", - "Topic :: Scientific/Engineering :: Information Analysis", - # Pick your license as you wish - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - # Specify the Python versions you support here. In particular, ensure - # that you indicate whether you support Python 2, Python 3 or both. - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - ], - entry_points={ - "console_scripts": ["litserve=litserve.__main__:main", "lightning=litserve.cli:main"], - }, -) diff --git a/tests/e2e/test_e2e.py b/tests/e2e/test_e2e.py index 93cfce227..40e51b05b 100644 --- a/tests/e2e/test_e2e.py +++ b/tests/e2e/test_e2e.py @@ -14,6 +14,7 @@ import json import os import subprocess +import sys import time from concurrent.futures import ThreadPoolExecutor from functools import wraps @@ -31,7 +32,7 @@ def decorator(test_fn): @wraps(test_fn) def wrapper(*args, **kwargs): process = subprocess.Popen( - ["python", filename], + [sys.executable, filename], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL, diff --git a/tests/parity_fastapi/main.py b/tests/parity_fastapi/main.py index fea0fdb7f..ffe818e8f 100644 --- a/tests/parity_fastapi/main.py +++ b/tests/parity_fastapi/main.py @@ -1,4 +1,5 @@ import subprocess +import sys import time from functools import wraps @@ -28,7 +29,7 @@ def decorator(test_fn): @wraps(test_fn) def wrapper(*args, **kwargs): process = subprocess.Popen( - ["python", filename], + [sys.executable, filename], ) print("Waiting for server to start...") time.sleep(10) diff --git a/tests/perf_test/bert/run_test.sh b/tests/perf_test/bert/run_test.sh index 86d24d9fc..3685c484b 100644 --- a/tests/perf_test/bert/run_test.sh +++ b/tests/perf_test/bert/run_test.sh @@ -2,14 +2,14 @@ # Function to clean up server process cleanup() { - pkill -f "python tests/perf_test/bert/server.py" + pkill -f "uv run tests/perf_test/bert/server.py" } # Trap script exit to run cleanup trap cleanup EXIT # Start the server in the background and capture its PID -python tests/perf_test/bert/server.py & +uv run tests/perf_test/bert/server.py & SERVER_PID=$! echo "Server started with PID $SERVER_PID" @@ -17,7 +17,7 @@ echo "Server started with PID $SERVER_PID" # Run your benchmark script echo "Preparing to run benchmark.py..." -export PYTHONPATH=$PWD && python tests/perf_test/bert/benchmark.py +export PYTHONPATH=$PWD && uv run tests/perf_test/bert/benchmark.py # Check if benchmark.py exited successfully if [ $? -ne 0 ]; then diff --git a/tests/perf_test/stream/run_test.sh b/tests/perf_test/stream/run_test.sh index 03cc139b2..aaca2fe60 100644 --- a/tests/perf_test/stream/run_test.sh +++ b/tests/perf_test/stream/run_test.sh @@ -3,14 +3,14 @@ # Function to clean up server process cleanup() { - pkill -f "python tests/perf_test/stream/stream_speed/server.py" + pkill -f "uv run tests/perf_test/stream/stream_speed/server.py" } # Trap script exit to run cleanup trap cleanup EXIT # Start the server in the background and capture its PID -python tests/perf_test/stream/stream_speed/server.py & +uv run tests/perf_test/stream/stream_speed/server.py & SERVER_PID=$! echo "Server started with PID $SERVER_PID" @@ -18,7 +18,7 @@ echo "Server started with PID $SERVER_PID" # Run your benchmark script echo "Preparing to run benchmark.py..." -export PYTHONPATH=$PWD && python tests/perf_test/stream/stream_speed/benchmark.py +export PYTHONPATH=$PWD && uv run tests/perf_test/stream/stream_speed/benchmark.py # Check if benchmark.py exited successfully if [ $? -ne 0 ]; then diff --git a/tests/unit/test_readme.py b/tests/unit/test_readme.py index 7060829df..64fa80c29 100644 --- a/tests/unit/test_readme.py +++ b/tests/unit/test_readme.py @@ -52,7 +52,7 @@ def run_script_with_timeout(file, timeout, extra_time, killall): sel = selectors.DefaultSelector() try: process = subprocess.Popen( - ["python", str(file)], + [sys.executable, str(file)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, # Line-buffered