vectorhd / Dockerfile
mcyakar's picture
deploy: VectorHD 0.2.10 (source ff79722)
5e84645
Raw
History Blame Contribute Delete
5.24 kB
# VectorHD β€” CPU-only service container.
#
# docker build -t vectorhd .
# docker run -p 7860:7860 -e VECTORHD_API_TOKEN=secret vectorhd
#
# CPU-only is the whole story now, not a slim variant of a GPU build. The retrofit replaced
# DiffVG (the only CUDA-relevant path) with the numpy/scipy analytic optimizer, the Rust kernel
# accelerates that on CPU, and the perception net β€” the sole remaining torch consumer β€” is gated
# off by default because every head measurably harmed geometry. So there is one target, and it is
# this one. (The old `cuda` target was removed for exactly that reason; see git history.)
#
# Stage 1 compiles the optional `vectorhd_core` Rust extension (β‰ˆ2.1Γ— on analytic refine); stage 2
# is a slim runtime with no compilers. The extension is optional by design β€” if stage 1 were ever
# dropped, `refine_backend=auto` silently serves the pure-Python kernel and the service still runs.
ARG PYTHON_VERSION=3.11
# --------------------------------------------------------------------- stage 1: Rust extension
# Must share PYTHON_VERSION with the runtime: the wheel is built for a specific CPython ABI
# (cp311), so a mismatch here yields a wheel the runtime cannot import.
FROM python:${PYTHON_VERSION}-slim AS rust-builder
ENV PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
CARGO_HOME=/usr/local/cargo \
RUSTUP_HOME=/usr/local/rustup \
PATH=/usr/local/cargo/bin:$PATH
RUN apt-get update && apt-get install -y --no-install-recommends \
curl build-essential ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain stable \
&& pip install "maturin>=1.5,<2"
WORKDIR /build
# Only the crate manifest + sources: the 46 MB of test vectors and the wasm harness are not
# build inputs (and are excluded from the context by .dockerignore anyway).
COPY rust/vectorhd-core/Cargo.toml rust/vectorhd-core/pyproject.toml ./
COPY rust/vectorhd-core/src ./src
# `--features python` comes from the crate's [tool.maturin]; release for the optimised kernel.
RUN maturin build --release --out /wheels
# --------------------------------------------------------------------------- stage 2: runtime
FROM python:${PYTHON_VERSION}-slim AS runtime
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1 \
VIRTUAL_ENV=/opt/venv \
PATH=/opt/venv/bin:$PATH
# opencv-python-headless still wants libGL/glib at import time even in its headless build.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgl1 libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/*
RUN python -m venv "$VIRTUAL_ENV"
WORKDIR /app
# CPU torch FIRST, from the CPU index: this satisfies the `torch>=2.2` core dependency so the
# subsequent `pip install .` does not pull the multi-GB CUDA wheel from PyPI.
RUN pip install --index-url https://download.pytorch.org/whl/cpu "torch>=2.2"
COPY pyproject.toml README.md ./
COPY src ./src
RUN pip install .
# The optional Rust kernel, built in stage 1. `refine_backend=auto` picks it up automatically.
COPY --from=rust-builder /wheels/*.whl /tmp/wheels/
RUN pip install /tmp/wheels/*.whl && rm -rf /tmp/wheels
# --- Runtime configuration ---------------------------------------------------------------
# Tuned for a small shared-CPU host (Hugging Face free tier = 2 vCPU / 16 GB).
#
# WORKER_POOL_SIZE=1: each worker runs the whole CPU-bound pipeline and parallelises internally
# through BLAS. On 2 vCPU a second worker would contend with the first rather than add throughput;
# one worker + capped BLAS threads keeps a single hd job from fighting itself. Raise this on a
# bigger box (rule of thumb: β‰ˆ cores), and bound concurrency at the edge either way.
#
# The *_NUM_THREADS caps are read by numpy/scipy/OpenCV at import. They are set here (container
# env) rather than in code so they are inherited by the spawned pool workers, which re-import
# numpy fresh in a new interpreter.
ENV VECTORHD_DEVICE=cpu \
VECTORHD_WORKER_POOL_SIZE=1 \
VECTORHD_WEIGHTS_DIR=/weights \
OMP_NUM_THREADS=2 \
OPENBLAS_NUM_THREADS=2 \
MKL_NUM_THREADS=2 \
NUMEXPR_NUM_THREADS=2 \
VECLIB_MAXIMUM_THREADS=2 \
PORT=7860
# Non-root. /app holds no writable state (nothing is ever persisted β€” see the privacy note), so
# the runtime user only needs read access.
RUN useradd --create-home --uid 1000 appuser && chown -R appuser:appuser /app
USER appuser
# 7860 is the Hugging Face Spaces convention; $PORT overrides it for other hosts.
EXPOSE 7860
# No curl in the image β€” urllib is already present and keeps the runtime layer lean.
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s --retries=3 \
CMD python -c "import os,urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:'+os.environ.get('PORT','7860')+'/healthz', timeout=4).status==200 else 1)"
# Shell form so ${PORT} expands. One uvicorn process: request concurrency is the worker pool's
# job, and a second uvicorn worker would double the pool.
CMD ["sh", "-c", "exec uvicorn vectorhd.api.app:app --host 0.0.0.0 --port ${PORT:-7860}"]