Files
vllm-omni/docker/Dockerfile.ci
T
2026-09-10 00:41:53 +08:00

162 lines
8.0 KiB
Docker

# Target vllm version: releases/v0.29.0 @ d2906cc19586, installed on top of
# the v0.28.0 base image via the precompiled wheel pinned below. The pin and
# base intentionally differ until an official v0.29.0 base image is published.
#
# Pinned form: VLLM_PRECOMPILED_WHEEL_COMMIT is the rebase baseline — the rebase
# repo-rebase-v3 advances it on each run. Keep the torch/flashinfer/numpy pins
# aligned with that commit's requirements/cuda.txt and generated test lock.
# VLLM_BASE_TAG stays at the newest official vLLM release below the pin; the
# exact vLLM comes from the wheel, not the image.
#
# This block is required whenever the target has no matching official base
# image, including release candidates. Drop back to the release-boundary form
# (no wheel pin, no reinstalls, everything from the base image as one
# self-consistent set) only after the exact tagged image is published.
ARG VLLM_BASE_IMAGE=vllm/vllm-openai
ARG VLLM_BASE_TAG=v0.28.0
# We use registry-based layer caching for the dependencies,
# where the tag is determined based on the hash of the contents
# of files like pyproject.toml, etc. The deps stage should have
# everything except the actual project dir, since that should not
# change much across branches.
FROM ${VLLM_BASE_IMAGE}:${VLLM_BASE_TAG} AS deps
# Keep APP_DIR outside /workspace so k8s Buildkite workspace mounts cannot
# shadow the editable install target (#5364). Docker WORKDIR creates /opt if
# needed.
ARG APP_DIR=/opt/vllm-omni
WORKDIR ${APP_DIR}
RUN ln -sf /usr/bin/python3 /usr/bin/python
# Placeholder package tree so editable install can register APP_DIR.
# The final stage COPY --link fills this directory with the real sources;
# the editable .pth / egg-link keeps resolving imports there — no PYTHONPATH.
RUN mkdir -p vllm_omni && touch vllm_omni/__init__.py
# Install system dependencies
RUN apt-get update && \
apt-get install -y espeak-ng git jq && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Copy dependency related things first and install deps/
# We set the version str to 0.0.0 since it doesn't matter
# for CI
COPY pyproject.toml setup.py ./
COPY requirements/ requirements/
ENV SETUPTOOLS_SCM_PRETEND_VERSION=0.0.0
# Editable install: deps + project metadata are cached in this layer, while
# the package itself is linked to APP_DIR (populated later by COPY --link).
# Do NOT use a non-editable install here — that copies an empty stub into
# site-packages and breaks ``import vllm_omni.entrypoints``.
RUN uv pip install --system -e ".[dev]"
# Install vLLM (CUDA 13.0 build) from precompiled wheel at the selected commit.
# Must use direct URL because the wheel has a PEP 440 local version identifier
# (e.g. +gaeee7ef93.cu130) which pip/uv refuse to install from a PEP 503 package index.
# The cu130 wheel index lives at /<commit>/cu130/vllm/ but the actual wheel files
# are stored at the top level /<commit>/<wheel>.
ENV VLLM_PRECOMPILED_WHEEL_COMMIT=d2906cc1958658f296aeee8b248deea684f56add
# Keep the base image's CUDA toolkit libs on the loader path. This does NOT by
# itself guarantee libnvJitLink.so.13 resolves — the .so.13 JIT linker is
# handled separately via ldconfig after the cu130 wheels are installed (see the
# nvidia-nvjitlink-cu13 step below), which is a no-op when the base image
# already provides it.
ENV LD_LIBRARY_PATH=/usr/local/cuda/targets/x86_64-linux/lib:${LD_LIBRARY_PATH}
RUN VLLM_WHEEL_URL=$(python3 -c "import urllib.request,re; \
from urllib.parse import urljoin, quote; \
index_url='https://wheels.vllm.ai/${VLLM_PRECOMPILED_WHEEL_COMMIT}/cu130/vllm/'; \
html=urllib.request.urlopen(index_url).read().decode(); \
m=re.search(r'href=\"([^\"]*x86_64\.whl)\"',html); \
assert m, f'No x86_64 vLLM wheel found at {index_url}'; \
print(quote(urljoin(index_url,m.group(1)), safe=':/+%'))") && \
echo "Installing vLLM from: ${VLLM_WHEEL_URL}" && \
uv pip install --system --force-reinstall "${VLLM_WHEEL_URL}"
# Pin torch BEFORE flashinfer. Versions mirror vLLM requirements/cuda.txt at
# VLLM_PRECOMPILED_WHEEL_COMMIT.
#
# The ordering is the load-bearing part, not the version. When the base image
# and the pinned wheel agree on torch this pin is a no-op guard; when they
# diverge it is what keeps flashinfer from resolving against a torch that the
# vLLM reinstall then removes. That failure mode is not hypothetical: during the
# 0.27 rebase (v0.27.0rc1 wheel on a v0.26.0 base, torch 2.11.0 -> 2.13.0),
# installing flashinfer first bound its compiled artifacts — and its bundled TVM
# FFI runtime — to the old torch, producing a C++ abort during engine-core init:
#
# terminate called after throwing an instance of 'tvm::ffi::Error'
# what(): TypeAttr `__ffi_repr__` is already registered for type index 132
#
# plus `from deep_ep_cpp import EventHandle` -> ImportError, which together took
# out 21 CI jobs on build #2947 that pass on main.
#
# Installing torch first means FlashInfer resolves against the final runtime.
RUN uv pip install --system --force-reinstall \
"torch==2.13.0" "torchaudio==2.11.0" "torchvision==0.28.0" \
--torch-backend cu130
# No --upgrade here: with torch already at its pinned version, --upgrade is what
# would drag it forward again and reintroduce the mismatch this ordering fixes.
RUN uv pip install --system \
"flashinfer-python==0.6.18" \
"flashinfer-cubin==0.6.18" \
--extra-index-url https://flashinfer.ai/whl/
RUN uv pip install --system \
"flashinfer-jit-cache==0.6.18" \
--index-url https://flashinfer.ai/whl/cu130
# Fail the build here rather than at engine-core init if anything above moved
# torch off its pin. The previous failure mode was a C++ terminate inside a
# worker subprocess, which surfaced only as "Failed core proc(s): {}" — an
# empty set, hours into the test run.
RUN python3 -c "import torch, sys; \
want='2.13.0'; got=torch.__version__.split('+')[0]; \
sys.exit(0) if got==want else sys.exit(f'torch pin drifted: want {want}, got {torch.__version__}')" && \
python3 -c "import deep_ep_cpp" 2>/dev/null \
&& echo 'deep_ep_cpp imports cleanly' \
|| echo 'WARNING: deep_ep_cpp not importable (non-fatal; vLLM treats deep_ep as optional)'
# CUDA 13 JIT linker: libnvJitLink.so.13 is provided by the nvidia-nvjitlink
# package that the cu130 torch/vllm wheels above pull into site-packages. When
# the base image ships a CUDA-12 toolkit, /usr/local/cuda never resolves the
# .so.13 and the LD_LIBRARY_PATH above is not enough. Even when it is present,
# its lib dir is not on the dynamic-loader path, so bitsandbytes / torch.compile
# worker subprocesses fail to dlopen it. Locate the already-installed lib and
# register its dir with ldconfig (system-wide, survives subprocess env resets).
# Only fall back to the pip wheel (correct name: nvidia-nvjitlink-cu13, NOT
# nvidia-cuda-nvjitlink-cu13, which does not exist) if the torch deps did not
# bring it in.
RUN NVJITLINK_LIB="$(find / -name 'libnvJitLink.so.13*' 2>/dev/null | head -1)" && \
if [ -z "$NVJITLINK_LIB" ]; then \
uv pip install --system "nvidia-nvjitlink-cu13" && \
NVJITLINK_LIB="$(find / -name 'libnvJitLink.so.13*' 2>/dev/null | head -1)"; \
fi && \
test -n "$NVJITLINK_LIB" && \
dirname "$NVJITLINK_LIB" > /etc/ld.so.conf.d/nvjitlink-cu13.conf && \
ldconfig && \
echo "Registered ${NVJITLINK_LIB} with ldconfig"
# Re-pin numpy LAST. The v0.29.0rc1 CUDA requirements use numba 0.65.0 and the
# generated CUDA test lock resolves numpy 2.2.6. This must be the final
# dependency operation so the wheel and FlashInfer installs cannot clobber it.
RUN uv pip install --system "numpy==2.2.6"
# The final stage just copies the working dir on top of the
# dependencies image. This uses --link so that we can skip
# the image pull in Buildkite when we have a cache hit.
#
# IMPORTANT: Do not make any modifications that depend on the
# file system after the --link copy, otherwise we'd need to pull
# the heavy base image to build it. Editable install above already
# registered APP_DIR, so we must not re-pip-install after COPY.
FROM deps
ARG APP_DIR=/opt/vllm-omni
WORKDIR ${APP_DIR}
COPY --link . .
ENTRYPOINT []