Skip to content

Commit 8e43842

Browse files
authored
feat(vllm, distributed): tensor parallel distributed workers (#9612)
* feat(vllm): build vllm from source for Intel XPU Upstream publishes no XPU wheels for vllm. The Intel profile was silently picking up a non-XPU wheel that imported but errored at engine init, and several runtime deps (pillow, charset-normalizer, chardet) were missing on Intel -- backend.py crashed at import time before the gRPC server came up. Switch the Intel profile to upstream's documented from-source procedure (docs/getting_started/installation/gpu.xpu.inc.md in vllm-project/vllm): - Bump portable Python to 3.12 -- vllm-xpu-kernels ships only a cp312 wheel. - Source /opt/intel/oneapi/setvars.sh so vllm's CMake build sees the dpcpp/sycl compiler from the oneapi-basekit base image. - Hide requirements-intel-after.txt during installRequirements (it used to 'pip install vllm'); install vllm's deps from a fresh git clone of vllm via 'uv pip install -r requirements/xpu.txt', swap stock triton for triton-xpu==3.7.0, then 'VLLM_TARGET_DEVICE=xpu uv pip install --no-deps .'. - requirements-intel.txt trimmed to LocalAI's direct deps (accelerate / transformers / bitsandbytes); torch-xpu, vllm, vllm_xpu_kernels and the rest come from upstream's xpu.txt during the source build. - requirements.txt: add pillow + charset-normalizer + chardet -- used by backend.py and missing on the Intel install profile. - run.sh: 'set -x' so backend startup is visible in container logs (the gRPC startup error path was previously opaque). Also adds a one-line docs example for engine_args.attention_backend under the vLLM section, since older XE-HPG GPUs (e.g. Arc A770) need TRITON_ATTN to bypass the cutlass path in vllm_xpu_kernels. Tested end-to-end on an Intel Arc A770 with Qwen2.5-0.5B-Instruct via LocalAI's /v1/chat/completions. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * feat(vllm): add multi-node data-parallel follower worker vLLM v1's multi-node story is one process per node sharing a DP coordinator over ZMQ -- the head runs the API server with data_parallel_size > 1 and followers run `vllm serve --headless ...` with matching topology. Today LocalAI can already configure DP on the head via the engine_args YAML map, but there's no way to bring up the follower nodes -- so the head sits waiting for ranks that never handshake. Add `local-ai p2p-worker vllm`, mirroring MLXDistributed's structural precedent (operator-launched, static config, no NATS placement). The worker: - Optionally self-registers with the frontend as an agent-type node tagged `node.role=vllm-follower` so it's visible in the admin UI and operators can scope ordinary models away via inverse selectors. - Resolves the platform-specific vllm backend via the gallery's "vllm" meta-entry (cuda*, intel-vllm, rocm-vllm, ...). - Runs vLLM as a child process so the heartbeat goroutine survives until vLLM exits; forwards SIGINT/SIGTERM so vLLM can clean up its ZMQ sockets before we tear down. - Validates --headless + --start-rank 0 is rejected (rank 0 is the head and must serve the API). Backend run.sh dispatches `serve` as the first arg to vllm's own CLI instead of LocalAI's backend.py gRPC server -- the follower speaks ZMQ directly to the head, there is no LocalAI gRPC on the follower side. Single-node usage is unchanged. Generalises the gallery resolution helper into findBackendPath() shared by MLX and vLLM workers; extracts ParseNodeLabels for the comma-separated label parsing both use. Ships with two compose recipes (`docker-compose.vllm-multinode.yaml` for NVIDIA, `docker-compose.vllm-multinode.intel.yaml` for Intel XPU/xccl) plus `tests/e2e/vllm-multinode/smoke.sh`. Both vendors are supported (NCCL for CUDA/ROCm, xccl for XPU) but mixed-vendor DP is not -- PyTorch's process group requires every rank to use the same collective backend, and NCCL/xccl/gloo don't interoperate. Out of scope (deferred): SmartRouter-driven placement of follower ranks via NATS backend.install events, follower log streaming through /api/backend-logs, tensor-parallel across nodes, disaggregated prefill via KVTransferConfig. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * test(vllm): CPU-only end-to-end test for multi-node DP Adds tests/e2e/vllm-multinode/, a Ginkgo + testcontainers-go suite that brings up a head + headless follower from the locally-built local-ai:tests image, bind-mounts the cpu-vllm backend extracted by make extract-backend-vllm so it's seen as a system backend (no gallery fetch, no registry server), and asserts a chat completion across both DP ranks. New `make test-e2e-vllm-multinode` target wires the docker build, backend extract, and ginkgo run together; BuildKit caches both images so re-runs only rebuild what changed. Tagged Label("VLLMMultinode") so the existing distributed suite isn't pulled along. Two pre-existing bugs surfaced by the test: 1. extract-backend-% (Makefile) failed for every backend, because all backend images end with `FROM scratch` and `docker create` rejects an image with no CMD/ENTRYPOINT. Fixed by passing --entrypoint=/run.sh -- the container is never started, only docker-cp'd, so the path doesn't have to exist; we just need anything that satisfies the daemon's create-time validation. 2. backend/python/vllm/run.sh's `serve` shortcut for the multi-node DP follower exec'd ${EDIR}/venv/bin/vllm directly, but uv bakes an absolute build-time shebang (`#!/vllm/venv/bin/python3`) that no longer resolves once the backend is relocated to BackendsPath. _makeVenvPortable's shebang rewriter only matches paths that already point at ${EDIR}, so the original shebang slips through unchanged. Fixed by exec-ing ${EDIR}/venv/bin/python with the script as an argument -- Python ignores the script's shebang in that case. The test fixture caps memory aggressively (max_model_len=512, VLLM_CPU_KVCACHE_SPACE=1, TORCH_COMPILE_DISABLE=1) so two CPU engines fit on a 32 GB box. TORCH_COMPILE_DISABLE is currently mandatory for cpu-vllm: torch._inductor's CPU-ISA probe runs even with enforce_eager=True and needs g++ on PATH, which the LocalAI runtime image doesn't ship -- to be addressed in a follow-up that bundles a toolchain in the cpu-vllm backend. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * feat(vllm): bundle a g++ toolchain in the cpu-vllm backend image torch._inductor's CPU-ISA probe (`cpu_model_runner.py:65 "Warming up model for the compilation"`) shells out to `g++` at vllm engine startup, regardless of `enforce_eager=True` -- the eager flag only disables CUDA graphs, not inductor's first-batch warmup. The LocalAI CPU runtime image (Dockerfile, unconditional apt list) does not ship build-essential, and the cpu-vllm backend image is `FROM scratch`, so any non-trivial inference on cpu-vllm crashes with: torch._inductor.exc.InductorError: InvalidCxxCompiler: No working C++ compiler found in torch._inductor.config.cpp.cxx: (None, 'g++') Bundling the toolchain in the CPU runtime image would bloat every non-vllm-CPU deployment and force a single GCC version on backends that may want clang or a different version. So this lives in the backend, gated to BUILD_TYPE=='' (the CPU profile). `package.sh` snapshots g++ + binutils + cc1plus + libstdc++ + libc6 (runtime + dev) + the math libs cc1plus links (libisl/libmpc/libmpfr/ libjansson) into ${BACKEND}/toolchain/, mirroring /usr/... layout. The unversioned binaries on Debian/Ubuntu are symlink chains pointing into multiarch packages (`g++` -> `g++-13` -> `x86_64-linux-gnu-g++-13`, the latter in `g++-13-x86-64-linux-gnu`), so the package list resolves both the version and the arch-triplet variant. Symlinks /lib -> usr/lib and /lib64 -> usr/lib64 are recreated under the toolchain root because Ubuntu's UsrMerge keeps them at /, and ld scripts (`libc.so`, `libm.so`) hardcode `/lib/...` paths that --sysroot re-roots into the toolchain. The unversioned `g++`/`gcc`/`cpp` symlinks are replaced with wrapper shell scripts that resolve their own location at runtime and pass `--sysroot=<toolchain>` and `-B <toolchain>/usr/lib/gcc/<triplet>/<ver>/` to the underlying versioned binary. That's how torch's bare `g++ foo.cpp -o foo` invocation finds cc1plus (-B), system headers (--sysroot), and the bundled libstdc++ (--sysroot, --sysroot is recursive into linker). `run.sh` adds the toolchain bin dir to PATH and the toolchain's shared-lib dir to LD_LIBRARY_PATH -- everything else (header search, linker search, executable search) is encapsulated in the wrappers. No-op for non-CPU builds, the dir doesn't exist there. The cpu-vllm image grows by ~217 MB. Tradeoff is acceptable -- cpu-vllm is already a niche profile (few users compared to GPU vllm) and the alternative is a backend that crashes at first inference unless the operator manually sets TORCH_COMPILE_DISABLE=1, which silently disables all torch.compile optimizations. Drops `TORCH_COMPILE_DISABLE=1` from tests/e2e/vllm-multinode -- the smoke now exercises the real compile path through the bundled toolchain. Test runtime is +20s for the warmup compile, still <90s end to end. Assisted-by: Claude:claude-opus-4-7 [Claude Code] Signed-off-by: Richard Palethorpe <io@richiejp.com> * fix(vllm): scope jetson-ai-lab index to L4T-specific wheels via pyproject.toml The L4T arm64 build resolves dependencies through pypi.jetson-ai-lab.io, which hosts the L4T-specific torch / vllm / flash-attn wheels but also transparently proxies the rest of PyPI through `/+f/<sha>/<filename>` URLs. With `--extra-index-url` + `--index-strategy=unsafe-best-match` uv would pick those proxy URLs for ordinary PyPI packages — anthropic/openai/propcache/annotated-types — and fail when the proxy 503s. Master is hitting the same bug on its own l4t-vllm matrix entry. Switch the l4t13 install path to a pyproject.toml that marks the jetson-ai-lab index `explicit = true` and pins only torch, torchvision, torchaudio, flash-attn, and vllm to it via [tool.uv.sources]. uv won't consult the L4T mirror for anything else, so transitive deps fall back to PyPI as the default index — no exposure to the proxy 503s. `uv pip install -r requirements.txt` ignores [tool.uv.sources], so the l4t13 branch in install.sh now invokes `uv pip install --requirement pyproject.toml` directly, replacing the old requirements-l4t13*.txt files. Other BUILD_PROFILEs continue using libbackend.sh's installRequirements and never read pyproject.toml. Local resolution test (x86_64, dry-run) confirms uv hits the L4T index for torch and falls through to PyPI for everything else. Assisted-by: claude-code:claude-opus-4-7-1m [Read] [Edit] [Bash] [Write] Signed-off-by: Richard Palethorpe <io@richiejp.com> --------- Signed-off-by: Richard Palethorpe <io@richiejp.com>
1 parent 503904d commit 8e43842

20 files changed

Lines changed: 1090 additions & 74 deletions

Makefile

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,20 @@ run-e2e-aio: protogen-go
232232
@echo 'Running e2e AIO tests'
233233
$(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --flake-attempts $(TEST_FLAKES) -v -r ./tests/e2e-aio
234234

235+
# vLLM multi-node DP smoke (CPU). Builds local-ai:tests and the
236+
# cpu-vllm backend from the current working tree, then drives a
237+
# head + headless follower via testcontainers-go and asserts a chat
238+
# completion. BuildKit caches both images, so re-runs only rebuild
239+
# what changed. The test lives under tests/e2e/distributed and is
240+
# selected by the VLLMMultinode label so it doesn't run alongside
241+
# the other distributed-suite tests by default.
242+
test-e2e-vllm-multinode: docker-build-e2e extract-backend-vllm protogen-go
243+
@echo 'Running e2e vLLM multi-node DP test'
244+
LOCALAI_IMAGE=local-ai \
245+
LOCALAI_IMAGE_TAG=tests \
246+
LOCALAI_VLLM_BACKEND_DIR=$(abspath ./local-backends/vllm) \
247+
$(GOCMD) run github.com/onsi/ginkgo/v2/ginkgo --label-filter='VLLMMultinode' -v -r ./tests/e2e/distributed
248+
235249
########################################################
236250
## E2E tests
237251
########################################################
@@ -319,7 +333,7 @@ local-backends:
319333

320334
extract-backend-%: docker-build-% local-backends
321335
@echo "Extracting backend $*..."
322-
@CID=$$(docker create local-ai-backend:$*) && \
336+
@CID=$$(docker create --entrypoint=/run.sh local-ai-backend:$*) && \
323337
rm -rf local-backends/$* && mkdir -p local-backends/$* && \
324338
docker cp $$CID:/ - | tar -xf - -C local-backends/$* && \
325339
docker rm $$CID > /dev/null
@@ -594,6 +608,14 @@ test-extra-backend-vllm: docker-build-vllm
594608
BACKEND_TEST_OPTIONS=tool_parser:hermes \
595609
$(MAKE) test-extra-backend
596610

611+
## vllm multi-node data-parallel smoke test. Runs LocalAI head + a
612+
## `local-ai p2p-worker vllm` follower in docker compose against
613+
## Qwen2.5-0.5B with data_parallel_size=2. Requires 2 NVIDIA GPUs and
614+
## nvidia-container-runtime on the host — vLLM v1's DP coordinator is
615+
## not viable on CPU so this cannot run in CI without GPU.
616+
test-extra-backend-vllm-multinode:
617+
./tests/e2e/vllm-multinode/smoke.sh
618+
597619
## tinygrad mirrors the vllm target (same model, same caps, same parser) so
598620
## the two backends are directly comparable. The LLM path covers Predict,
599621
## streaming and native tool-call extraction. Companion targets below cover

backend/python/vllm/install.sh

Lines changed: 77 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ else
1818
source $backend_dir/../common/libbackend.sh
1919
fi
2020

21-
# This is here because the Intel pip index is broken and returns 200 status codes for every package name, it just doesn't return any package links.
22-
# This makes uv think that the package exists in the Intel pip index, and by default it stops looking at other pip indexes once it finds a match.
23-
# We need uv to continue falling through to the pypi default index to find optimum[openvino] in the pypi index
24-
# the --upgrade actually allows us to *downgrade* torch to the version provided in the Intel pip index
21+
# Intel XPU: torch==2.11.0+xpu lives on the PyTorch XPU index, transitive
22+
# deps on PyPI — unsafe-best-match lets uv mix both. vllm-xpu-kernels only
23+
# ships a python3.12 wheel per upstream docs, so bump the portable Python
24+
# before installRequirements (matches the l4t13 pattern below).
25+
# https://github.com/vllm-project/vllm/blob/main/docs/getting_started/installation/gpu.xpu.inc.md
2526
if [ "x${BUILD_PROFILE}" == "xintel" ]; then
26-
EXTRA_PIP_INSTALL_FLAGS+=" --upgrade --index-strategy=unsafe-first-match"
27+
PYTHON_VERSION="3.12"
28+
PYTHON_PATCH="11"
29+
EXTRA_PIP_INSTALL_FLAGS+=" --index-strategy=unsafe-best-match"
2730
fi
2831

2932
# CPU builds need unsafe-best-match to pull torch==2.10.0+cpu from the
@@ -42,27 +45,90 @@ fi
4245

4346
# JetPack 7 / L4T arm64 wheels (torch, vllm, flash-attn) live on
4447
# pypi.jetson-ai-lab.io and are built for cp312, so bump the venv Python
45-
# accordingly. JetPack 6 keeps cp310 + USE_PIP=true. unsafe-best-match
46-
# is required because the jetson-ai-lab index lists transitive deps at
47-
# limited versions — without it uv pins to the first matching index and
48-
# fails to resolve a compatible wheel from PyPI.
48+
# accordingly. JetPack 6 keeps cp310 + USE_PIP=true.
49+
#
50+
# l4t13 uses pyproject.toml (see the elif branch below) to pin only the
51+
# L4T-specific wheels to the jetson-ai-lab index via [tool.uv.sources].
52+
# That keeps PyPI as the resolution path for transitive deps like
53+
# anthropic/openai/propcache, which the L4T mirror's proxy 503s on.
4954
if [ "x${BUILD_PROFILE}" == "xl4t12" ]; then
5055
USE_PIP=true
5156
fi
5257
if [ "x${BUILD_PROFILE}" == "xl4t13" ]; then
5358
PYTHON_VERSION="3.12"
5459
PYTHON_PATCH="12"
5560
PY_STANDALONE_TAG="20251120"
56-
EXTRA_PIP_INSTALL_FLAGS+=" --index-strategy=unsafe-best-match"
5761
fi
5862

63+
# Intel XPU has no upstream-published vllm wheels, so we always build vllm
64+
# from source against torch-xpu and replace the default triton with
65+
# triton-xpu (matching torch 2.11). Mirrors the upstream procedure:
66+
# https://github.com/vllm-project/vllm/blob/main/docs/getting_started/installation/gpu.xpu.inc.md
67+
if [ "x${BUILD_TYPE}" == "xintel" ]; then
68+
# Hide requirements-intel-after.txt so installRequirements doesn't
69+
# try `pip install vllm` (would either fail or grab a non-XPU wheel).
70+
_intel_after="${backend_dir}/requirements-intel-after.txt"
71+
_intel_after_bak=""
72+
if [ -f "${_intel_after}" ]; then
73+
_intel_after_bak="${_intel_after}.xpu.bak"
74+
mv "${_intel_after}" "${_intel_after_bak}"
75+
fi
76+
installRequirements
77+
if [ -n "${_intel_after_bak}" ]; then
78+
mv "${_intel_after_bak}" "${_intel_after}"
79+
fi
80+
81+
# vllm's CMake build needs the Intel oneAPI dpcpp/sycl compiler — the
82+
# base image (intel/oneapi-basekit) has it but the env isn't sourced.
83+
if [ -f /opt/intel/oneapi/setvars.sh ]; then
84+
set +u
85+
source /opt/intel/oneapi/setvars.sh --force
86+
set -u
87+
fi
88+
89+
_vllm_src=$(mktemp -d)
90+
trap 'rm -rf "${_vllm_src}"' EXIT
91+
git clone --depth 1 https://github.com/vllm-project/vllm "${_vllm_src}/vllm"
92+
pushd "${_vllm_src}/vllm"
93+
# Install vllm's own runtime deps (torch-xpu, vllm_xpu_kernels,
94+
# pydantic, fastapi, …) from upstream's requirements/xpu.txt — the
95+
# canonical source of truth. Avoids re-pinning everything ourselves.
96+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} -r requirements/xpu.txt
97+
# Stock triton (NVIDIA-only) may have come in transitively; replace
98+
# with triton-xpu==3.7.0 which matches torch 2.11.
99+
uv pip uninstall triton triton-xpu 2>/dev/null || true
100+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} \
101+
--extra-index-url https://download.pytorch.org/whl/xpu \
102+
triton-xpu==3.7.0
103+
export CMAKE_PREFIX_PATH="$(python -c 'import site; print(site.getsitepackages()[0])'):${CMAKE_PREFIX_PATH:-}"
104+
VLLM_TARGET_DEVICE=xpu uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} --no-deps .
105+
popd
106+
# L4T arm64 (JetPack 7): drive the install through pyproject.toml so that
107+
# [tool.uv.sources] can pin torch/vllm/flash-attn/torchvision/torchaudio
108+
# to the jetson-ai-lab index, while everything else (transitive deps and
109+
# PyPI-resolvable packages like transformers) comes from PyPI. Bypasses
110+
# installRequirements because uv pip install -r requirements.txt does not
111+
# honor sources — see backend/python/vllm/pyproject.toml for the rationale.
112+
elif [ "x${BUILD_PROFILE}" == "xl4t13" ]; then
113+
ensureVenv
114+
if [ "x${PORTABLE_PYTHON}" == "xtrue" ]; then
115+
export C_INCLUDE_PATH="${C_INCLUDE_PATH:-}:$(_portable_dir)/include/python${PYTHON_VERSION}"
116+
fi
117+
pushd "${backend_dir}"
118+
# Build deps first (matches installRequirements' requirements-install.txt
119+
# pass — fastsafetensors and friends need pybind11 in the venv before
120+
# their sdists can build under --no-build-isolation).
121+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} -r requirements-install.txt
122+
uv pip install ${EXTRA_PIP_INSTALL_FLAGS:-} --requirement pyproject.toml
123+
popd
124+
runProtogen
59125
# FROM_SOURCE=true on a CPU build skips the prebuilt vllm wheel in
60126
# requirements-cpu-after.txt and compiles vllm locally against the host's
61127
# actual CPU. Not used by default because it takes ~30-40 minutes, but
62128
# kept here for hosts where the prebuilt wheel SIGILLs (CPU without the
63129
# required SIMD baseline, e.g. AVX-512 VNNI/BF16). Default CI uses a
64130
# bigger-runner with compatible hardware instead.
65-
if [ "x${BUILD_TYPE}" == "x" ] && [ "x${FROM_SOURCE:-}" == "xtrue" ]; then
131+
elif [ "x${BUILD_TYPE}" == "x" ] && [ "x${FROM_SOURCE:-}" == "xtrue" ]; then
66132
# Temporarily hide the prebuilt wheel so installRequirements doesn't
67133
# pull it — the rest of the requirements files (base deps, torch,
68134
# transformers) are still installed normally.

