# syntax=docker/dockerfile:1.7

# Model-free llama.cpp CUDA appliance.
# The build is intentionally split into 10 named checkpoints. The expensive
# CUDA compilation is stage 06, so changes to the UI/runtime or the late
# llama-server linker workaround do not invalidate it.

ARG CUDA_VERSION=12.9.1
ARG UBUNTU_VERSION=24.04
ARG LLAMA_CPP_REF=master
# Broad CUDA matrix: Maxwell/Pascal/Volta/Turing/Ampere/Ada/Hopper + CUDA 12.9 Blackwell.
ARG CUDA_ARCHS="50-virtual;60-virtual;61-virtual;70-virtual;75-virtual;80-virtual;86-real;89-real;90-virtual;100-real;101-real;103-real;120a-real;121a-real"

# -----------------------------------------------------------------------------
# 01 - CUDA BASE
# -----------------------------------------------------------------------------
FROM nvidia/cuda:${CUDA_VERSION}-devel-ubuntu${UBUNTU_VERSION} AS s01-cuda-base
ARG CUDA_VERSION
ARG UBUNTU_VERSION
LABEL org.opencontainers.image.title="llama-model-appliance build checkpoint 01"
RUN printf 'CUDA_VERSION=%s\nUBUNTU_VERSION=%s\n' "$CUDA_VERSION" "$UBUNTU_VERSION" \
    > /build-checkpoint-01.txt

