# ── Base image ──────────────────────────────────────────────── # Alpine (musl libc), not Debian slim (glibc) — the abetlen prebuilt CPU # wheel for llama-cpp-python is itself linked against musl (confirmed via # readelf: NEEDED libc.musl-x86_64.so.1), even though its filename is # tagged with the generic "linux_x86_64" platform tag rather than a # proper musllinux tag. On a glibc image this crashes at import time with # "OSError: libc.musl-x86_64.so.1: cannot open shared object file". # All other compiled deps here (pydantic-core, uvloop, httptools) publish # real musllinux wheels, so nothing needs to compile from source. FROM python:3.11-alpine # ── Environment ─────────────────────────────────────────────── ENV PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 # ── System dependencies ─────────────────────────────────────── # Runtime-only — libstdc++/libgomp satisfy the wheel's NEEDED entries # (ggml-cpu uses OpenMP + C++). No compiler needed since nothing builds # from source. RUN apk add --no-cache curl libstdc++ libgomp # ── Work directory ──────────────────────────────────────────── WORKDIR /app # ── Python dependencies ─────────────────────────────────────── # Install llama-cpp-python from precompiled CPU wheels (no compile — fast!) # Pinned version + --only-binary forces pip to fail fast if the abetlen wheel # index ever stops publishing a matching wheel, instead of silently falling # back to a from-source cmake build that blows past HF's build timeout. # Plain llama-cpp-python (no [server] extra) — we load the model directly # with the Llama class and implement the OpenAI-style routes ourselves, # rather than running llama_cpp.server's own bundled FastAPI app (which had # a bug in its generic error-handling path that returned empty 400s on every # proxied request). Then install our own minimal FastAPI stack. RUN pip install --upgrade pip && \ pip install \ --only-binary=llama-cpp-python \ "llama-cpp-python==0.3.19" \ --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cpu && \ pip install \ fastapi \ "uvicorn[standard]" \ huggingface_hub \ pydantic # ── Copy app ────────────────────────────────────────────────── COPY app.py . # ── Port ────────────────────────────────────────────────────── EXPOSE 7860 # ── Default environment (override via Space Settings) ───────── # SPACE_ROLE: "reasoning" | "uncensored" | "general" (default: general) — # picks the curated model automatically. Deliberately NOT setting MODEL_ID # here: a hardcoded ENV MODEL_ID would always exist in os.environ, so # app.py's `os.environ.get("MODEL_ID", ROLE_TO_MODEL_ID[SPACE_ROLE])` # fallback could never trigger — every Space would pin to that value # regardless of SPACE_ROLE. (This bit us once: this line used to read # `ENV MODEL_ID="qwen"` and silently overrode SPACE_ROLE on every Space.) # MODEL_ID / MODEL_REPO / MODEL_FILE: optional raw overrides, set only as a # Space Variable if you want to bypass role-based selection entirely. # N_CTX: context window tokens (default: 8192) # N_THREADS: CPU threads (default: 4) # API_KEY: optional bearer token to protect the API # CONFIG_URL: optional URL to a JSON config (for multi-Space clusters) ENV N_CTX="8192" \ N_THREADS="4" # ── Launch ──────────────────────────────────────────────────── CMD ["python", "app.py"]