A high-throughput and memory-efficient inference and serving engine for LLMs
[Rust Frontend] [CI] Unify Rust artifact builds with setuptools-rust (#44981)
Signed-off-by: Bugen Zhao <i@bugenzhao.com>
Bugen Zhao committed
Jun 10, 2026 at 17:48 UTC
fdfb2566c03638be4bfa34b5e75bf9f169be8869
11 files changed
+117
-90
MANIFEST.in
+1
@@ -4,6 +4,7 @@ include requirements/cuda.txt
4
include requirements/rocm.txt
5
include requirements/cpu.txt
6
include CMakeLists.txt
7
+include tools/build_rust.py
8
9
recursive-include cmake *
10
recursive-include csrc *
build_rust.sh
+4
-15
@@ -1,5 +1,5 @@
1
#!/bin/bash
2
-# Build the vllm-rs Rust frontend binary and install it into the vllm package.
2
+# Build the vllm-rs Rust frontend binary.
3
# Usage: ./build_rust.sh [--debug]
4
#
5
# By default builds in release mode. Pass --debug for faster compile times
@@ -8,8 +8,6 @@
8
set -euo pipefail
9
10
REPO_ROOT="$(cd "$(dirname "$0")" && pwd)"
11
-RUST_DIR="$REPO_ROOT/rust"
12
-TARGET_PATH="${VLLM_RS_TARGET_PATH:-$REPO_ROOT/vllm/vllm-rs}"
11
12
# Read the required toolchain from rust-toolchain.toml.
13
TOOLCHAIN=$(grep '^channel' "$REPO_ROOT/rust-toolchain.toml" | sed 's/.*= *"\(.*\)"/\1/')
@@ -27,18 +25,9 @@ if ! rustup run "$TOOLCHAIN" rustc --version &>/dev/null; then
25
fi
26
27
if [[ "${1:-}" == "--debug" ]]; then
30
- PROFILE_ARGS=()
31
- PROFILE_DIR="debug"
28
+ PROFILE_ARG="--debug"
29
else
33
- PROFILE_ARGS=(--release)
34
- PROFILE_DIR="release"
30
+ PROFILE_ARG="--release"
31
fi
32
37
-cargo +"$TOOLCHAIN" build "${PROFILE_ARGS[@]}" \
38
- --manifest-path "$RUST_DIR/Cargo.toml" \
39
- --bin vllm-rs \
40
- --features native-tls-vendored
41
-
42
-mkdir -p "$(dirname "$TARGET_PATH")"
43
-cp "$RUST_DIR/target/$PROFILE_DIR/vllm-rs" "$TARGET_PATH"
44
-echo "Installed vllm-rs to $TARGET_PATH"
33
+python3 "$REPO_ROOT/tools/build_rust.py" "$PROFILE_ARG"
docker/Dockerfile
+17
-20
@@ -255,51 +255,47 @@ ENV TORCH_CUDA_ARCH_LIST=${torch_cuda_arch_list}
255
#################### RUST BUILD IMAGE ####################
256
# Build the Rust frontend (`vllm-rs`) in a dedicated stage so the main wheel
257
# build stage doesn't need the rust toolchain, protoc, or the rust source.
258
-# This stage runs in parallel with csrc-build/extensions-build.
259
-FROM ${BUILD_BASE_IMAGE} AS rust-build
258
+# This stage reuses the Python environment from base and runs in parallel with
259
+# csrc-build/extensions-build.
260
+FROM base AS rust-build
261
ARG BUILD_OS
262
262
-ENV DEBIAN_FRONTEND=noninteractive
263
-
264
-# Install a basic C toolchain (some rust crates compile C in their build.rs
265
-# scripts) and unzip (used to extract the pinned protoc release below).
263
+# Install native tools needed only for Rust/protoc builds.
264
RUN if [ "${BUILD_OS}" = "manylinux" ]; then \
265
dnf install -y --setopt=install_weak_deps=False \
268
- ca-certificates curl git gcc gcc-c++ make unzip \
266
+ make unzip \
267
&& dnf clean all && rm -rf /var/cache/dnf; \
268
else \
269
apt-get update -y \
270
&& apt-get install -y --no-install-recommends \
273
- ca-certificates curl git build-essential unzip \
271
+ make unzip \
272
&& rm -rf /var/lib/apt/lists/*; \
273
fi
274
275
COPY tools/install_protoc.sh /tmp/install_protoc.sh
276
RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh
277
280
-# Install rustup; the toolchain itself is pinned by rust-toolchain.toml.
281
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
282
- sh -s -- -y --profile minimal --default-toolchain none
283
-ENV PATH="/root/.cargo/bin:${PATH}"
284
-
278
WORKDIR /workspace
279
287
-# Copy only the rust workspace — the binary is the sole artifact we need.
280
+COPY requirements/build/rust.txt requirements/build/rust.txt
281
+RUN --mount=type=cache,target=/opt/uv/cache \
282
+ uv pip install --python /opt/venv/bin/python3 -r requirements/build/rust.txt
283
+
284
+# Copy only the Rust build inputs. The binary is the sole artifact we need.
285
COPY rust rust
286
COPY rust-toolchain.toml rust-toolchain.toml
287
+COPY tools/build_rust.py tools/build_rust.py
288
COPY build_rust.sh build_rust.sh
289
290
# Cap cargo parallelism to avoid exhausting the CI host's open-file limit
291
# (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise).
292
ENV CARGO_BUILD_JOBS=4
293
296
-# Build the release binary. Cache cargo registry/git and target/, but copy the
297
-# binary out of the target/ cache mount so it persists into the image layer
298
-# for later COPY --from=rust-build.
294
+# Build the release binary. Cache cargo registry/git, but not target/, because
295
+# stale target metadata can outlive source updates across BuildKit cache reuse.
296
RUN --mount=type=cache,target=/root/.cargo/registry \
297
--mount=type=cache,target=/root/.cargo/git \
301
- --mount=type=cache,target=/workspace/rust/target \
302
- VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh
298
+ bash build_rust.sh
299
#################### RUST BUILD IMAGE ####################
300
301
#################### CSRC BUILD IMAGE ####################
@@ -342,6 +338,7 @@ RUN --mount=type=cache,target=/opt/uv/cache \
338
WORKDIR /workspace
339
340
COPY pyproject.toml setup.py CMakeLists.txt ./
341
+COPY tools/build_rust.py tools/build_rust.py
342
COPY cmake cmake/
343
COPY csrc csrc/
344
COPY vllm/envs.py vllm/envs.py
@@ -508,7 +505,7 @@ COPY . .
505
506
# Drop the pre-built rust frontend binary into the source tree. setup.py
507
# detects it and ships it as-is, skipping the local cargo build.
511
-COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs
508
+COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs
509
510
ARG GIT_REPO_CHECK=0
511
RUN --mount=type=bind,source=.git,target=.git \
docker/Dockerfile.cpu
+10
-13
@@ -93,35 +93,32 @@ ENV DEBIAN_FRONTEND=noninteractive
93
94
RUN apt-get update -y \
95
&& apt-get install -y --no-install-recommends \
96
- ca-certificates curl git build-essential unzip \
96
+ ca-certificates curl git build-essential unzip python3 python3-pip \
97
&& rm -rf /var/lib/apt/lists/*
98
99
COPY tools/install_protoc.sh /tmp/install_protoc.sh
100
RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh
101
102
-# Install rustup; the toolchain itself is pinned by rust-toolchain.toml.
103
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
104
- sh -s -- -y --profile minimal --default-toolchain none
105
-ENV PATH="/root/.cargo/bin:${PATH}"
106
-
102
WORKDIR /workspace
103
109
-# Copy only the rust workspace — the binary is the sole artifact we need.
104
+COPY requirements/build/rust.txt requirements/build/rust.txt
105
+RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt
106
+
107
+# Copy only the Rust build inputs. The binary is the sole artifact we need.
108
COPY rust rust
109
COPY rust-toolchain.toml rust-toolchain.toml
110
+COPY tools/build_rust.py tools/build_rust.py
111
COPY build_rust.sh build_rust.sh
112
113
# Cap cargo parallelism to avoid exhausting the CI host's open-file limit
114
# (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise).
115
ENV CARGO_BUILD_JOBS=4
116
118
-# Build the release binary. Cache cargo registry/git and target/, but copy the
119
-# binary out of the target/ cache mount so it persists into the image layer
120
-# for later COPY --from=rust-build.
117
+# Build the release binary. Cache cargo registry/git, but not target/, because
118
+# stale target metadata can outlive source updates across BuildKit cache reuse.
119
RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked \
120
--mount=type=cache,target=/root/.cargo/git,sharing=locked \
123
- --mount=type=cache,target=/workspace/rust/target,sharing=locked \
124
- VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh
121
+ bash build_rust.sh
122
123
######################### BUILD IMAGE #########################
124
FROM base AS vllm-build
@@ -156,7 +153,7 @@ COPY . .
153
154
# Drop the pre-built rust frontend binary into the source tree. setup.py
155
# detects it and ships it as-is, skipping the local cargo build.
159
-COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs
156
+COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs
157
158
RUN if [ "$GIT_REPO_CHECK" != 0 ]; then bash tools/check_repo.sh ; fi
159
docker/Dockerfile.nightly_torch
+8
-9
@@ -102,21 +102,21 @@ ENV DEBIAN_FRONTEND=noninteractive
102
103
RUN apt-get update -y \
104
&& apt-get install -y --no-install-recommends \
105
- ca-certificates curl git build-essential unzip \
105
+ ca-certificates curl git build-essential unzip python3 python3-pip \
106
&& rm -rf /var/lib/apt/lists/*
107
108
COPY tools/install_protoc.sh /tmp/install_protoc.sh
109
RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh
110
111
-# Install rustup; the toolchain itself is pinned by rust-toolchain.toml.
112
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
113
- sh -s -- -y --profile minimal --default-toolchain none
114
-ENV PATH="/root/.cargo/bin:${PATH}"
115
-
111
WORKDIR /workspace
112
113
+COPY requirements/build/rust.txt requirements/build/rust.txt
114
+RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt
115
+
116
+# Copy only the Rust build inputs. The binary is the sole artifact we need.
117
COPY rust rust
118
COPY rust-toolchain.toml rust-toolchain.toml
119
+COPY tools/build_rust.py tools/build_rust.py
120
COPY build_rust.sh build_rust.sh
121
122
# Cap cargo parallelism to avoid exhausting the CI host's open-file limit
@@ -125,8 +125,7 @@ ENV CARGO_BUILD_JOBS=4
125
126
RUN --mount=type=cache,target=/root/.cargo/registry \
127
--mount=type=cache,target=/root/.cargo/git \
128
- --mount=type=cache,target=/workspace/rust/target \
129
- VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh
128
+ bash build_rust.sh
129
#################### RUST BUILD IMAGE ####################
130
131
#################### WHEEL BUILD IMAGE ####################
@@ -141,7 +140,7 @@ COPY . .
140
141
# Drop the pre-built rust frontend binary into the source tree. setup.py
142
# detects it and ships it as-is, skipping the local cargo build.
144
-COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs
143
+COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs
144
145
RUN python3 use_existing_torch.py
146
docker/Dockerfile.rocm
+11
-12
@@ -138,27 +138,25 @@ RUN apt-get update -q -y && apt-get install -q -y --no-install-recommends \
138
COPY tools/install_protoc.sh /tmp/install_protoc.sh
139
RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh
140
141
-# Install rustup; the toolchain itself is pinned by rust-toolchain.toml.
142
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
143
- sh -s -- -y --profile minimal --default-toolchain none
144
-ENV PATH="/root/.cargo/bin:${PATH}"
145
-
141
# Cap cargo parallelism to avoid exhausting the AMD CI host's open-file limit
142
# (rustc spawns enough concurrent processes to hit RLIMIT_NOFILE otherwise).
143
ENV CARGO_BUILD_JOBS=4
144
ENV CARGO_NET_RETRY=10
145
ENV RUSTUP_MAX_RETRIES=10
146
147
+RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
148
+ cd ${COMMON_WORKDIR}/vllm \
149
+ && uv pip install --system -r requirements/build/rust.txt
150
+
151
# Build the release binary. Cargo's registry/git caches can be written by
152
# concurrent BuildKit jobs on shared workers, so lock those cache mounts while
154
-# keeping the cache benefit. Copy the binary out so it persists into the image
155
-# layer for later COPY --from=rust-build.
153
+# keeping the cache benefit. Do not cache target/, because stale target metadata
154
+# can outlive source updates across BuildKit cache reuse.
155
RUN --mount=type=cache,id=vllm-rocm-cargo-registry,target=/root/.cargo/registry,sharing=locked \
156
--mount=type=cache,id=vllm-rocm-cargo-git,target=/root/.cargo/git,sharing=locked \
158
- --mount=type=cache,id=vllm-rocm-cargo-target,target=${COMMON_WORKDIR}/vllm/rust/target,sharing=locked \
157
cd ${COMMON_WORKDIR}/vllm \
160
- && VLLM_RS_TARGET_PATH=/tmp/vllm-rs bash build_rust.sh \
161
- && test -x /tmp/vllm-rs
158
+ && bash build_rust.sh \
159
+ && test -x vllm/vllm-rs
160
161
# -----------------------
162
# vLLM native build stages
@@ -178,6 +176,7 @@ RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
176
# pyproject.toml is bind-mounted in the RUN step so metadata-only changes do
177
# not invalidate the expensive native build layer.
178
COPY setup.py CMakeLists.txt ./
179
+COPY tools/build_rust.py tools/build_rust.py
180
COPY cmake cmake/
181
COPY csrc csrc/
182
COPY vllm/envs.py vllm/envs.py
@@ -211,7 +210,7 @@ COPY --from=csrc-build ${COMMON_WORKDIR}/vllm/dist /precompiled-wheels
210
211
# Drop the pre-built rust frontend binary into the source tree. setup.py
212
# detects it and ships it as-is, skipping the local cargo build.
214
-COPY --from=rust-build /tmp/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs
213
+COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs
214
215
RUN --mount=type=cache,id=vllm-rocm-uv,target=/root/.cache/uv \
216
cd vllm \
@@ -420,7 +419,7 @@ ARG COMMON_WORKDIR
419
420
# Drop the pre-built rust frontend binary into the source tree. setup.py
421
# detects it and ships it as-is, skipping the local cargo build.
423
-COPY --from=rust-build /tmp/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs
422
+COPY --from=rust-build ${COMMON_WORKDIR}/vllm/vllm/vllm-rs ${COMMON_WORKDIR}/vllm/vllm/vllm-rs
423
424
# Create /install directory for custom wheels
425
RUN mkdir -p /install
docker/Dockerfile.xpu
+8
-9
@@ -6,21 +6,21 @@ ENV DEBIAN_FRONTEND=noninteractive
6
7
RUN apt-get update -y \
8
&& apt-get install -y --no-install-recommends \
9
- ca-certificates curl git build-essential unzip \
9
+ ca-certificates curl git build-essential unzip python3 python3-pip \
10
&& rm -rf /var/lib/apt/lists/*
11
12
COPY tools/install_protoc.sh /tmp/install_protoc.sh
13
RUN /tmp/install_protoc.sh && rm /tmp/install_protoc.sh
14
15
-# Install rustup; the toolchain itself is pinned by rust-toolchain.toml.
16
-RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
17
- sh -s -- -y --profile minimal --default-toolchain none
18
-ENV PATH="/root/.cargo/bin:${PATH}"
19
-
15
WORKDIR /workspace
16
17
+COPY requirements/build/rust.txt requirements/build/rust.txt
18
+RUN python3 -m pip install --no-cache-dir -r requirements/build/rust.txt
19
+
20
+# Copy only the Rust build inputs. The binary is the sole artifact we need.
21
COPY rust rust
22
COPY rust-toolchain.toml rust-toolchain.toml
23
+COPY tools/build_rust.py tools/build_rust.py
24
COPY build_rust.sh build_rust.sh
25
26
# Cap cargo parallelism to avoid exhausting the CI host's open-file limit
@@ -29,8 +29,7 @@ ENV CARGO_BUILD_JOBS=4
29
30
RUN --mount=type=cache,target=/root/.cargo/registry \
31
--mount=type=cache,target=/root/.cargo/git \
32
- --mount=type=cache,target=/workspace/rust/target \
33
- VLLM_RS_TARGET_PATH=/workspace/vllm-rs bash build_rust.sh
32
+ bash build_rust.sh
33
34
FROM intel/deep-learning-essentials:2025.3.2-0-devel-ubuntu24.04 AS vllm-base
35
@@ -215,7 +214,7 @@ COPY . .
214
215
# Drop the pre-built rust frontend binary into the source tree. setup.py
216
# detects it and ships it as-is, skipping the local cargo build.
218
-COPY --from=rust-build /workspace/vllm-rs vllm/vllm-rs
217
+COPY --from=rust-build /workspace/vllm/vllm-rs vllm/vllm-rs
218
219
ARG GIT_REPO_CHECK=0
220
RUN --mount=type=bind,source=.git,target=.git \
requirements/build/rust.txt
new
+4
@@ -0,0 +1,4 @@
1
+# Dependencies for building Rust artifacts through setuptools-rust.
2
+setuptools>=77.0.3,<81.0.0
3
+setuptools-rust>=1.9.0
4
+wheel
rust/README.md
+1
-1
@@ -71,7 +71,7 @@ To build the `vllm-rs` in isolation:
71
72
```bash
73
# from the local checkout
74
-cargo install --path src/cmd --bin vllm-rs
74
+./build_rust.sh
75
```
76
77
### Example Request
setup.py
+6
-11
@@ -18,7 +18,6 @@ import torch
18
from packaging.version import Version, parse
19
from setuptools import Extension, setup
20
from setuptools.command.build_ext import build_ext
21
-from setuptools_rust import Binding, RustExtension
21
from setuptools_rust.build import build_rust
22
from setuptools_scm import get_version
23
from torch.utils.cpp_extension import CUDA_HOME, ROCM_HOME
@@ -40,6 +39,9 @@ PRECOMPILED_RUST_FRONTEND_PATH = ROOT_DIR / "vllm" / "vllm-rs"
39
# cannot import envs directly because it depends on vllm,
40
# which is not installed yet
41
envs = load_module_from_path("envs", os.path.join(ROOT_DIR, "vllm", "envs.py"))
42
+rust_build = load_module_from_path(
43
+ "rust_build", os.path.join(ROOT_DIR, "tools", "build_rust.py")
44
+)
45
46
VLLM_TARGET_DEVICE = envs.VLLM_TARGET_DEVICE
47
USE_PRECOMPILED_EXTENSIONS = envs.VLLM_USE_PRECOMPILED
@@ -1146,16 +1148,9 @@ if USE_PRECOMPILED_RUST_FRONTEND or PRECOMPILED_RUST_FRONTEND_PATH.exists():
1148
# package directory alongside the Python modules.
1149
# TODO: we may use `RustBin` to directly install it into `bin` directory, but this
1150
# requires extra work on using precompiled binaries.
1149
-rust_extensions = [
1150
- RustExtension(
1151
- target="vllm.vllm-rs",
1152
- path="rust/src/cmd/Cargo.toml",
1153
- args=["--bin", "vllm-rs"],
1154
- features=["native-tls-vendored"],
1155
- binding=Binding.Exec,
1156
- optional=not should_require_rust_frontend(),
1157
- ),
1158
-]
1151
+rust_extensions = rust_build.rust_extensions(
1152
+ optional=not should_require_rust_frontend()
1153
+)
1154
1155
setup(
1156
# static metadata should rather go in pyproject.toml
tools/build_rust.py
new
+47
@@ -0,0 +1,47 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+"""Shared setuptools-rust build entry for the vllm-rs binary."""
5
+
6
+from __future__ import annotations
7
+
8
+import os
9
+import sys
10
+from pathlib import Path
11
+
12
+from setuptools import setup
13
+from setuptools_rust import Binding, RustExtension
14
+
15
+ROOT_DIR = Path(__file__).resolve().parents[1]
16
+
17
+
18
+def rust_extensions(*, optional: bool) -> list[RustExtension]:
19
+ return [
20
+ RustExtension(
21
+ target="vllm.vllm-rs",
22
+ path="rust/src/cmd/Cargo.toml",
23
+ args=["--bin", "vllm-rs"],
24
+ features=["native-tls-vendored"],
25
+ binding=Binding.Exec,
26
+ optional=optional,
27
+ ),
28
+ ]
29
+
30
+
31
+def build_binary(build_rust_args: list[str]) -> None:
32
+ os.chdir(ROOT_DIR)
33
+ (ROOT_DIR / "vllm").mkdir(exist_ok=True)
34
+ setup(
35
+ name="vllm-rust-frontend-build",
36
+ packages=[],
37
+ rust_extensions=rust_extensions(optional=False),
38
+ script_args=["build_rust", "--quiet", "--inplace", *build_rust_args],
39
+ )
40
+
41
+
42
+def main() -> None:
43
+ build_binary(sys.argv[1:])
44
+
45
+
46
+if __name__ == "__main__":
47
+ main()