backend/python/vllm/package.sh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,109 @@ copy_with_symlinks() {
4545
copy_with_symlinks libnuma.so.1
4646
copy_with_symlinks libgomp.so.1
4747

48+
# CPU profile only: bundle a g++ toolchain so torch._inductor's
49+
# ISA probe (always run at vllm engine startup, regardless of
50+
# enforce_eager) finds a C++ compiler. The LocalAI runtime image
51+
# is FROM ubuntu:24.04 with a minimal apt list that does not
52+
# include build-essential, and the backend image itself is FROM
53+
# scratch -- so without this, cpu-vllm crashes with
54+
# torch._inductor.exc.InvalidCxxCompiler at first inference
55+
# unless the operator manually sets TORCH_COMPILE_DISABLE=1.
56+
#
57+
# We snapshot every file owned by the toolchain packages, mirroring
58+
# the /usr/... layout into ${BACKEND}/toolchain/ so g++ can find
59+
# cc1plus, headers, libs etc. via GCC_EXEC_PREFIX / CPATH /
60+
# LIBRARY_PATH at runtime (libbackend.sh wires those up). Adds
61+
# ~400 MB to the cpu-vllm image, which is tolerable -- cpu-vllm is
62+
# already a niche profile.
63+
if [ "${BUILD_TYPE:-}" = "" ] && command -v dpkg-query >/dev/null 2>&1; then
64+
TOOLCHAIN_DIR="${CURDIR}/toolchain"
65+
mkdir -p "${TOOLCHAIN_DIR}"
66+
# The unversioned g++/gcc packages on Debian/Ubuntu only ship
67+
# symlinks; the actual binaries live in g++-${VER}/gcc-${VER}.
68+
# Discover the active version so the symlink targets get bundled
69+
# along with their owners.
70+
GCC_VER=$(gcc -dumpversion 2>/dev/null | cut -d. -f1 || true)
71+
# `g++-${VER}` itself is just another symlink layer on Debian/
72+
# Ubuntu — the real binary `x86_64-linux-gnu-g++-${VER}` lives
73+
# in `g++-${VER}-x86-64-linux-gnu` (a separate package pulled in
74+
# as a dependency). Same story for gcc/cpp. Compute the dpkg
75+
# arch-triplet to find the right package name for both amd64 and
76+
# arm64 hosts.
77+
case "$(dpkg --print-architecture 2>/dev/null)" in
78+
amd64) HOST_TRIPLET="x86-64-linux-gnu" ;;
79+
arm64) HOST_TRIPLET="aarch64-linux-gnu" ;;
80+
*) HOST_TRIPLET="" ;;
81+
esac
82+
PKGS=(g++ gcc cpp libstdc++-${GCC_VER}-dev libgcc-${GCC_VER}-dev libc6 libc6-dev binutils binutils-common libbinutils libc-dev-bin linux-libc-dev libcrypt-dev libgomp1 libstdc++6 libgcc-s1 libisl23 libmpc3 libmpfr6 libjansson4 libctf0 libctf-nobfd0 libsframe1)
83+
if [ -n "${GCC_VER}" ]; then
84+
PKGS+=("g++-${GCC_VER}" "gcc-${GCC_VER}" "cpp-${GCC_VER}" "gcc-${GCC_VER}-base")
85+
if [ -n "${HOST_TRIPLET}" ]; then
86+
PKGS+=(
87+
"g++-${GCC_VER}-${HOST_TRIPLET}"
88+
"gcc-${GCC_VER}-${HOST_TRIPLET}"
89+
"cpp-${GCC_VER}-${HOST_TRIPLET}"
90+
"binutils-${HOST_TRIPLET}"
91+
)
92+
fi
93+
fi
94+
for pkg in "${PKGS[@]}"; do
95+
if ! dpkg-query -W "${pkg}" >/dev/null 2>&1; then
96+
continue
97+
fi
98+
# Copy each owned path, preserving symlinks and mode. We
99+
# tolerate dpkg listing directories alongside files.
100+
dpkg -L "${pkg}" | while IFS= read -r path; do
101+
if [ -L "${path}" ] || [ -f "${path}" ]; then
102+
mkdir -p "${TOOLCHAIN_DIR}$(dirname "${path}")"
103+
cp -aP "${path}" "${TOOLCHAIN_DIR}${path}" 2>/dev/null || true
104+
fi
105+
done
106+
done
107+
# Ubuntu's filesystem layout has /lib -> /usr/lib (UsrMerge) and
108+
# /lib64 -> /usr/lib64. ld scripts (e.g. libm.so) hardcode
109+
# `/lib/x86_64-linux-gnu/libm.so.6`; with --sysroot the linker
110+
# looks for that path under the sysroot, which means we need
111+
# the same symlinks under TOOLCHAIN_DIR.
112+
[ -e "${TOOLCHAIN_DIR}/lib" ] || ln -s usr/lib "${TOOLCHAIN_DIR}/lib"
113+
[ -e "${TOOLCHAIN_DIR}/lib64" ] || ln -s usr/lib64 "${TOOLCHAIN_DIR}/lib64"
114+
115+
# Replace the unversioned g++/gcc/cpp symlinks with wrapper
116+
# scripts that pass --sysroot=<toolchain> and -B <gcc-exec-prefix>.
117+
# Without these flags gcc would fall back to its compiled-in
118+
# /usr search and fail to find headers (the runtime image has no
119+
# libc6-dev) or fail to invoke `as`/`ld` (binutils not on PATH at
120+
# /usr/bin). Wrappers self-resolve their location at runtime so
121+
# they work from any BackendsPath.
122+
BIN_DIR="${TOOLCHAIN_DIR}/usr/bin"
123+
if [ -n "${GCC_VER}" ] && [ -n "${HOST_TRIPLET}" ]; then
124+
# HOST_TRIPLET in package names uses dashes ("x86-64-linux-gnu");
125+
# the binary suffix uses underscores in the arch part
126+
# ("x86_64-linux-gnu-g++-13"). Translate.
127+
BIN_TRIPLET=${HOST_TRIPLET//x86-64/x86_64}
128+
for tool in g++ gcc cpp; do
129+
real="${BIN_DIR}/${BIN_TRIPLET}-${tool}-${GCC_VER}"
130+
if [ -x "${real}" ]; then
131+
rm -f "${BIN_DIR}/${tool}" "${BIN_DIR}/${tool}-${GCC_VER}"
132+
cat > "${BIN_DIR}/${tool}" <<EOF
133+
#!/bin/bash
134+
# Auto-generated by package.sh. Passes --sysroot and -B so the
135+
# bundled toolchain works from any BackendsPath without depending
136+
# on libc6-dev / binutils being installed at /usr in the runtime
137+
# image. See backend/python/vllm/package.sh.
138+
DIR="\$(dirname "\$(readlink -f "\$0")")" # …/toolchain/usr/bin
139+
SYSROOT="\$(dirname "\$(dirname "\${DIR}")")" # …/toolchain
140+
exec "\${DIR}/${BIN_TRIPLET}-${tool}-${GCC_VER}" \\
141+
-B "\${SYSROOT}/usr/lib/gcc/${BIN_TRIPLET}/${GCC_VER}/" \\
142+
--sysroot="\${SYSROOT}" \\
143+
"\$@"
144+
EOF
145+
chmod +x "${BIN_DIR}/${tool}"
146+
fi
147+
done
148+
fi
149+
echo "Bundled g++ toolchain (gcc-${GCC_VER}) into ${TOOLCHAIN_DIR} ($(du -sh "${TOOLCHAIN_DIR}" | cut -f1))"
150+
fi
151+
48152
echo "vllm packaging completed successfully"
49153
ls -liah "${LIB_DIR}/"

backend/python/vllm/pyproject.toml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# L4T arm64 (JetPack 7 / sbsa cu130) install spec for the vllm backend.
2+
#
3+
# Why this file exists, and why only the l4t13 BUILD_PROFILE consumes it:
4+
#
5+
# pypi.jetson-ai-lab.io hosts the L4T-specific torch / vllm / flash-attn
6+
# wheels we need on aarch64 + cuda13, but it ALSO transparently proxies the
7+
# rest of PyPI through `/+f/<sha>/<filename>` URLs that 503 frequently. With
8+
# `--extra-index-url` + `--index-strategy=unsafe-best-match` (the historical
9+
# fix in install.sh) uv would pick those proxy URLs for ordinary PyPI
10+
# packages — `anthropic`, `openai`, `propcache`, `annotated-types` — and
11+
# trip on the 503s. See e.g. CI run 25212201349 (anthropic-0.97.0).
12+
#
13+
# `explicit = true` on the index makes uv consult the L4T mirror ONLY for
14+
# packages mapped under [tool.uv.sources]. Everything else goes to PyPI.
15+
# This breaks the historical 503 path without losing access to the L4T
16+
# wheels we actually need from there.
17+
#
18+
# `uv pip install -r requirements.txt` does NOT honor [tool.uv.sources]
19+
# (sources are project-mode only, not pip-compat mode), so install.sh's
20+
# l4t13 branch invokes `uv pip install --requirement pyproject.toml`
21+
# directly. Other BUILD_PROFILEs continue to use the requirements-*.txt
22+
# pipeline through libbackend.sh's installRequirements and never read
23+
# this file.
24+
[project]
25+
name = "localai-vllm-l4t13"
26+
version = "0.0.0"
27+
requires-python = ">=3.12,<3.13"
28+
dependencies = [
29+
# Mirror of requirements.txt — kept in sync manually for now since the
30+
# l4t13 path bypasses installRequirements (see install.sh).
31+
"grpcio==1.80.0",
32+
"protobuf",
33+
"certifi",
34+
"setuptools",
35+
"pillow",
36+
"charset-normalizer>=3.4.0",
37+
"chardet",
38+
# L4T-specific accelerator stack (sourced from jetson-ai-lab below).
39+
"torch",
40+
"torchvision",
41+
"torchaudio",
42+
"flash-attn",
43+
"vllm",
44+
# PyPI-resolvable packages that complete the runtime — accelerate,
45+
# transformers, bitsandbytes carry their own wheels for aarch64.
46+
"accelerate",
47+
"transformers",
48+
"bitsandbytes",
49+
]
50+
51+
[[tool.uv.index]]
52+
name = "jetson-ai-lab"
53+
url = "https://pypi.jetson-ai-lab.io/sbsa/cu130"
54+
explicit = true
55+
56+
[tool.uv.sources]
57+
torch = { index = "jetson-ai-lab" }
58+
torchvision = { index = "jetson-ai-lab" }
59+
torchaudio = { index = "jetson-ai-lab" }
60+
flash-attn = { index = "jetson-ai-lab" }
61+
vllm = { index = "jetson-ai-lab" }
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
vllm
1+
# Intel XPU has no upstream-published vllm wheels — install.sh builds vllm
2+
# from source with VLLM_TARGET_DEVICE=xpu and hides this file during
3+
# installRequirements. Don't add a `vllm` line here.

backend/python/vllm/requirements-intel.txt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
--extra-index-url https://download.pytorch.org/whl/xpu
2+
# vllm's own deps (torch==2.11.0+xpu, vllm_xpu_kernels, pydantic, …) are
3+
# installed from upstream's requirements/xpu.txt during the source build —
4+
# see install.sh. Only list what LocalAI's vllm backend.py needs directly.
25
accelerate
3-
torch
46
transformers
5-
optimum[openvino]
7+
bitsandbytes
68
setuptools
7-
bitsandbytes

backend/python/vllm/requirements-l4t13-after.txt

Lines changed: 0 additions & 2 deletions
This file was deleted.

backend/python/vllm/requirements-l4t13.txt

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
grpcio==1.80.0
22
protobuf
33
certifi
4-
setuptools
4+
setuptools
5+
pillow
6+
charset-normalizer>=3.4.0
7+
chardet

0 commit comments

Comments
 (0)