# -----------------------------------------------------------------------------
# 02 - TOOLCHAIN + CUDA DRIVER STUB VALIDATION
# -----------------------------------------------------------------------------
FROM s01-cuda-base AS s02-toolchain
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
      git ca-certificates cmake ninja-build build-essential pkg-config \
      libcurl4-openssl-dev binutils file \
    && rm -rf /var/lib/apt/lists/*
RUN set -eux; \
    nvcc --version; \
    cmake --version; \
    ninja --version; \
    c++ --version; \
    test -f /usr/local/cuda/lib64/stubs/libcuda.so; \
    file /usr/local/cuda/lib64/stubs/libcuda.so; \
    printf 'toolchain-ok\n' > /build-checkpoint-02.txt

# -----------------------------------------------------------------------------
# 03 - LLAMA.CPP SOURCE
# Works with a branch, tag, or reachable commit SHA via LLAMA_CPP_REF.
# -----------------------------------------------------------------------------
FROM s02-toolchain AS s03-llama-source
ARG LLAMA_CPP_REF
WORKDIR /src/llama.cpp
RUN set -eux; \
    git init .; \
    git remote add origin https://github.com/ggml-org/llama.cpp.git; \
    git fetch --depth 1 origin "${LLAMA_CPP_REF}"; \
    git checkout --detach FETCH_HEAD; \
    git rev-parse HEAD | tee /llama-cpp-commit.txt; \
    git status --short; \
    printf 'source-ok\n' > /build-checkpoint-03.txt

# -----------------------------------------------------------------------------
# 04 - CMAKE CONFIGURE ONLY
# Deliberately contains NO special libcuda linker workaround. If we need to
# change that workaround later, stages 04-07 (especially the expensive CUDA
# objects in stage 06) stay cacheable.
# -----------------------------------------------------------------------------
FROM s03-llama-source AS s04-cmake-configure
ARG CUDA_ARCHS
WORKDIR /src/llama.cpp
RUN set -eux; \
    printf 'CUDA_ARCHS=%s\n' "${CUDA_ARCHS}" | tee /cuda-architectures.txt; \
    cmake -S . -B build -G Ninja \
      -DCMAKE_BUILD_TYPE=Release \
      -DGGML_CUDA=ON \
      -DGGML_NATIVE=OFF \
      -DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHS}" \
      -DLLAMA_CURL=ON \
      -DLLAMA_BUILD_SERVER=ON \
      -DLLAMA_BUILD_TESTS=OFF \
      -DLLAMA_BUILD_EXAMPLES=OFF \
      -DGGML_CUDA_GRAPHS=ON \
      -DGGML_CUDA_FA=ON; \
    test -f build/build.ninja; \
    printf 'cmake-configure-ok\n' > /build-checkpoint-04.txt

# -----------------------------------------------------------------------------
# 05 - GGML CPU / BASE LIBRARIES
# -----------------------------------------------------------------------------
FROM s04-cmake-configure AS s05-ggml-cpu
WORKDIR /src/llama.cpp
RUN set -eux; \
    cmake --build build --target ggml-base ggml-cpu -j"$(nproc)"; \
    find build -type f \( -name 'libggml-base.so*' -o -name 'libggml-cpu.so*' \) -print; \
    printf 'ggml-cpu-ok\n' > /build-checkpoint-05.txt

# -----------------------------------------------------------------------------
# 06 - EXPENSIVE CUDA BACKEND CHECKPOINT
# This is the stage we want Docker/BuildKit to preserve aggressively.
# -----------------------------------------------------------------------------
FROM s05-ggml-cpu AS s06-ggml-cuda
WORKDIR /src/llama.cpp
RUN --mount=type=cache,target=/root/.cache \
    set -eux; \
    cmake --build build --target ggml-cuda -j"$(nproc)"; \
    test -n "$(find build -type f -name 'libggml-cuda.so*' -print -quit)"; \
    find build -type f -name 'libggml-cuda.so*' -exec ls -lh {} \;; \
    printf 'ggml-cuda-ok\n' > /build-checkpoint-06.txt

# -----------------------------------------------------------------------------
# 07 - LLAMA CORE LIBRARY
# -----------------------------------------------------------------------------
FROM s06-ggml-cuda AS s07-llama-core
WORKDIR /src/llama.cpp
RUN set -eux; \
    cmake --build build --target llama -j"$(nproc)"; \
    test -n "$(find build -type f -name 'libllama.so*' -print -quit)"; \
    find build -type f -name 'libllama.so*' -exec ls -lh {} \;; \
    printf 'llama-core-ok\n' > /build-checkpoint-07.txt

# -----------------------------------------------------------------------------
# 08 - LLAMA-SERVER LINK
# Late linker workaround for CUDA Driver API symbols.
# We intentionally install a libcuda.so.1 symlink to the CUDA TOOLKIT STUB only
# in this build stage. It is never copied into the runtime image. Keeping this
# fix here means changing it does not force stage 06 CUDA recompilation.
# -----------------------------------------------------------------------------
FROM s07-llama-core AS s08-server-link
WORKDIR /src/llama.cpp
RUN set -eux; \
    test -f /usr/local/cuda/lib64/stubs/libcuda.so; \
    install -d /usr/local/lib/cuda-stubs; \
    ln -sf /usr/local/cuda/lib64/stubs/libcuda.so /usr/local/lib/cuda-stubs/libcuda.so.1; \
    ln -sf /usr/local/cuda/lib64/stubs/libcuda.so /usr/lib/x86_64-linux-gnu/libcuda.so.1; \
    LD_LIBRARY_PATH=/usr/local/lib/cuda-stubs:/usr/local/cuda/lib64/stubs:${LD_LIBRARY_PATH:-} \
      cmake --build build --target llama-server -j"$(nproc)"; \
    test -x build/bin/llama-server; \
    ls -lh build/bin/llama-server; \
    printf 'server-link-ok\n' > /build-checkpoint-08.txt

# -----------------------------------------------------------------------------
# 09 - LINK / BINARY SMOKE TEST
# No GPU is required here. The CUDA stub only satisfies build-time loader/linker
# discovery; the real NVIDIA driver is supplied by NVIDIA Container Toolkit at
# runtime when the final container is started with --gpus all.
# -----------------------------------------------------------------------------
FROM s08-server-link AS s09-smoke-test
WORKDIR /src/llama.cpp
RUN set -eux; \
    test -x build/bin/llama-server; \
    echo '--- llama-server file ---'; \
    file build/bin/llama-server; \
    echo '--- llama-server dynamic dependencies ---'; \
    LD_LIBRARY_PATH=/usr/local/lib/cuda-stubs:/usr/local/cuda/lib64/stubs:${LD_LIBRARY_PATH:-} \
      ldd build/bin/llama-server | tee /llama-server.ldd; \
    if grep -q 'not found' /llama-server.ldd; then \
      echo 'ERROR: unresolved llama-server shared-library dependency'; \
      cat /llama-server.ldd; \
      exit 1; \
    fi; \
    echo '--- CUDA backend dynamic dependencies ---'; \
    CUDA_SO="$(find build/bin -maxdepth 1 -name 'libggml-cuda.so*' -type f | head -n1)"; \
    test -n "$CUDA_SO"; \
    LD_LIBRARY_PATH=/usr/local/lib/cuda-stubs:/usr/local/cuda/lib64/stubs:${LD_LIBRARY_PATH:-} \
      ldd "$CUDA_SO" | tee /ggml-cuda.ldd; \
    if grep -q 'not found' /ggml-cuda.ldd; then \
      echo 'ERROR: unresolved CUDA-backend shared-library dependency'; \
      cat /ggml-cuda.ldd; \
      exit 1; \
    fi; \
    readelf -d "$CUDA_SO" | grep -E 'NEEDED|SONAME' | tee /ggml-cuda.dynamic; \
    printf 'smoke-test-ok\n' > /build-checkpoint-09.txt

# -----------------------------------------------------------------------------
# 10 - FINAL MODEL-FREE RUNTIME APPLIANCE
# Application files are copied only here. Editing HTML/Python therefore cannot
# invalidate stages 01-09 or trigger a CUDA rebuild.
# -----------------------------------------------------------------------------
FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_VERSION} AS s10-runtime
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
      python3 python3-pip python3-venv ca-certificates curl libgomp1 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /opt/appliance
COPY --from=s09-smoke-test /src/llama.cpp/build/bin/llama-server /usr/local/bin/llama-server
COPY --from=s09-smoke-test /src/llama.cpp/build/bin/*.so* /usr/local/lib/llama/
COPY --from=s09-smoke-test /llama-cpp-commit.txt /opt/appliance/llama-cpp-commit.txt
COPY --from=s09-smoke-test /cuda-architectures.txt /opt/appliance/cuda-architectures.txt
ENV LD_LIBRARY_PATH=/usr/local/lib/llama:${LD_LIBRARY_PATH}

COPY requirements.txt /opt/appliance/requirements.txt
RUN python3 -m pip install --break-system-packages --no-cache-dir -r /opt/appliance/requirements.txt
COPY app /opt/appliance/app

ENV MODEL_PATHS=/models \
    CONFIG_DIR=/config \
    RESULTS_DIR=/results \
    RESULTS_WRITE_INTERVAL=1.0 \
    LLAMA_SERVER=/usr/local/bin/llama-server \
    SETTINGS_PORT=8081 \
    QUEUE_PORT=8082 \
    INFERENCE_PORT=8080 \
    PYTHONUNBUFFERED=1

VOLUME ["/models", "/config", "/results"]
EXPOSE 8080 8081 8082
STOPSIGNAL SIGTERM
ENTRYPOINT ["python3", "/opt/appliance/app/main.py"]
