diff --git a/MANIFEST.in b/MANIFEST.in index fb3cccbb4a..cbb136e6b7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,7 @@ include requirements/cuda.txt include requirements/rocm.txt include requirements/cpu.txt include CMakeLists.txt +include tools/build_rust.py recursive-include cmake * recursive-include csrc * diff --git a/build_rust.sh b/build_rust.sh index 98871ec8ab..b5ba1d739a 100755 --- a/build_rust.sh +++ b/build_rust.sh @@ -1,5 +1,5 @@ #!/bin/bash -# Build the vllm-rs Rust frontend binary and install it into the vllm package. +# Build the vllm-rs Rust frontend binary. # Usage: ./build_rust.sh [--debug] # # By default builds in release mode. Pass --debug for faster compile times @@ -8,8 +8,6 @@ set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")" && pwd)" -RUST_DIR="$REPO_ROOT/rust" -TARGET_PATH="${VLLM_RS_TARGET_PATH:-$REPO_ROOT/vllm/vllm-rs}" # Read the required toolchain from rust-toolchain.toml. TOOLCHAIN=$(grep '^channel' "$REPO_ROOT/rust-toolchain.toml" | sed 's/.*= *"\(.*\)"/\1/') @@ -27,18 +25,9 @@ if ! rustup run "$TOOLCHAIN" rustc --version &>/dev/null; then fi if [[ "${1:-}" == "--debug" ]]; then - PROFILE_ARGS=() - PROFILE_DIR="debug" + PROFILE_ARG="--debug" else - PROFILE_ARGS=(--release) - PROFILE_DIR="release" + PROFILE_ARG="--release" fi -cargo +"$TOOLCHAIN" build "${PROFILE_ARGS[@]}" \ - --manifest-path "$RUST_DIR/Cargo.toml" \ - --bin vllm-rs \ - --features native-tls-vendored - -mkdir -p "$(dirname "$TARGET_PATH")" -cp "$RUST_DIR/target/$PROFILE_DIR/vllm-rs" "$TARGET_PATH" -echo "Installed vllm-rs to $TARGET_PATH" +python3 "$REPO_ROOT/tools/build_rust.py" "$PROFILE_ARG" diff --git a/docker/Dockerfile b/docker/Dockerfile index 9b4227cdf6..34d1ec7975 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -255,51 +255,47 @@ ENV TORCH_CUDA_ARCH_LIST=${torch_cuda_arch_list} #################### RUST BUILD IMAGE #################### # Build the Rust frontend (`vllm-rs`) in a dedicated stage so the main wheel # build stage doesn't need the rust toolchain, protoc, or the rust source. -# This stage runs in parallel with csrc-build/extensions-build. -FROM ${BUILD_BASE_IMAGE} AS rust-build +# This stage reuses the Python environment from base and runs in parallel with +# csrc-build/extensions-build. +FROM base AS rust-build ARG BUILD_OS -ENV DEBIAN_FRONTEND=noninteractive - -# Install a basic C toolchain (some rust crates compile C in their build.rs -# scripts) and unzip (used to extract the pinned protoc release below). +# Install native tools needed only for Rust/protoc builds. RUN if [ "${BUILD_OS}" = "manylinux" ]; then \ dnf install -y --setopt=install_weak_deps=False \ - ca-certificates curl git gcc gcc-c++ make unzip \ + make unzip \ && dnf clean all && rm -rf /var/cache/dnf; \ else \ apt-get update -y \ && apt-get install -y --no-install-recommends \ - ca-certificates curl git build-essential unzip \ + make unzip \ && rm -rf /var/lib/apt/lists/*; \ fi COPY tools/install_protoc.sh /tmp/install_protoc.sh RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh -# Install rustup; the toolchain itself is pinned by rust-toolchain.toml. -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain none -ENV PATH="/root/.cargo/bin:${PATH}" - WORKDIR /workspace -# Copy only the rust workspace — the binary is the sole artifact we need. +COPY requirements/build/rust.txt requirements/build/rust.txt +RUN --mount=type=cache,target=/opt/uv/cache \ + uv pip install --python /opt/venv/bin/python3 -r requirements/build/rust.txt + +# Copy only the Rust build inputs. The binary is the sole artifact we need. COPY rust rust COPY rust-toolchain.toml rust-toolchain.toml +COPY tools/build_rust.py tools/build_rust.py COPY build_rust.sh build_rust.sh # Cap cargo parallelism to avoid exhausting the CI host's open-file limit # (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise). ENV CARGO_BUILD_JOBS=4 -# Build the release binary. Cache cargo registry/git and target/, but copy the -# binary out of the target/ cache mount so it persists into the image layer -# for later COPY --from=rust-build. +# Build the release binary. Cache cargo registry/git, but not target/, because +# stale target metadata can outlive source updates across BuildKit cache reuse. RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ - --mount=type=cache,target=/workspace/rust/target \ - VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh + bash build_rust.sh #################### RUST BUILD IMAGE #################### #################### CSRC BUILD IMAGE #################### @@ -342,6 +338,7 @@ RUN --mount=type=cache,target=/opt/uv/cache \ WORKDIR /workspace COPY pyproject.toml setup.py CMakeLists.txt ./ +COPY tools/build_rust.py tools/build_rust.py COPY cmake cmake/ COPY csrc csrc/ COPY vllm/envs.py vllm/envs.py @@ -508,7 +505,7 @@ COPY . . # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs +COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs ARG GIT_REPO_CHECK=0 RUN --mount=type=bind,source=.git,target=.git \ diff --git a/docker/Dockerfile.cpu b/docker/Dockerfile.cpu index 02af3fd3c3..f86097cdb3 100644 --- a/docker/Dockerfile.cpu +++ b/docker/Dockerfile.cpu @@ -93,35 +93,32 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ - ca-certificates curl git build-essential unzip \ + ca-certificates curl git build-essential unzip python3 python3-pip \ && rm -rf /var/lib/apt/lists/* COPY tools/install_protoc.sh /tmp/install_protoc.sh RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh -# Install rustup; the toolchain itself is pinned by rust-toolchain.toml. -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain none -ENV PATH="/root/.cargo/bin:${PATH}" - WORKDIR /workspace -# Copy only the rust workspace — the binary is the sole artifact we need. +COPY requirements/build/rust.txt requirements/build/rust.txt +RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt + +# Copy only the Rust build inputs. The binary is the sole artifact we need. COPY rust rust COPY rust-toolchain.toml rust-toolchain.toml +COPY tools/build_rust.py tools/build_rust.py COPY build_rust.sh build_rust.sh # Cap cargo parallelism to avoid exhausting the CI host's open-file limit # (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise). ENV CARGO_BUILD_JOBS=4 -# Build the release binary. Cache cargo registry/git and target/, but copy the -# binary out of the target/ cache mount so it persists into the image layer -# for later COPY --from=rust-build. +# Build the release binary. Cache cargo registry/git, but not target/, because +# stale target metadata can outlive source updates across BuildKit cache reuse. RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \ --mount=type=cache,target=/root/.cargo/git,sharing=locked \ - --mount=type=cache,target=/workspace/rust/target,sharing=locked \ - VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh + bash build_rust.sh ######################### BUILD IMAGE ######################### FROM base AS vllm-build @@ -156,7 +153,7 @@ COPY . . # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs +COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs RUN if [ "$GIT_REPO_CHECK" != 0 ]; then bash tools/check_repo.sh ; fi diff --git a/docker/Dockerfile.nightly_torch b/docker/Dockerfile.nightly_torch index 4fbfe832ac..1ac3626088 100644 --- a/docker/Dockerfile.nightly_torch +++ b/docker/Dockerfile.nightly_torch @@ -102,21 +102,21 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ - ca-certificates curl git build-essential unzip \ + ca-certificates curl git build-essential unzip python3 python3-pip \ && rm -rf /var/lib/apt/lists/* COPY tools/install_protoc.sh /tmp/install_protoc.sh RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh -# Install rustup; the toolchain itself is pinned by rust-toolchain.toml. -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain none -ENV PATH="/root/.cargo/bin:${PATH}" - WORKDIR /workspace +COPY requirements/build/rust.txt requirements/build/rust.txt +RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt + +# Copy only the Rust build inputs. The binary is the sole artifact we need. COPY rust rust COPY rust-toolchain.toml rust-toolchain.toml +COPY tools/build_rust.py tools/build_rust.py COPY build_rust.sh build_rust.sh # Cap cargo parallelism to avoid exhausting the CI host's open-file limit @@ -125,8 +125,7 @@ ENV CARGO_BUILD_JOBS=4 RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ - --mount=type=cache,target=/workspace/rust/target \ - VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh + bash build_rust.sh #################### RUST BUILD IMAGE #################### #################### WHEEL BUILD IMAGE #################### @@ -141,7 +140,7 @@ COPY . . # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs +COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs RUN python3 use_existing_torch.py diff --git a/docker/Dockerfile.rocm b/docker/Dockerfile.rocm index 16284e999a..ebde46b6d0 100644 --- a/docker/Dockerfile.rocm +++ b/docker/Dockerfile.rocm @@ -138,27 +138,25 @@ RUN apt-get update -q -y && apt-get install -q -y --no-install-recommends \ COPY tools/install_protoc.sh /tmp/install_protoc.sh RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh -# Install rustup; the toolchain itself is pinned by rust-toolchain.toml. -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain none -ENV PATH="/root/.cargo/bin:${PATH}" - # Cap cargo parallelism to avoid exhausting the AMD CI host's open-file limit # (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise). ENV CARGO_BUILD_JOBS=4 ENV CARGO_NET_RETRY=10 ENV RUSTUP_MAX_RETRIES=10 +RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \ + cd ${COMMON_WORKDIR}/vllm \ + && uv pip install --system -r requirements/build/rust.txt + # Build the release binary. Cargo's registry/git caches can be written by # concurrent BuildKit jobs on shared workers, so lock those cache mounts while -# keeping the cache benefit. Copy the binary out so it persists into the image -# layer for later COPY --from=rust-build. +# keeping the cache benefit. Do not cache target/, because stale target metadata +# can outlive source updates across BuildKit cache reuse. RUN --mount=type=cache,id=vllm-rocm-cargo-registry,target=/root/.cargo/registry,sharing=locked \ --mount=type=cache,id=vllm-rocm-cargo-git,target=/root/.cargo/git,sharing=locked \ - --mount=type=cache,id=vllm-rocm-cargo-target,target=${COMMON_WORKDIR}/vllm/rust/target,sharing=locked \ cd ${COMMON_WORKDIR}/vllm \ - && VLLM_RS_TARGET_PATH=/tmp/vllm-rs bash build_rust.sh \ - && test -x /tmp/vllm-rs + && bash build_rust.sh \ + && test -x vllm/vllm-rs # ----------------------- # vLLM native build stages @@ -178,6 +176,7 @@ RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \ # pyproject.toml is bind-mounted in the RUN step so metadata-only changes do # not invalidate the expensive native build layer. COPY setup.py CMakeLists.txt ./ +COPY tools/build_rust.py tools/build_rust.py COPY cmake cmake/ COPY csrc csrc/ COPY vllm/envs.py vllm/envs.py @@ -211,7 +210,7 @@ COPY --from=csrc-build ${COMMON_WORKDIR}/vllm/dist /precompiled-wheels # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /tmp/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs +COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \ cd vllm \ @@ -420,7 +419,7 @@ ARG COMMON_WORKDIR # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /tmp/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs +COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs # Create /install directory for custom wheels RUN mkdir -p /install diff --git a/docker/Dockerfile.xpu b/docker/Dockerfile.xpu index 7b19e20f54..3137d882fd 100644 --- a/docker/Dockerfile.xpu +++ b/docker/Dockerfile.xpu @@ -6,21 +6,21 @@ ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update -y \ && apt-get install -y --no-install-recommends \ - ca-certificates curl git build-essential unzip \ + ca-certificates curl git build-essential unzip python3 python3-pip \ && rm -rf /var/lib/apt/lists/* COPY tools/install_protoc.sh /tmp/install_protoc.sh RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh -# Install rustup; the toolchain itself is pinned by rust-toolchain.toml. -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ - sh -s -- -y --profile minimal --default-toolchain none -ENV PATH="/root/.cargo/bin:${PATH}" - WORKDIR /workspace +COPY requirements/build/rust.txt requirements/build/rust.txt +RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt + +# Copy only the Rust build inputs. The binary is the sole artifact we need. COPY rust rust COPY rust-toolchain.toml rust-toolchain.toml +COPY tools/build_rust.py tools/build_rust.py COPY build_rust.sh build_rust.sh # Cap cargo parallelism to avoid exhausting the CI host's open-file limit @@ -29,8 +29,7 @@ ENV CARGO_BUILD_JOBS=4 RUN --mount=type=cache,target=/root/.cargo/registry \ --mount=type=cache,target=/root/.cargo/git \ - --mount=type=cache,target=/workspace/rust/target \ - VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh + bash build_rust.sh FROM intel/deep-learning-essentials:2025.3.2-0-devel-ubuntu24.04 AS vllm-base @@ -215,7 +214,7 @@ COPY . . # Drop the pre-built rust frontend binary into the source tree. setup.py # detects it and ships it as-is, skipping the local cargo build. -COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs +COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs ARG GIT_REPO_CHECK=0 RUN --mount=type=bind,source=.git,target=.git \ diff --git a/requirements/build/rust.txt b/requirements/build/rust.txt new file mode 100644 index 0000000000..e2874dee0a --- /dev/null +++ b/requirements/build/rust.txt @@ -0,0 +1,4 @@ +# Dependencies for building Rust artifacts through setuptools-rust. +setuptools>=77.0.3,<81.0.0 +setuptools-rust>=1.9.0 +wheel diff --git a/rust/README.md b/rust/README.md index 679a7f0966..b14aba3fae 100644 --- a/rust/README.md +++ b/rust/README.md @@ -71,7 +71,7 @@ To build the `vllm-rs` in isolation: ```bash # from the local checkout -cargo install --path src/cmd --bin vllm-rs +./build_rust.sh ``` ### Example Request diff --git a/setup.py b/setup.py index b674d55a14..d067aae349 100644 --- a/setup.py +++ b/setup.py @@ -18,7 +18,6 @@ import torch from packaging.version import Version, parse from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -from setuptools_rust import Binding, RustExtension from setuptools_rust.build import build_rust from setuptools_scm import get_version from torch.utils.cpp_extension import CUDA_HOME, ROCM_HOME @@ -40,6 +39,9 @@ PRECOMPILED_RUST_FRONTEND_PATH = ROOT_DIR / "vllm" / "vllm-rs" # cannot import envs directly because it depends on vllm, # which is not installed yet envs = load_module_from_path("envs", os.path.join(ROOT_DIR, "vllm", "envs.py")) +rust_build = load_module_from_path( + "rust_build", os.path.join(ROOT_DIR, "tools", "build_rust.py") +) VLLM_TARGET_DEVICE = envs.VLLM_TARGET_DEVICE USE_PRECOMPILED_EXTENSIONS = envs.VLLM_USE_PRECOMPILED @@ -1146,16 +1148,9 @@ if USE_PRECOMPILED_RUST_FRONTEND or PRECOMPILED_RUST_FRONTEND_PATH.exists(): # package directory alongside the Python modules. # TODO: we may use `RustBin` to directly install it into `bin` directory, but this # requires extra work on using precompiled binaries. -rust_extensions = [ - RustExtension( - target="vllm.vllm-rs", - path="rust/src/cmd/Cargo.toml", - args=["--bin", "vllm-rs"], - features=["native-tls-vendored"], - binding=Binding.Exec, - optional=not should_require_rust_frontend(), - ), -] +rust_extensions = rust_build.rust_extensions( + optional=not should_require_rust_frontend() +) setup( # static metadata should rather go in pyproject.toml diff --git a/tools/build_rust.py b/tools/build_rust.py new file mode 100644 index 0000000000..169e636ccb --- /dev/null +++ b/tools/build_rust.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: Apache-2.0 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project + +"""Shared setuptools-rust build entry for the vllm-rs binary.""" + +from __future__ import annotations + +import os +import sys +from pathlib import Path + +from setuptools import setup +from setuptools_rust import Binding, RustExtension + +ROOT_DIR = Path(__file__).resolve().parents[1] + + +def rust_extensions(*, optional: bool) -> list[RustExtension]: + return [ + RustExtension( + target="vllm.vllm-rs", + path="rust/src/cmd/Cargo.toml", + args=["--bin", "vllm-rs"], + features=["native-tls-vendored"], + binding=Binding.Exec, + optional=optional, + ), + ] + + +def build_binary(build_rust_args: list[str]) -> None: + os.chdir(ROOT_DIR) + (ROOT_DIR / "vllm").mkdir(exist_ok=True) + setup( + name="vllm-rust-frontend-build", + packages=[], + rust_extensions=rust_extensions(optional=False), + script_args=["build_rust", "--quiet", "--inplace", *build_rust_args], + ) + + +def main() -> None: + build_binary(sys.argv[1:]) + + +if __name__ == "__main__": + main()