# syntax=docker/dockerfile:1 # Three named build contexts feed this image. docker-compose.yml passes all three by itself: # # ops the compiled kernels from build.sh step 8 (this repo's artifacts/). The image then # installs them instead of compiling: ~2 min. # uvcache $HOME/.cache/uv ─┐ two wheel caches, so the build does not re-download the # pipcache $HOME/.cache/pip ─┘ torch + CUDA wheel set (~6 GB). Read only. # # Plain `docker build -t vllm:latest .` passes none of them: the ops context is empty, so the # image compiles every kernel from source, and the wheel caches start empty. BuildKit keeps the # caches in its own volumes after the first build, so a rebuild downloads nothing again. # # docker compose up -d # kernels installed # docker build -t vllm:latest . # compiles # docker build --build-context ops="$PWD/artifacts" -t vllm:latest . # no compile # # A cache mount copies its context into the BuildKit cache volume the first time and reuses it # after that. The caches on the host are only read: the build never writes to them. # # build.sh compares the tree object, the arch, CPython, CUDA and torch in the manifest with the # tree it installs into, and hashes every unpacked file. A tarball that does not fit this tree # stops the build. The image build runs with PACK=0: an image layer must not carry a 340 MB tarball. FROM scratch AS uvcache FROM scratch AS pipcache FROM scratch AS ops # trixie, not bookworm: the kernels of a host build on Debian 13 need glibc 2.38, bookworm has 2.36, # so the loader would refuse them at serve time. trixie has 2.41. FROM python:3.13-slim-trixie # vllm: Qwen3.8-Flash-Next-W4A16 at 262K on 4x RTX 3090 (vLLM 0.30.0 + patches/). # # docker build -t vllm:latest . # docker run -d --name vllm --gpus all --stop-timeout 20 --shm-size=8g \ # --ulimit memlock=-1 -p 8080:8000 \ # -v /data/models/halt95/Swift1.5-Qwen3.8-Flash-Next-W4A16-Merlin:/models/model:ro \ # -v "$PWD/.vllm-cache":/cache \ # -e MODEL_NAME=Scarlet vllm:latest # # The checkpoint mount must contain qsa_kv_scales_swift.json (the FP8 KV scales sidecar). # # The build fetches vLLM v0.30.0 and applies patches/ (build.sh step 2; needs network), then compiles # every kernel of this tree from source (build.sh: cmake + ninja + nvcc 13.0 from the venv's # CUDA wheels, arch 8.6). No prebuilt binaries go in. Budget 20-40 min and ~15 GB of build disk. # Pass the ops context (see above) to install prebuilt kernels instead: ~2 min, same tree. # trixie ships cmake 3.25, which is too old, so cmake and ninja come from pip. # # Layers are split so a rebuild stays cheap. The long compile only re-runs when a file it reads # changes (build.sh, scripts/, patches/, start.sh). Docs, README, compose and # the example env land in a later COPY, so editing them costs seconds. The apt, pip, uv and ccache # caches are BuildKit cache mounts: they live outside the image, so a rebuild downloads nothing # again and recompiles only the kernels whose source changed (build.sh prints the ccache stats). ARG MAX_JOBS=32 ARG ARCH=8.6 ENV DEBIAN_FRONTEND=noninteractive PIP_NO_CACHE_DIR=1 RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates tar gzip procps \ build-essential ccache binutils WORKDIR /opt/vllm # The build tools. These two mounts have their own ids: a cache mount with the same target path # shares one volume, and a from= seed applies only when the volume is first created. Without the # ids, this layer would create the pip/uv volumes empty and the seeds of the compile layer below # would never be read. (PIP_NO_CACHE_DIR= (empty) makes pip use the cache.) # build.sh uses uv for the venv when it is on PATH; it makes the 196-pin install several times faster. # cmake<4: pip ships 4.x, the tree and its vendored projects are built and tested on 3.x. RUN --mount=type=cache,id=tools-pip,target=/root/.cache/pip,sharing=locked \ --mount=type=cache,id=tools-uv,target=/root/.cache/uv,sharing=locked \ PIP_NO_CACHE_DIR= python -m pip install uv 'cmake<4' ninja # The build inputs, in one layer. Nothing here changes on a docs edit. COPY build.sh start.sh ./ COPY scripts/ scripts/ COPY patches/ patches/ RUN --mount=type=cache,id=compile-uv,target=/root/.cache/uv,from=uvcache,sharing=locked \ --mount=type=cache,id=compile-pip,target=/root/.cache/pip,from=pipcache,sharing=locked \ --mount=type=cache,id=compile-ccache,target=/root/.cache/ccache \ --mount=type=bind,from=ops,target=/ops,ro \ chmod +x start.sh build.sh scripts/*.sh scripts/*.py \ && echo "==> caches at the start of the compile:" && (du -sh /root/.cache/uv /root/.cache/pip /root/.cache/ccache 2>/dev/null || true) \ && ops_tar="$(ls -1 /ops/build-artifacts-*.tar.gz 2>/dev/null | head -1 || true)" \ && if [ -n "$ops_tar" ]; then echo "==> ops source: $ops_tar (prebuilt, no compile)"; else echo "==> ops source: none passed, compiling from source"; fi \ && PIP_NO_CACHE_DIR= MAX_JOBS="$MAX_JOBS" ARCH="$ARCH" PACK=0 ARTIFACTS="$ops_tar" bash build.sh \ && rm -rf /opt/vllm/vllm/.deps /opt/vllm/vllm/CMakeUserPresets.json \ /opt/vllm/build-*.log # The record build.sh writes (vllm/.build-products, vllm/.build-products.sha256) is KEPT on # purpose: start.sh re-hashes every kernel against it before it starts the engine. # Everything else: README, Dockerfile, compose, .env.example, systemd/. A copy, so it is cheap. COPY . /opt/vllm/ RUN chmod +x start.sh build.sh scripts/*.sh scripts/*.py \ && rm -rf /root/.cache ENV TREE=/opt/vllm/vllm VENV=/opt/vllm/.venv HOST=0.0.0.0 PORT=8000 CACHE_ROOT=/cache \ MODEL_NAME=Scarlet MODEL_MOUNT_PATH=/models/model LOG= \ CUDA_HOME=/opt/vllm/.venv/lib/python3.13/site-packages/nvidia/cu13 \ FLASHINFER_WORKSPACE_BASE=/cache/flashinfer TRITON_CACHE_DIR=/cache/triton ENV PATH="/opt/vllm/.venv/lib/python3.13/site-packages/nvidia/cu13/bin:$PATH" VOLUME /cache EXPOSE 8000 # /v1/models answers 200 even when the engine is dead: probe a real one-token generation. HEALTHCHECK --interval=60s --timeout=90s --start-period=30m --retries=3 \ CMD m="${MODEL_NAME%% *}"; m="${m:-Scarlet}"; curl -fs -m 85 -X POST "http://127.0.0.1:${PORT}/v1/chat/completions" \ -H "Content-Type: application/json" ${VLLM_API_KEY:+-H "Authorization: Bearer $VLLM_API_KEY"} \ -d "{\"model\":\"$m\",\"messages\":[{\"role\":\"user\",\"content\":\"ok\"}],\"max_tokens\":1,\"chat_template_kwargs\":{\"enable_thinking\":false}}" \ | grep -q '"choices"' || exit 1 ENTRYPOINT ["/opt/vllm/start.sh"] CMD ["/models/model"]